2.5 Modules and signals in SystemVerilog
Why this matters
Every circuit in this book is first written down as text, because text can be checked by a program and a drawing cannot. SystemVerilog looks like a programming language, but a module is a description of wires and gates that all exist at once, and reading it as a list of steps is the most common way beginners go wrong. This section gives you the small vocabulary for the rest of the chapter: modules, signals, assignments and operators.
Learning goals
Section titled “Learning goals”- Write a combinational module with ports,
logicsignals andassignstatements for a circuit given as a schematic or an expression. - Rewrite one circuit in three styles: a single expression, named intermediate signals, and gate primitives.
- Predict how an expression with mixed
&,^and|is grouped, and add parentheses that say so. - Explain why the order of assignments does not matter and why a signal may have only one driver.
- Read a tool's message about an illegal multiple assignment or a
wiredeclaration.
Intuition first
Section titled “Intuition first”Section 1.1 compared a circuit to a spreadsheet: a formula in a cell is a relationship that stays true, not a step that runs once. A SystemVerilog assign is that kind of statement. assign F = ~((A & B) | (C & D)); does not compute F once. It says that F is, at every moment, the NOT of the OR of two ANDs, in the same way that a gate with its inputs wired up is always producing its output. A module is a collection of such statements and gates, and they are all active together, so it does not matter in which order you write them.
The picture breaks in one place that Section 1.1 did not cover. A formula that depends on its own result is an error in a spreadsheet; in hardware it is a loop. Chapter 4 shows that a loop of gates alone cannot count, and that the loops the book builds all pass through a register.
Modules, signals and assignments
Section titled “Modules, signals and assignments”The module of Definition 1.4.3 has a port list, and this section adds that a port can be wider than one bit. Each port is an input or an output and carries one bit or, with a range such as [3:0], several. Everything between module and endmodule describes what is inside the block, and the rest of the design sees only the ports.
A continuous assignment, written assign y = expression;, connects the signal y to the value of the expression for as long as the circuit exists. Whenever an input of the expression changes, y follows. The signal on the left is said to be driven by the statement.
Signals that carry bits inside a module are declared before they are used. The language has two kinds of signal, and tool messages use both words. A net models a wire: its value is whatever is driving it, and the language allows a net several drivers, whose values it then combines. A variable holds the value that was written to it. When that value comes from an assign, a gate or an instance output, the language allows the variable only that one driver. In the combinational circuits of this chapter a variable still carries only what its driver produces now, as Section 1.1 said, so the word does not mean a stored value; the memory elements of Chapter 4 are variables too, and they do keep their value. When signals are declared with logic, as in this book, the inputs of a module are nets, and outputs and internal signals are variables. You will meet both words in tool messages, for example when a second driver is added to a variable or when code tries to assign an input.
How CSE 369 writes this
In CSE 369 we declare every signal with the type logic, as an input or output port or as an internal signal, and never write wire or reg. The same word logic serves for nets and for variables; which one a signal is follows from where it is declared: an input is a net, and an output or an internal signal is a variable.
In SystemVerilog
Section titled “In SystemVerilog”The gate operators
Section titled “The gate operators”Section 1.2 gave each of the seven gates its operator: ~, &, | and ^, with NAND, NOR and XNOR written as a NOT around the operator they negate. The rest of this section uses them in larger circuits, starting with one that has four inputs.
The AOI gate
Section titled “The AOI gate”The AND-OR-INVERT gate, AOI for short, makes a good first module. It has four inputs and one output, and F is 0 exactly when A and B are both 1, or C and D are both 1. As a drawing it is two AND gates feeding a NOR.
A logic schematic with inputs A, B, C and D and output F.
AND gate g1: inputs A and B; output n1.
AND gate g2: inputs C and D; output n2.
NOR gate g3: inputs n1 and n2; output F.
It computes F = not (A and B or C and D).
Description: Two AND gates, one with inputs A and B and one with inputs C and D, feed a NOR gate whose output is F.
Section 1.4 showed three ways to write one circuit; here is the same idea on the AOI gate. The first version writes the whole function as one expression. The second names every wire of the schematic: the output of the first AND is ab, the output of the second is cd, and either is the output of the OR, before the final inversion. The third uses one gate primitive for each gate.
A logic schematic with inputs A, B, C and D and output F.
AND gate g1: inputs A and B; output ab.
AND gate g2: inputs C and D; output cd.
OR gate g3: inputs ab and cd; output either.
NOT gate g4: input either; output F.
It is checked against the module hdl:ch02/aoi_signals.
Description: Two AND gates feed an OR gate, and the OR gate feeds a NOT gate whose output is F. The wire from the first AND is labeled ab, the wire from the second AND is labeled cd, and the wire between the OR and the NOT is labeled either.
// AND-OR-INVERT, written as one expression: F is 0 when A and B are both 1, or when C and D are both 1.module aoi_ops(input logic A, B, C, D, output logic F); assign F = ~((A & B) | (C & D));endmodule// The same circuit with a named signal for every wire of the schematic.module aoi_signals(input logic A, B, C, D, output logic F); logic ab, cd, either; // one name per internal wire of the schematic
assign ab = A & B; assign cd = C & D; assign either = ab | cd; assign F = ~either;endmodule// The same circuit again, one gate primitive per gate: the output is the first port.module aoi_gates(input logic A, B, C, D, output logic F); logic ab, cd, either;
and g1(ab, A, B); and g2(cd, C, D); or g3(either, ab, cd); not g4(F, either);endmoduleCompare the second listing with Figure 2.5.2 line by line. The declaration logic ab, cd, either; creates the three internal wires. Each assign is one gate: the AND gates, the OR gate and the NOT gate. The third version says the same thing with the primitives and, or and not. Section 1.4 introduced the primitives: and g1(ab, A, B) drives ab from A and B, because the output comes first. The language makes the instance name, here g1, optional, and this book always gives one.
All three modules are checked against one specification of AOI, on every one of the 16 input rows, so they are the same function. They differ only in how much of the structure you can see.
| A | B | C | D | F |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 |
| 0 | 0 | 0 | 1 | 1 |
| 0 | 0 | 1 | 0 | 1 |
| 0 | 0 | 1 | 1 | 0 |
| 0 | 1 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 | 1 |
| 0 | 1 | 1 | 0 | 1 |
| 0 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 0 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 0 | 1 | 0 | 1 |
| 1 | 0 | 1 | 1 | 0 |
| 1 | 1 | 0 | 0 | 0 |
| 1 | 1 | 0 | 1 | 0 |
| 1 | 1 | 1 | 0 | 0 |
| 1 | 1 | 1 | 1 | 0 |
Nothing happens in order
Section titled “Nothing happens in order”Because every assign is always active, the circuit has no first line. The next module has the four statements of the second version in the opposite order, with the final inversion first.
In the module below, assign F = ~either; comes before either has been assigned anything. What does the simulation do?
// The statements of aoi_signals in the opposite order. Each assign is always active, so the order on the page changes nothing.module aoi_reordered(input logic A, B, C, D, output logic F); logic ab, cd, either;
assign F = ~either; assign either = ab | cd; assign cd = C & D; assign ab = A & B;endmoduleReading the statements top to bottom is still a good habit for a person, because it follows the signal from the inputs to the output, but it is a habit of the reader and not a rule of the language.
You can watch the same behavior in simulation. The testbench below changes one input at a time and the waveform shows the internal wires following. A testbench is a module that drives the inputs of the module under test (called dut) and is never part of the circuit; Chapter 3 treats testbenches properly.
// Testbench for aoi_signals: change one input at a time and watch the wires inside the module follow.module aoi_signals_tb(); logic A, B, C, D, F;
aoi_signals dut(.*);
initial begin A = 0; B = 0; C = 0; D = 0; #10; // expect: F=1 A = 1; #10; // expect: F=1 B = 1; #10; // expect: F=0 C = 1; #10; // expect: F=0 D = 1; #10; // expect: F=0 B = 0; #10; // expect: F=0 C = 0; #10; // expect: F=1 D = 0; #10; // expect: F=1 A = 0; #10; // expect: F=1 $stop; endendmoduleTime in ps; one column is 10 ps.
Timing diagram from simulation, ch02/aoi-trace: time in ps from 0 to 100, 10 columns of 10 ps.
A: 0 at 0, then 1 at 10, 0 at 80.
B: 0 at 0, then 1 at 20, 0 at 50.
C: 0 at 0, then 1 at 30, 0 at 60.
D: 0 at 0, then 1 at 40, 0 at 70.
dut.ab: 0 at 0, then 1 at 20, 0 at 50.
dut.cd: 0 at 0, then 1 at 40, 0 at 60.
dut.either: 0 at 0, then 1 at 20, 0 at 60.
F: 1 at 0, then 0 at 20, 1 at 60.
Description: A timing diagram of the inputs A, B, C and D, the internal signals ab, cd and either, and the output F, over ten steps. F is high at the start, falls when A and B are both high, rises again later when the inputs change back, and the internal signals ab, cd and either change at the same step as the input that causes them.
Read Figure 2.5.3 one column at a time. In the first column every input is 0, so ab, cd and either are 0 and F is 1. When A rises nothing else moves, because ab needs B as well. When B rises, ab rises in the same column, either rises with it and F falls. Each internal signal changes in the same column as the input that causes it, because the simulator treats a gate as having no delay; a real gate takes a short time, and Chapter 3 shows what that does.
Operator precedence
Section titled “Operator precedence”An expression with several operators needs a rule for which one goes first. In SystemVerilog the order, from first to last, is ~, then &, then ^, then |. So a | b & c means a | (b & c), as multiplication goes before addition in arithmetic, and a ^ b & c means a ^ (b & c). Many readers expect left-to-right reading instead, which gives a different function.
// No parentheses: & binds tighter than ^, and ^ binds tighter than |.module precedence_bare(input logic a, b, c, output logic x_bare, o_bare); assign x_bare = a ^ b & c; assign o_bare = a | b & c;endmoduleThe module is correct, but a linter does not like it. This is what the compiler front end used by the book's tools says about the XOR line when the module is written without the waiver the listing above carries:
// The bare form again, without the module-level waiver the shown version carries, to show what the linter says about it.module precedence_warn(input logic a, b, c, output logic x_bare); assign x_bare = a ^ b & c;endmodulehdl/ch02/s05/precedence_warn.sv:3:25: warning: '&' within '^' [-Wbitwise-op-parentheses]
assign x_bare = a ^ b & c;
~ ~~^~~
hdl/ch02/s05/precedence_warn.sv:3:25: note: place parentheses around the '&' expression to silence this warning
assign x_bare = a ^ b & c;
~~^~~The message asks for parentheses, and the book's code always gives them when different operators are mixed. Writing the grouping out changes nothing about the function, and the next module has exactly the same two outputs as the bare one.
// The same two functions with the grouping written out: this is what the bare form means.module precedence_paren(input logic a, b, c, output logic x_bare, o_bare); assign x_bare = a ^ (b & c); assign o_bare = a | (b & c);endmoduleThe grouping a left-to-right reader would guess is a different function, which is what makes the bare form dangerous:
// The grouping a reader often expects from left-to-right reading: a different function.module precedence_left(input logic a, b, c, output logic x_left, o_left); assign x_left = (a ^ b) & c; assign o_left = (a | b) & c;endmodule| a | b | c | ||||
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 1 | 1 | 1 | 1 |
| 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| 1 | 0 | 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 0 | 1 | 0 | 1 | 0 |
| 1 | 1 | 1 | 0 | 0 | 1 | 1 |
Description: A truth table with inputs a, b and c and four output columns, x_bare, x_left, o_bare and o_left, with eight rows.
One driver per signal
Section titled “One driver per signal”Because a statement is a permanent connection, two statements that connect the same signal are in conflict: the wire would be pulled to two values at once. The tools reject it.
// A lamp that two assign statements both try to drive.module drive_twice(input logic a, b, c, output logic y); assign y = a & b; assign y = c;endmodulehdl/ch02/s05/drive_twice.sv:4:10: error: cannot have multiple continuous assignments to variable 'y' [-Wmultiple-cont-assigns]
assign y = c;
^
hdl/ch02/s05/drive_twice.sv:3:10: note: also assigned here
assign y = a & b;
^The error names the signal, y, and the rule it breaks, and the note under it points at the other driver. The message calls y a variable, the kind of signal that allows exactly one driver. The fix is to decide what y really is and write one statement for it; combine the two conditions with an OR, as the exercises below do.
Why logic and not wire or reg
Section titled “Why logic and not wire or reg”Older Verilog needs wire for some signals and reg for others, and the word reg has nothing to do with the registers of a processor. SystemVerilog's logic replaces both, so the book uses it for everything. Compilers accept wire and reg too, so only the book's own checks reject them:
// Declares a net with wire and a variable with reg. Compilers accept both; the book's rule is logic for every signal.module uses_wire(input logic a, b, output logic y); wire both; reg inverted;
assign both = a & b; always_comb inverted = ~both; assign y = inverted;endmodulehdl/ch02/s05/uses_wire.sv:3:3: error: [no-wire] use logic, never wire or other net declarations
hdl/ch02/s05/uses_wire.sv:4:3: error: [no-reg] use logic, never regCourse conventions
Section titled “Course conventions”How CSE 369 writes this
The conventions of Section 1.4 still hold: in this book's code, gate primitives are lowercase with the output first, and port lists have no trailing comma before the closing parenthesis. The time axis of a simulated waveform counts the simulator's own time units, and the book labels it ps; those numbers are arbitrary, and a label of ps does not mean the circuit is that fast. Waveforms later in the book do not repeat this.
Pitfalls
Section titled “Pitfalls”Common pitfall
- Repeating a first-module mistake from Section 1.4: a gate primitive with its output last or written in capitals, or a comma after the last port.
- Reading
a ^ b & cora | b & cfrom left to right. The&binds first. Put parentheses around the part you mean. - Assigning one signal in two statements. Each signal has exactly one driver, and the tools report the second statement as an error.
- Reading the order of
assignstatements as the order of events. All of them hold at all times, so reordering them changes nothing. - Declaring signals with
wireorreg. Uselogicfor every port and every internal signal.
Check your understanding
Section titled “Check your understanding”Take the quiz (5 questions)
The AOI output F is 0 when A and B are both 1 or when C and D are both 1. Which expression is the same function?
Which is the grouping SystemVerilog gives to a | b & c?
& binds tighter than |, so only b and c are ANDed first. The second choice is the left-to-right reading, which is a different function, and the third reverses the roles of the two operators.A module has the statements assign F = ~either; and assign either = ab | cd;. Does moving assign either = ab | cd; above the other change what the circuit does?
In or g3(either, ab, cd);, which signal does the gate drive?
either is the OR of ab and cd.Which declaration does the book use for an internal one-bit signal?
logic for every signal. The tools accept the other two, so the book's own rule is what rejects them.Exercises
Section titled “Exercises”The module aoi_signals is simulated with the inputs shown. Fill in ab, cd and F for every column.
Time in ps; one column is 10 ps.
Timing diagram from simulation, ch02/aoi-trace-ex: time in ps from 0 to 100, 10 columns of 10 ps.
A: 1 at 0, then 0 at 40, 1 at 70, 0 at 90.
B: 1 at 0, then 0 at 10, 1 at 60.
C: 0 at 0, then 1 at 20, 0 at 50.
D: 0 at 0, then 1 at 30, 0 at 80.
Empty lanes for the reader to complete: dut.ab, dut.cd, F.
Description: A timing diagram of the inputs A, B, C and D over ten steps, with the lanes for ab, cd and F left empty.
Hint
ab and cd first. F is 1 exactly when both of them are 0.Solution
In each column ab is 1 only if A and B are both 1, cd is 1 only if C and D are both 1, and F is 1 only when both are 0.
- First column: A and B are 1, so
abis 1 and F is 0. - Second column: B falls, so
abis 0,cdis 0 and F is 1. - Third column: C rises, but D is still 0, so nothing changes.
- Fourth column: D rises, so
cdbecomes 1 and F falls to 0. - Fifth column: A falls, so
abstays 0;cdis still 1, so F stays 0. - Sixth column: C falls, so
cdis 0 and F rises to 1. - Seventh column: B rises, but A is 0, so
abstays 0 and F stays 1. - Eighth column: A rises, so
abis 1 and F falls to 0. - Ninth column: D falls.
cdwas already 0 because C is 0, and A and B are both 1, soabstays 1 and F stays 0. - Tenth column: A falls, so
abis 0 and F rises to 1.
Time in ps; one column is 10 ps.
Timing diagram from simulation, ch02/aoi-trace-ex: time in ps from 0 to 100, 10 columns of 10 ps.
A: 1 at 0, then 0 at 40, 1 at 70, 0 at 90.
B: 1 at 0, then 0 at 10, 1 at 60.
C: 0 at 0, then 1 at 20, 0 at 50.
D: 0 at 0, then 1 at 30, 0 at 80.
dut.ab: 1 at 0, then 0 at 10, 1 at 70, 0 at 90.
dut.cd: 0 at 0, then 1 at 30, 0 at 50.
F: 0 at 0, then 1 at 10, 0 at 30, 1 at 50, 0 at 70, 1 at 90.
Description: The full timing diagram of A, B, C, D, ab, cd and F over ten steps.
Rewrite a ^ b & c with parentheses so that its grouping is explicit. Then write the expression a left-to-right reader would guess, and say whether the two are the same function.
Hint
& and ^ goes first?Solution
The & goes first, so the explicit form is a ^ (b & c). The left-to-right guess is (a ^ b) & c. They are different functions: in Figure 2.5.4 the columns x_bare and x_left are different. For instance, when a is 1 and b and c are both 0, the first is 1 ^ 0, which is 1, and the second is (1 ^ 0) & 0, which is 0.
Write a module tri_ops for the circuit below, using operators only. Give the two internal signals the names shown on the drawing.
A logic schematic with inputs a, b and c and output z.
NAND gate g1: inputs a and b; output n1.
XOR gate g2: inputs b and c; output x1.
AND gate g3: inputs n1 and x1; output z.
It agrees with the function logic:ch02/s05-tri#z.
Description: A NAND gate takes a and b. An XOR gate takes b and c. An AND gate takes the output of the NAND, labeled n1, and the output of the XOR, labeled x1, and drives z. The input b is split between the NAND and the XOR.
Hint
assign for each gate. The NAND is a NOT around an AND.Solution
Declare n1 and x1, then write one statement per gate, working from the left of the drawing. The NAND is ~(a & b), the XOR is b ^ c, and the last gate ANDs the two named wires.
// Exercise solution, operator style: one assign per gate, with the schematic's net names.module tri_ops(input logic a, b, c, output logic z); logic n1, x1;
assign n1 = ~(a & b); assign x1 = b ^ c; assign z = n1 & x1;endmoduleThe module is compared with the function of the drawing on all eight input rows.
Write the module for the same circuit again, this time named tri_gates, with a gate primitive for every gate.
Hint
nand, xor and and, and the output is always the first port.Solution
The same two wires are declared, and each gate becomes one primitive with its own instance name. The first signal in each pair of parentheses is the wire that gate drives.
// Exercise solution, gate style: one primitive per gate, output first.module tri_gates(input logic a, b, c, output logic z); logic n1, x1;
nand g1(n1, a, b); xor g2(x1, b, c); and g3(z, n1, x1);endmoduleBoth versions pass the same check, so the choice between them is a matter of taste. The primitive form shows the gates of the drawing one for one; the operator form is shorter.
A lamp y should be on when a and b are both 1, or when c is 1. This module does not compile. Say why, and write a correct version.
// Exercise: the lamp should be on when a and b are both 1, or when c is 1.module lamp_twice(input logic a, b, c, output logic y); and g1(y, a, b); assign y = c;endmoduleHint
y? The condition for the lamp has two cases that must meet in one gate.Solution
Both the AND primitive and the assign drive y, and a signal may have only one driver. This is what the compiler front end reports:
hdl/ch02/s05/ex/lamp_twice.sv:4:10: error: cannot have multiple continuous assignments to variable 'y' [-Wmultiple-cont-assigns]
assign y = c;
^
hdl/ch02/s05/ex/lamp_twice.sv:3:10: note: also assigned here
and g1(y, a, b);
^The lamp needs one gate where the two conditions meet. Let the AND gate drive a separate wire, and let an OR gate combine that wire with c and drive y.
// Exercise solution: one driver for y, and the OR written where the two conditions meet.module lamp_fixed(input logic a, b, c, output logic y); logic both;
and g1(both, a, b); or g2(y, both, c);endmoduleWrite a module exactly_one with inputs a, b and c whose output y is 1 exactly when one of the three inputs is 1 (not none, two or three). Use only the operators you have met.
Hint
Solution
An odd number of 1s among three bits means one or three. The three-1s case is the only one that should be removed, and it is the case where a & b & c is 1. So y is the XOR of the three inputs ANDed with the NOT of that product.
// Exercise solution: the XOR of three bits is 1 when an odd number of them are 1, so remove the case where all three are.module exactly_one(input logic a, b, c, output logic y); logic odd, all_three;
assign odd = a ^ b ^ c; assign all_three = a & b & c; assign y = odd & ~all_three;endmoduleThe module is compared with the function that is 1 on the rows 001, 010 and 100, on all eight rows. A sum of three three-input products gives the same function; this version is shorter to write.