Skip to content

2.7 Hierarchy, always_comb and multiplexers

Why this matters

No real design is one module. A processor, a game controller and a lock are each built by connecting smaller modules, and half of the skill is deciding where the boundaries go. This section shows how modules connect, how to describe a choice between two or more values, and which constructs the tools reject. It ends with a first attempt at a small guessing game, which puts instances, signals and a comparator together.

  • Instantiate a module with named connections, with implicit connections, and with .*.
  • Describe a choice with if and case in always_comb, and recognize the multiplexer it makes.
  • Predict which assignment wins when a block assigns the same variable several times.
  • Explain why an if without an else makes a latch, and fix it.
  • Recognize three illegal arrangements of instances and drivers, and read the tool's message for each.

A module is like a function you write once and use many times. The difference is what "use" means. Calling a function runs its code at one moment; instantiating a module places a copy of its hardware in the design, and that copy exists and works for as long as the circuit does. Two instances of a module are two separate pieces of hardware, each with its own wires, working at the same time.

Think of a kitchen. A module is a recipe card for a small appliance, say a toaster. An instance is a toaster actually standing on the counter. Wiring up a design means placing appliances and running cords between their sockets, and the card itself never moves. The picture breaks where a function call would be convenient: you cannot place an appliance in the middle of a decision, because the appliances are already on the counter before anyone decides anything. That is why an instance can never appear inside a procedural block.

A multiplexer, or mux, is the circuit form of a decision. A mux has several data inputs and a select input, and it passes along one of the data inputs, the one the select names. All the data inputs are always there, each with its own value, and the select only decides which one is let through. An if in a circuit has the same shape: both branches exist as hardware, and a mux chooses between them.

Definition 2.7.1(instance).

An instance is one copy of a module placed inside another. It is written as the module's name, then a name for this copy, then a list of connections that join each of the module's ports to a signal of the surrounding module: majority_three u1(.A(x), .B(y), .C(z), .M(out));. The surrounding module is the parent. Instances appear at the level of the module body, next to assign statements, and never inside a procedural block.

There are three ways to write the connections. Naming each port, .A(x), connects port A to the signal x; the order of the list does not matter. An implicit connection, .A, is short for .A(A), and works when the signal has the same name as the port. The wildcard .* connects every port that is not listed to the signal of the same name in the parent. Each connection should join a port to a signal of the same width. With a named connection, .A(x), a signal of a different width is still legal and the tools warn about it (the compiler front end and Verilator both do). With .A or .* the language asks for matching types, so a width mismatch is an error.

connect_forms.sv
// Three ways to connect the same instance: by name, by implicit name, and with .* for every port that matches.
module connect_forms(input logic A, B, C, output logic m_named, m_implicit, m_star);
majority_three u_named(.A(A), .B(B), .C(C), .M(m_named));
majority_three u_implicit(.A, .B, .C, .M(m_implicit));
majority_three u_star(.*, .M(m_star));
endmodule

The three instances are the same hardware. A separate test applies all eight rows of inputs and compares each output with the majority of the three inputs.

Definition 2.7.2(multiplexer).

A multiplexer, or mux, passes one of its data inputs to its output. A 2:1 mux has two data inputs and one select input; the output is the data input numbered by the select. A mux with 2k2^k data inputs has a kk-bit select. In a diagram it is a trapezoid with the data input numbers written inside and the select entering the slanted side.

Example 2.7.1(a 2:1 multiplexer from an AOI cell).

A 2:1 mux needs y = d1 when the select s is 1 and y = d0 when it is 0. Two AND terms do that: d1 with s, and d0 with the inverse of s. An AOI cell computes the NOR of two such terms, which is the inverse of what we want, so an inverter after the cell fixes it. The module below instantiates the AOI cell and adds two inverters.

