Skip to content
code0.HDL

A module, its ports, and assign

Read a half adder as a box with ports, learn the module and port declaration forms, predict what its testbench prints, run it, then change one wire and see the print change.

In this chapter

  • Declare a module with ANSI-style ports and say what direction, kind and width each port has
  • Derive the half adder from its truth table, sum as XOR and carry as AND, and name the printed columns that confirm it
  • Explain why assign is a permanent connection rather than a statement that runs once
  • Read a testbench with an initial block, named port connections and $display, and predict its output before running
  • Name the two Icarus steps a Run performs and what a printed line does and does not prove
Concepts in this chapter
  • module, ports, assign
Prerequisite concepts

1. A module is a box with ports

Verilog is a hardware description language: you write text, and the text describes a digital circuit. It looks a lot like C, but a line of Verilog is not a command to execute. Most lines declare how wires and gates are connected.

The unit of a Verilog design is the module: a name, the ports that connect it to the outside, and the behavior inside. One module describes one block of circuitry, and its ports are the terminals through which it connects to other blocks. Ports have a direction. input brings a signal in; output sends one out. The behavior inside the module sees the outside only through the port names.

Form

module module_name (port list);
  // signal declarations
  // circuit description
endmodule

Here is about the smallest useful module: it outputs the AND of two inputs.

module and_gate (input a, input b, output y);
  assign y = a & b;
endmodule
CodeMeaning
module and_gate (...)Starts a module named and_gate and declares its ports
input a, input bTwo 1-bit input ports
output yOne 1-bit output port
assign y = a & b;Declares that y is always the AND of a and b
endmoduleCloses the module

Note. A module body is not a program that runs once from top to bottom. assign y = a & b; does not mean "put a value in y now"; it means "y equals a & b, always". The simulator recomputes y every time a or b changes.

A port is one bit unless it says otherwise. Ports that bundle several bits are vectors, the subject of Vectors, bit selects and operators.

1.1 Lexical rules

ItemRuleExample
CaseSignificantData and data are different names
Line commentFrom // to the end of the line// output port
Block commentFrom /* to *//* description */
End of statementSimple statements end with ;; a begin/end block ends with endassign y = a;
GroupingWhere the syntax expects one statement, begin and end group severalinitial begin ... end

2. Ports: direction, kind and width

2.1 Declaring ports in the port list

In the ANSI style, each port's direction, kind, and width are declared right in the port list. Every example in this course uses this style.

Form

module module_name (
  direction [kind] [width] port_name,
  direction [kind] [width] port_name
);
  // module body
endmodule
module counter (
  input            clock,
  input      [7:0] limit,
  output reg [7:0] count
);
PartMeaning
input, outputThe port's direction
regThe port's kind. Without it, the port is a wire
[7:0]The port's width. Without it, the port is one bit wide
clock, limit, countPort names. The module body reads and writes the signals by these names

There is no comma after the last port. The body follows the ); that closes the port list.

2.2 Port directions and kinds

DirectionInside the moduleWhat the parent may connect
inputA net the module only readsAny expression
outputA net by default; a variable if declared output regA net
inoutA bidirectional netA net

If an output is driven by assign, leave it a wire. If it is assigned inside always or initial, declare it output reg. This is the net-versus-variable rule that Nets, variables and the combinational always spells out; until then, every output in this chapter is driven by assign and stays a wire.

Tip. Older code often lists only the port names in the header and declares direction and width again in the body. Both styles describe the same module.

module and_old (a, b, y);
  input  a, b;
  output y;
  assign y = a & b;
endmodule

2.3 The top module

The module that no other module instantiates is the top of the hierarchy. In this course the top is always a testbench called tb, which has no ports. Icarus finds the module nobody uses and makes it the top automatically.

3. assign is a wire

assign left = right; connects the value of the right-hand expression to the left-hand signal, all the time. When an input on the right changes, the left follows at once. It is not a statement that runs once, like an assignment in software; it is a wire laid down in the circuit for good. In the expression, ^ is XOR and & is AND.

3.1 From one AND gate to a half adder

The and_gate of section 1 is a single assign. A half adder needs only one more. Adding two 1-bit numbers gives 0, 1 or 2, and 2 needs two bits: carry, worth 2, and sum, worth 1. Copy the table and fill in the last two columns before you read on.

aba + bcarrysum
000??
011??
101??
112??

Now read each column you wrote as the truth table of a gate.

Check

Which two-input gate has exactly the truth table of your carry column?

Your answer

Check

Which two-input gate has exactly the truth table of your sum column?

Your answer

So the half adder is the and_gate with its output renamed carry, plus one XOR line for sum.

HalfAdder.v is that half adder, written in Verilog: two 1-bit inputs and two 1-bit outputs. sum is the XOR of the inputs and carry is their AND. Four ports and two assign lines are the whole design. Keep the declaration order in mind: a, b, sum, carry.

HalfAdder.v
// Sum and carry of two bits, with no stored state.
module half_adder (
  input  a,
  input  b,
  output sum,
  output carry
);
  assign sum   = a ^ b;
  assign carry = a & b;
endmodule

4. The testbench drives the inputs and reads the outputs

A design does nothing on its own. The second file, Tb.v, is the testbench: a module without ports that feeds inputs into a design and looks at its outputs. It makes an instance of the half adder named dut, connects its own signals by port name (.a(a)), then applies the four combinations to a and b and prints sum and carry once for each. To print values, or to make things happen in a particular order during simulation, a testbench uses a procedural block.

4.1 The initial block and $display

An initial block starts at the beginning of simulation, time 0, and executes the statements inside it once, in order.

Form

initial begin
  statement;
  statement;