mux2_aoi.sv
// A 2:1 multiplexer built from an AOI cell and two inverters: y is d1 when s is 1, and d0 when s is 0.
module mux2_aoi(input logic s, d0, d1, output logic y);
logic s_n, y_n;
not g1(s_n, s);
aoi_cell core(.A(d1), .B(s), .C(d0), .D(s_n), .F(y_n)); // y_n is 0 when d1 and s are 1, or d0 and s_n are 1
not g2(y, y_n);
endmodule

The cell is a module of its own, listed with the file:

aoi_cell.sv
// An AND-OR-INVERT cell, used below as a building block.
module aoi_cell(input logic A, B, C, D, output logic F);
assign F = ~((A & B) | (C & D));
endmodule

Following the signals: s_n is the inverse of s, the cell's output y_n is 0 when either d1 and s are both 1 or d0 and s_n are both 1, and the last inverter turns that into y. The module is compared with the specification of a 2:1 mux on all eight input rows.

Truth table with 3 inputs (s, d0, d1) and output y; 8 rows in ascending binary order, s most significant
sd0d_{0}d1d_{1}y
0000
0010
0101
0111
1000
1011
1100
1111

A circuit built from gates is correct but long. Most of the time you describe the choice itself and let the tool build the mux. Three more modules describe the same 2:1 mux: with if and else, with a case, and with the conditional operator.

mux2_if.sv
// The same multiplexer written with if and else.
module mux2_if(input logic s, d0, d1, output logic y);
always_comb begin
if (s) y = d1;
else y = d0;
end
endmodule
mux2_case.sv
// The same multiplexer written with a case statement and a default arm.
module mux2_case(input logic s, d0, d1, output logic y);
always_comb begin
case (s)
1'b0: y = d0;
default: y = d1;
endcase
end
endmodule
mux2_cond.sv
// The same multiplexer as a conditional expression.
module mux2_cond(input logic s, d0, d1, output logic y);
assign y = s ? d1 : d0;
endmodule

All four mux modules pass the same check, on every row.

Definition 2.7.3(always_comb).

An always_comb block is a group of statements that computes one or more variables from the signals it reads. It always works, like an assign: when an input of the block changes, its results follow. Inside it, assignments use =, and the statements may be if, else, case and plain assignments. A variable that a block assigns must be assigned in no other block and in no assign.

ReadingTwo ways to read the same code

Chapters 1 and 2 say that nothing runs: an assign or an always_comb describes hardware that is always connected and always working. Later pages, and the tools' own manuals, say that a block "runs". Both are right, about different things. The hardware reading is that the code describes gates and wires. The simulator reading is that a simulator computes that hardware's behavior on an ordinary computer, so it executes an always_comb block once at the start and again whenever a signal the block reads changes. When Chapter 4 says that an always_ff block runs at each rising clock edge, that is the simulator reading of the same kind of description. "Runs" always means the simulator carrying out the description, never a step taken by the circuit.

An always_comb block holds exactly one statement, and so does each arm of an if or case. The words begin and end turn several statements into that one statement. You need them whenever a body has more than one statement, and they are optional around a single statement, which is why some listings in this book have them around one case and others do not. The usual slip is adding a second line under an if and forgetting them: the new line is then not part of the if and runs whatever the condition says, and after an always_comb or always_ff header it is a syntax error.

In design code, if and case statements live inside a procedural block such as this one, and the block can describe more than a mux. The rule that matters is how several assignments to one variable combine. The statements are read from top to bottom, and when more than one of them applies, the last one wins.

last_wins.sv
// Several assignments to one variable in a block: when more than one if is true, the last assignment decides.
module last_wins(input logic a, b, output logic [1:0] level);
always_comb begin
level = 2'd0;
if (a) level = 2'd1;
if (b) level = 2'd2;
end
endmodule

The level is 0 unless a or b is 1, and when both are, the second if wins: 2'b10 in simulation. Here the order matters, because the order says which assignment is last. This is a rule about the statements inside one block, and it does not conflict with the idea that circuits have no order: the block is one piece of logic, and its text is the recipe for how the choices are ranked.

Every variable that a block assigns must receive a value on every path through it. If some path assigns nothing, the variable keeps its old value on that path, which means the circuit has to remember it. That memory is called a latch: a circuit that follows its input while an enable is on and holds its last value while the enable is off. Here the enable is the condition of the if. Designs in this book never use one, and a latch written by accident is a bug.

latch_demo.sv
// A lamp that should be on when the door is open and the key is out. The else case is missing.
module latch_demo(input logic door_open, key_out, output logic lamp);
always_comb begin
if (door_open & key_out) lamp = 1'b1;
end
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (inferred-latch). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
hdl/ch02/s07/latch_demo.sv:4:30: warning: latch inferred for 'lamp' because it is not assigned on all control paths [-Winferred-latch]
    if (door_open & key_out) lamp = 1'b1;
                             ^~~~
Output of slang for latch_demo

There is a cure with two forms. Either end the if with an else, or assign a default value on the first line and let later statements override it, as last_wins did above with level = 2'd0.

no_latch.sv
// The lamp with a value for every case: the first assignment is the default, and a later one overrides it.
module no_latch(input logic door_open, key_out, output logic lamp);
always_comb begin
lamp = 1'b0;
if (door_open & key_out) lamp = 1'b1;
end
endmodule

A case statement needs the same care: a value of the select that no item names leaves the variable unassigned, which is a latch for the same reason. End the case with a default arm, as the earlier case did, so that every value has an assignment even after someone edits the items. The default is also where you decide what the circuit does for the values you did not list. If the input can truly never occur and any output is acceptable, as for the unused codes of a decoder, assigning X says so (the decoder of Section 3.1 assigns 7'bX). If you must say what happens, as for the unused codes of a state machine, assign a named safe value; Section 5.3 says what that does and does not promise once a synthesis tool has handled the code.

A circuit built from instances: variable majority

Section titled “A circuit built from instances: variable majority”

The majority of three bits is 1 when at least two of them are 1. The case below lists the four patterns of {A, B, C} for which that holds, and the default arm covers the other four. Writing the three bits as one value with a concatenation is what lets a single case look at all of them.

majority_three.sv
// Majority of three: M is 1 when at least two of A, B and C are 1.
module majority_three(input logic A, B, C, output logic M);
always_comb begin
case ({A, B, C})
3'b011, 3'b101, 3'b110, 3'b111: M = 1'b1;
default: M = 1'b0;
endcase
end
endmodule

Now a circuit that takes the majority of up to three inputs, with a 2-bit count that says how many of them take part: 0 gives 0, 1 gives A, 2 gives the majority of A and B, and 3 gives the majority of A, B and C. The count-2 case is a majority module whose third input is the constant 0, and a majority of three with one input stuck at 0 needs the other two inputs to be 1.

Illustration a block diagram that explains an idea; its behavior is not checked

Schematic ch02/variable-majority-blockmajABCMmajABCM0000011011ABCcountM2

A block diagram with inputs A, B, C and count (2 bits) and output M.

Constant 0 drives n1.

Block "maj" maj2: A is A, B is B, C is n1, M is n2.

Block "maj" maj3: A is A, B is B, C is C, M is n3.

Constant 0 drives n4.

4-to-1 multiplexer sel: selects among n4 as input 0, A as input 1, n2 as input 2 and n3 as input 3 using count; output M.

This diagram is illustrative: its behavior is not checked.

Description: A block diagram. Inputs A and B each go to two maj blocks, one above the other. The upper maj block has its third input tied to 0, and the lower one takes C as its third input. A multiplexer with four inputs, numbered 00, 01, 10 and 11, has the constant 0 on input 00, A on input 01, the output of the upper maj block on input 10 and the output of the lower maj block on input 11. The 2-bit input count enters the select side of the multiplexer, and the multiplexer output is M.

Figure 2.7.1. variable_majority as a block diagram: two majority blocks feed a 4:1 multiplexer whose select is the 2-bit count. The inputs of the mux are numbered by the value of the count.

The drawing marks the structure: two instances of the same module feeding a mux that picks one of four values. The code reads the same way.

variable_majority.sv
// Vote among the first `count` of the inputs A, B and C: no inputs gives 0, one input gives A, two inputs give their majority (with C held at 0), three give the full majority.
module variable_majority(input logic A, B, C, input logic [1:0] count, output logic M);
logic vote_pair, vote_trio;
majority_three pair_vote(.A(A), .B(B), .C(1'b0), .M(vote_pair));
majority_three trio_vote(.A(A), .B(B), .C(C), .M(vote_trio));
always_comb begin
case (count)
2'd0: M = 1'b0;
2'd1: M = A;
2'd2: M = vote_pair;
default: M = vote_trio;
endcase
end
endmodule

The case on count is the mux. Its arms compare count with constants as wide as count: 2'd0, 2'd1, 2'd2, and the default arm covers 3. The two instances are named connections, and the third input of the first one is the literal 1'b0. A shorter way to write the same two instances uses an implicit connection and .*:

variable_majority_short.sv
// The same voter with abbreviated connections: `.A` stands for `.A(A)`, and `.*` fills in every port that has a same-named signal here.
module variable_majority_short(input logic A, B, C, input logic [1:0] count, output logic M);
logic vote_pair, vote_trio;
majority_three pair_vote(.A, .B, .C(1'b0), .M(vote_pair));
majority_three trio_vote(.*, .M(vote_trio));
always_comb begin
case (count)
2'd0: M = 1'b0;
2'd1: M = A;
2'd2: M = vote_pair;
default: M = vote_trio;
endcase
end
endmodule

Both modules were run through all 32 combinations of A, B, C and count and compared with a reference that counts the 1s among the inputs that take part instead of using majority_three. The testbench below shows a few of them:

variable_majority_tb.sv
// Tries several combinations of inputs and count on variable_majority.
module variable_majority_tb();
logic A, B, C, M;
logic [1:0] count;
variable_majority dut(.*);
initial begin
A = 1; B = 0; C = 1; count = 2'd0; #10; // expect: M=0
count = 2'd1; #10; // expect: M=1
count = 2'd2; #10; // expect: M=0
$display("DD m_101_count2=%0d'b%b", $bits(M), M);
count = 2'd3; #10; // expect: M=1
A = 1; B = 1; C = 0; count = 2'd2; #10; // expect: M=1
count = 2'd3; #10; // expect: M=1
$display("DD m_110_count3=%0d'b%b", $bits(M), M);
A = 0; B = 1; C = 0; count = 2'd1; #10; // expect: M=0
count = 2'd3; #10; // expect: M=0
A = 0; B = 1; C = 1; count = 2'd2; #10; // expect: M=0
count = 2'd3; #10; // expect: M=1
$stop;
end
endmodule

For example, with A, B and C equal to 1, 0 and 1 and a count of 2, only A and B take part, one of them is 1, and the majority is 1'b0. With the count at 3 and the inputs 1, 1 and 0 it is 1'b1.

Compare count with a constant of its own width. The compiler front end says nothing about a 3-bit constant against a 2-bit signal, but Verilator warns about each comparison, and the book's checks count any warning as a failure:

count_wide_bad.sv
// A 2-bit count compared with 3-bit constants.
module count_wide_bad(input logic [1:0] count, input logic A, B, output logic M);
always_comb begin
if (count == 3'd1) M = A;
else if (count == 3'd2) M = B;
else M = 1'b0;
end
endmodule
Compiles, but is rejected. This file compiles and Verilator only prints a warning (WIDTHEXPAND). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
%Warning-WIDTHEXPAND: hdl/ch02/s07/count_wide_bad.sv:5:20: Operator EQ expects 3 bits on the LHS, but LHS's VARREF 'count' generates 2 bits.
                                                         : ... note: In instance 'count_wide_bad'
    5 |     else if (count == 3'd2) M = B;
      |                    ^~
                      ... For warning description see https://verilator.org/warn/WIDTHEXPAND?v=5.055
                      ... Use "/* verilator lint_off WIDTHEXPAND */" and lint_on around source to disable this message.
%Warning-WIDTHEXPAND: hdl/ch02/s07/count_wide_bad.sv:4:15: Operator EQ expects 3 bits on the LHS, but LHS's VARREF 'count' generates 2 bits.
                                                         : ... note: In instance 'count_wide_bad'
    4 |     if (count == 3'd1) M = A;
      |               ^~
Output of Verilator for count_wide_bad

A comma after the last port is also an error:

trailing_comma_bad.sv
// A comma after the last port.
module trailing_comma_bad(input logic A, B, output logic M,);
assign M = A & B;
endmodule
Does not compile. Slang reports an error (misplaced-trailing-separator) and stops; the output is shown below the listing.
hdl/ch02/s07/trailing_comma_bad.sv:2:59: error: misplaced trailing ',' [-Wmisplaced-trailing-separator]
module trailing_comma_bad(input logic A, B, output logic M,);
                                                          ^
Output of slang for trailing_comma_bad

Three arrangements are wrong in hardware and rejected by the tools. The first puts an instance inside an always_comb block. The instance is a piece of hardware, not a step, so it cannot depend on a condition.

bad_in_always.sv
// A module instance placed inside an always_comb block, which is not allowed.
module bad_in_always(input logic A, B, C, input logic [1:0] count, output logic M);
logic vote_trio;
always_comb begin
if (count == 2'd3) begin
majority_three trio_vote(.A(A), .B(B), .C(C), .M(vote_trio));
M = vote_trio;
end else begin
M = A & B & C;
end
end
endmodule
Does not compile. Slang reports an error and stops; the output is shown below the listing.
hdl/ch02/s07/bad_in_always.sv:3:9: warning: variable 'vote_trio' is never assigned a value [-Wunassigned-variable]
  logic vote_trio;
        ^~~~~~~~~
hdl/ch02/s07/bad_in_always.sv:7:7: error: 'majority_three' is a module definition but is used as a value
      majority_three trio_vote(.A(A), .B(B), .C(C), .M(vote_trio));
      ^~~~~~~~~~~~~~
hdl/ch02/s07/majority_three.sv:2:8: warning: module definition is unused [-Wunused-def]
module majority_three(input logic A, B, C, output logic M);
       ^~~~~~~~~~~~~~
Output of slang for bad_in_always

The error is the line saying that majority_three is a module definition used as a value. The two warnings around it follow from it: with no valid instance, nothing drives vote_trio, and nothing uses the module majority_three.

The second assigns an input of the module. The message calls the input a net, as in Section 2.5: an input is driven by the parent module, and a procedural block may not assign it.

bad_assign_input.sv
// An input port assigned inside an always_comb block, which is not allowed.
module bad_assign_input(input logic A, B, C, input logic [1:0] count, output logic M);
logic vote_pair;
majority_three pair_vote(.A(A), .B(B), .C(C), .M(vote_pair));
always_comb begin
if (count == 2'd2) begin
C = 1'b0;
M = vote_pair;
end else begin
M = A;
end
end
endmodule
Does not compile. Slang reports an error and stops; the output is shown below the listing.
hdl/ch02/s07/bad_assign_input.sv:9:7: error: cannot assign to a net within a procedural context
      C = 1'b0;
      ^
Output of slang for bad_assign_input

The third drives one output from two places: here the output of an instance and an always_comb block.

bad_two_drivers.sv
// An output that an instance and an always_comb block both try to drive.
module bad_two_drivers(input logic A, B, C, input logic [1:0] count, output logic M);
majority_three trio_vote(.A(A), .B(B), .C(C), .M(M));
always_comb begin
if (count == 2'd1) M = A;
else if (count == 2'd2) M = B;
else M = 1'b0;
end
endmodule
Does not compile. Slang reports an error (mixed-var-assigns) and stops; the output is shown below the listing.
hdl/ch02/s07/bad_two_drivers.sv:6:24: error: cannot mix continuous and procedural assignments to variable 'M' [-Wmixed-var-assigns]
    if (count == 2'd1) M = A;
                       ^
hdl/ch02/s07/bad_two_drivers.sv:3:52: note: also assigned here
  majority_three trio_vote(.A(A), .B(B), .C(C), .M(M));
                                                   ^
Output of slang for bad_two_drivers

The message names the variable and points at the other driver. Each signal has one driver, as in Section 2.5; whichever way the second driver sneaks in, the rule is the same.

To finish, a small game. A secret number is fixed inside the circuit. The player sets three switches as a guess and a fourth switch to say "check my guess". When the check switch is on, one of three lights shows whether the guess is greater than, equal to or less than the secret, with all the numbers read as signed values, as in Section 2.6.

The game needs a comparator. The first attempt uses subtraction: compute the 3-bit difference a - b, then read the answer from it. If the difference is all zeros, the numbers are equal. If its leftmost bit is 1, the difference looks negative, so a is less than b. Otherwise a is greater.

compare3.sv
// A first attempt at a comparator for 3-bit signed numbers, built on subtraction.
module compare3(input logic [2:0] a, b, output logic lt, eq, gt);
logic [2:0] diff;
assign diff = a - b;
assign eq = ~|diff; // every bit of the difference is 0
assign lt = diff[2]; // the difference looks negative
assign gt = ~eq & ~lt;
endmodule

The game instantiates the comparator with the guess on one port and the constant secret on the other. Order matters here, because subtraction is not commutative: a is the guess and b is the secret.

Illustration a block diagram that explains an idea; its behavior is not checked

Schematic ch02/guessing-game-block2compare3abgteqltguesscheckled_gtled_eqled_lt33

A block diagram with inputs guess (3 bits) and check and outputs led_gt, led_eq and led_lt.

Constant 2 drives n4.

Block "compare3" cmp: a is guess, b is n4, gt is n1, eq is n2, lt is n3.

AND gate g_gt: inputs n1 and check; output led_gt.

AND gate g_eq: inputs n2 and check; output led_eq.

AND gate g_lt: inputs n3 and check; output led_lt.

This diagram is illustrative: its behavior is not checked.

Description: A block diagram. The 3-bit input guess goes to the a input of a compare3 block, and the constant 2 goes to its b input. The block has three outputs, gt, eq and lt, from top to bottom. Each output goes to its own AND gate, and the check input goes to the other input of all three AND gates. The three AND gates drive the outputs labeled led_gt, led_eq and led_lt, the lights for greater, equal and less.

Figure 2.7.2. The guessing game as a block diagram: a comparator against a constant, and one AND gate per light with the check switch. The inputs guess and check are SW[2:0] and SW[3]; the outputs led_gt, led_eq and led_lt are LEDR[0], LEDR[1] and LEDR[2].
guessing_game.sv
// A guessing game with a fixed secret. SW[2:0] is the guess and SW[3] says "check my guess".
// LEDR[0] lights when the guess is greater than the secret, LEDR[1] when it is equal, LEDR[2] when it is less.
module guessing_game(input logic [3:0] SW, output logic [2:0] LEDR);
logic guess_lt, guess_eq, guess_gt;
compare3 judge(.a(SW[2:0]), .b(3'b010), .lt(guess_lt), .eq(guess_eq), .gt(guess_gt)); // the secret is 3'b010
assign LEDR[0] = guess_gt & SW[3];
assign LEDR[1] = guess_eq & SW[3];
assign LEDR[2] = guess_lt & SW[3];
endmodule

The first light, LEDR[0], shows greater, the second shows equal and the third shows less. The secret is 2. The testbench below plays six guesses.

guessing_game_tb.sv
// Plays six guesses against the secret 2, with the check switch on except in the fourth.
module guessing_game_tb();
logic [3:0] SW;
logic [2:0] LEDR;
guessing_game dut(.*);
initial begin
SW = 4'b1011; #10; // expect: LEDR=3'b001
SW = 4'b1010; #10; // expect: LEDR=3'b010
SW = 4'b1001; #10; // expect: LEDR=3'b100
SW = 4'b0011; #10; // expect: LEDR=3'b000
SW = 4'b1000; #10; // expect: LEDR=3'b100
SW = 4'b1111; #10; // expect: LEDR=3'b100
$stop;
end
endmodule

The first guess is 3, the pattern 011, and the greater light comes on. Guess 2 lights the equal light, and guess 1 lights the less light. In the fourth line the check switch is off, so no light comes on. The last two guesses are 0 and then the pattern 111, which is −1-1 when read as a signed number; both are less than 2, and both light the less light.

The rule is not right for every pair: some guesses the testbench does not try light the wrong lamp. Section 3.2 tries every guess and finds them.

This is a first attempt, and six friendly guesses do not make a tested comparator: they show that it works on those guesses and say nothing about the other pairs of numbers. Chapter 3 shows how to try every input in a testbench and read the result, and Chapter 4 returns to the arithmetic behind the comparator. Until then, take this section's version as an example of how modules connect, not as a finished design.

How CSE 369 writes this

In this book's code, instances use named connections, implicit connections or .*, and never appear inside an always_comb block. A multiplexer is drawn as a trapezoid with its data inputs numbered, and port lists have no trailing comma. Constants compared with a bus are sized to the bus.

Common pitfall

  • Writing an instance inside always_comb, an if or a case. Instances go at the top level of the module body, and an if chooses between values, not between pieces of hardware.
  • Assigning an input of the module, or driving a signal from both an instance and a procedural block. Each signal has one driver, and inputs are driven by the parent module.
  • Leaving a path of an always_comb block without an assignment. The missing else or default is a latch; give a default first or finish every if and case.
  • Comparing a 2-bit signal with a 3-bit constant. The comparison still comes out right here, but Verilator warns about it; write 2'd1.
  • Putting a comma after the last port of a module.
  • Trusting a design because a few inputs worked. Six friendly guesses do not test a comparator; when the number of inputs is small, try all of them.
Take the quiz (6 questions)

In majority_three u1(.*, .M(x));, which ports are connected by .*?

Which of these is not legal?

In the testbench of variable_majority, A, B and C are 1, 0 and 1 and the count is 2. What is M?

In last_wins, both a and b are 1. What is level?

An always_comb block assigns a variable only inside an if that has no else. What does the tool report?

How many copies of majority_three does variable_majority contain, and how do they relate?

Exercise 2.7.1

Here is a block with three if statements that each assign code. What is code when x and y are 1 and z is 0? What is it when x and z are 1 and y is 0?

last_of_three.sv
// Exercise: three ifs assign the same variable; when several are true, the last one decides.
module last_of_three(input logic x, y, z, output logic [1:0] code);
always_comb begin
code = 2'd0;
if (x) code = 2'd1;
if (y) code = 2'd2;
if (z) code = 2'd3;
end
endmodule
Hint
When several of the ifs are true, which assignment decides?
Solution

When x and y are 1, the first two ifs are true. The second is the last true one, so code is 2'b10, which is 2. When x and z are 1, the first and the third are true, and the third wins, so code is 2'b11, which is 3. A simulation of these two cases printed the values.

Tags: Warm-upSystemVerilog

Exercise 2.7.2

Build xor_aoi, with inputs a and b and output y, from one instance of aoi_cell and two inverters (gate primitives). Name the two inverted signals a_n and b_n.

Hint
The cell's output is 0 when A and B are both 1, or when C and D are both 1. The inputs are equal when both are 1, or when both are 0.
Solution

The cell's output is 0 when A and B are both 1, or when C and D are both 1. That is the condition "a and b are both 1, or a and b are both 0". Feed a and b to the first pair and the inverse of each to the second pair, a_n and b_n: the second pair is both 1 exactly when a and b are both 0. The cell's output is then 0 when a and b are equal and 1 when they differ, which is XOR, so it connects directly to y.

xor_aoi.sv
// Exercise solution: an XOR from one AOI cell and two inverters. The cell's output is 0 when a and b are both 1 or both 0.
module xor_aoi(input logic a, b, output logic y);
logic a_n, b_n;
not g1(a_n, a);
not g2(b_n, b);
aoi_cell core(.A(a), .B(b), .C(a_n), .D(b_n), .F(y));
endmodule

The module is compared with an XOR on all four input rows.

Tags: StandardSystemVerilogGates and schematics

Exercise 2.7.3

Write a module logic_unit with inputs a, b and a 2-bit op, and one output y. When op is 00, y is a AND b; when it is 01, OR; when it is 10, XOR; and for 11, NAND. Use an always_comb block with a case.

Hint
One arm for each of 00, 01 and 10, and a default arm for the last.
Solution

Each arm assigns y with one gate's expression, and the default arm covers 11, so every value of op assigns y and no latch is inferred.

logic_unit.sv
// Exercise solution: a case on a two-bit operation code chooses which gate's result reaches y.
module logic_unit(input logic a, b, input logic [1:0] op, output logic y);
always_comb begin
case (op)
2'b00: y = a & b;
2'b01: y = a | b;
2'b10: y = a ^ b;
default: y = ~(a & b);
endcase
end
endmodule

All 16 combinations of op, a and b are checked against the expected result.

Tags: StandardSystemVerilog

Exercise 2.7.4

An alarm should sound when it is hot, or when there is smoke and the alarm is not muted. The tools warn about this module, and the book's checks treat that warning as a failure. Explain the message and write a correct version.

alarm_latch.sv
// Exercise: the alarm should sound when it is hot, or when there is smoke and the alarm is not muted.
module alarm_latch(input logic hot, smoke, muted, output logic alarm);
always_comb begin
if (hot) alarm = 1'b1;
else if (smoke & ~muted) alarm = 1'b1;
end
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (inferred-latch). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
Hint
What does alarm get when hot is 0 and the second condition is false?
Solution

The tool reports:

hdl/ch02/s07/ex/alarm_latch.sv:4:14: warning: latch inferred for 'alarm' because it is not assigned on all control paths [-Winferred-latch]
    if (hot) alarm = 1'b1;
             ^~~~~
Output of slang for alarm_latch

When hot is 0 and the second condition is false, no statement assigns alarm, so it would have to hold its old value. Add the missing last else, which gives that path the value 0.

alarm_fixed.sv
// Exercise solution: every path through the block assigns alarm.
module alarm_fixed(input logic hot, smoke, muted, output logic alarm);
always_comb begin
if (hot) alarm = 1'b1;
else if (smoke & ~muted) alarm = 1'b1;
else alarm = 1'b0;
end
endmodule

The corrected module is compared with the function hot OR (smoke AND NOT muted) on all eight input rows.

Tags: StandardSystemVerilog

Exercise 2.7.5

Write a module at_least with inputs a, b, c and a 2-bit k, and one output y. The output is 1 when at least k of the three inputs are 1. So with k equal to 0 it is always 1, and with k equal to 3 it needs all three inputs. Use an always_comb block with a case on k.

Hint
Write the condition for each value of k on its own: "at least one" is an OR, and "at least two" is the majority. Use the default arm for 3.
Solution

Each arm of the case is the condition for one value of k. For 0 the answer is the constant 1. For 1, one input being 1 is enough, so it is a | b | c. For 2, some pair must be 1, which is (a & b) | (a & c) | (b & c). The default arm covers the one value left, 3, which needs all three inputs, a & b & c. Every value of k assigns y, so no latch is inferred.

at_least.sv
// Exercise solution: y is 1 when at least k of the three inputs are 1; a case on k picks the condition.
module at_least(input logic a, b, c, input logic [1:0] k, output logic y);
always_comb begin
case (k)
2'd0: y = 1'b1;
2'd1: y = a | b | c;
2'd2: y = (a & b) | (a & c) | (b & c);
default: y = a & b & c;
endcase
end
endmodule

All 32 combinations of k, a, b and c are compared with a reference that adds up the three inputs and compares the sum with k.

Tags: ChallengeSystemVerilog