end
module hello;
  initial begin
    $display("Hello, Verilog");
    $finish;
  end
endmodule
ConstructRole
initial begin ... endStarts at time 0 and runs the enclosed statements once, in order
$display(...)Prints text and values as one line, much like C's printf
$finish;Ends the simulation

Names that start with $ identify system tasks or system functions. Tasks such as $display perform an action; functions such as $clog2 return a value. Many of these services have no hardware meaning and synthesis tools ignore or reject them, though a few ($clog2, $readmemh) are accepted by synthesis. The always block, which repeats instead of running once, is covered in Testbench techniques; its clocked form is the subject of Registers and nonblocking assignment.

Every example runs on Icarus Verilog. iverilog compiles the source files into a file called sim.vvp, and vvp simulates that file. Pressing Run performs both steps in order.

4.2 Reading Tb.v

You do not need every line of Tb.v to use it. Three lines matter in this chapter:

Line in Tb.vWhat it does for you
half_adder dut (.a(a), .b(b), .carry(carry), .sum(sum));Places the half adder and ties each port to the testbench signal of the same name
{a, b} = i;Sets the inputs to the next row of your table: 00, 01, 10, 11. The low two bits of i land in a and b, left to right
#1 $display("HA a=%b b=%b -> carry=%b sum=%b", ...);Waits one time unit so the assign outputs have followed the new inputs, then prints one row

The rest, the reg and wire declarations, the integer i loop and the #1 delay, is the machinery that drives a design through time. You can use it without writing it yet; Testbench techniques explains each piece. One more detail: the connection list names .carry before .sum, the opposite of the declaration order.

Tb.v
module tb;
  reg  a, b;
  wire sum, carry;
  integer i;

  // Named connections: .port(signal), so order in the list does not matter.
  half_adder dut (.a(a), .b(b), .carry(carry), .sum(sum));

  initial begin
    for (i = 0; i < 4; i = i + 1) begin
      // Unpack the loop count into the two input bits.
      {a, b} = i;
      #1 $display("HA a=%b b=%b -> carry=%b sum=%b", a, b, carry, sum);
    end
    $finish;
  end
endmodule

Where to look. Each printed line is one row of your table. a= and b= are the inputs the testbench chose, so they only tell you which row you are on. The two columns to check are carry= and sum=: compare them with the last two columns you filled in for From one AND gate to a half adder.

Predict before you run. There will be four lines. For a, b equal to 0 0, 0 1, 1 0 and 1 1, write down carry and sum from the half-adder truth table in the fundamentals.

  1. How many of the four lines show carry=1, and what is sum on that line?
  2. Does listing .carry first change any printed column?

Check

On the line where a = 1 and b = 1, what are carry and sum?

Your answer

Press Run: the lab opens, compiles the two files and simulates them. Compare the four printed lines with your prediction one by one. They come in the same order as the rows of the truth table.

Check the result.

Output lineWhat it shows
a=0 b=0Both outputs are 0
a=0 b=1, a=1 b=0Only sum is 1: with exactly one input high, the XOR is 1
a=1 b=1carry=1, sum=0, which is 1 + 1 = 2 written as the two bits 10

The four lines are the half-adder truth table, and the order of .carry and .sum made no difference, because named connections match by name.

Caution. Output appearing means the simulation ran, not that the design is correct. Compare the values with the truth table to check its behavior. Whether the circuit can be synthesized into a chip is a separate question; a combinational circuit written with assign alone is the typical shape synthesis tools accept. The testbench's $display and #1 exist only in simulation.

5. Change one wire

In the lab's editor, change assign sum = a ^ b; in HalfAdder.v to assign sum = a | b; and run again. Before you do, write down which line will change.

Check

If sum becomes a | b, on which of the four lines does sum change?

Your answer

Now the other output. Restore ^ in sum, change assign carry = a & b; to assign carry = a | b;, and predict again before you run.

Check

With carry = a | b and sum = a ^ b, which printed lines show carry=1?

Your answer

The edited code stays in the lab. To return to the original, change | back to &, or press the page's Run to load the example afresh.

A second change touches the testbench instead of the design. Replace the connection list with the positional form half_adder dut (a, b, carry, sum);. Positional connections follow the definition's order a, b, sum, carry, so the testbench's carry net is tied to the child's sum and its sum net to the child's carry. It still compiles, but the two columns trade places: the middle two lines now show carry=1, and the last line shows sum=1. The rules for positional connections, and why this course connects by name, are in Instances, hierarchy and parameters.

6. Summary

ElementMeaning
module … endmodulethe design unit: name, ports, behavior. Its body is a set of connections, not a program that runs in order
direction [kind] [width] namea port declaration. Without a kind the port is a wire; without a width it is one bit
input, output, inoutport direction
assign y = expr;a wire that keeps y equal to the expression
initial begin … end, $display, $finisha procedural block that runs once at time 0; system tasks for simulation
instance name dut (.port(signal));a module placed inside another, connected by port name

7. Check your understanding

  1. The half-adder instance was switched to the positional list (a, b, carry, sum). Why did it still compile?

    Show answer

    A positional list just connects signals in the definition's order a, b, sum, carry; it never compares names. carry and sum are both 1-bit nets, so swapping them still fits, and nothing gives the compiler a reason to complain.

  2. assign y = a & b; appears once in a module. How many times does the simulator evaluate it?

    Show answer

    As often as a or b changes. A continuous assignment is a connection, not a statement that runs once: y is recomputed whenever an input of the expression changes, for as long as the simulation runs.

This chapter has no Practice problem; its checkpoint is the run above. The other port declaration style and positional connection are not needed until you place one module inside another.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows