Every combinational operator has a cell waiting for it. Addition becomes an adder, comparison becomes a comparator, a shift becomes a shifter, a ternary becomes a multiplexer, and a case becomes a parallel mux with decoders. Elaboration works one operator at a time and never looks at the neighbours, so the word-level netlist reads like the RTL with one cell per operation. A later pass, techmap, expands each word cell into single-bit generic cells, which is the level the mapper consumes; area and delay estimates begin only after mapping.
The design below exercises five operators over 4-bit buses in a single module: + on two 4-bit buses, == on the same pair, a variable << shift, a ?: select, and a four-way case with a default. One script runs proc and opt_clean, records the word cells, then runs techmap and records the bit cells. Predict first, then read the recorded lists against your guess.
Addition records 1 $add cell, comparison records 3 $eq cells with 2 of them decoding the case, the shift records 1 $shl cell, the ternary records 1 $mux cell, and the case records 1 $pmux cell. After techmap the netlist holds 118 single-bit generic cells and no word cells at all.
The script reads the module on its own, selects it as top, and runs proc, which turns processes into word cells, plus opt_clean, which drops unused wires without merging operators. It records stat and the JSON netlist, then asserts the five expected families and the absence of latches: the case has a default, so every path assigns the output. Then techmap runs, the bit-level stat and netlist are recorded, and one PASS line covers both levels, so a passing run certifies the word reading and the bit reading together.
read_verilog <<EOV
module operators (
input wire [3:0] a, b,
input wire [2:0] sh,
input wire sel,
input wire [1:0] op,
output wire [4:0] sum,
output wire eq,
output wire [3:0] shl,
output wire [3:0] m,
output wire [3:0] c
);
assign sum = a + b;
assign eq = (a == b);
assign shl = a << sh;
assign m = sel ? a : b;
always @* begin
case (op)
2'd0: c = a;
2'd1: c = b;
2'd2: c = a ^ b;
default: c = 4'd0;
endcase
end
endmodule
EOV
hierarchy -check -top operators
proc
opt_clean
tee -o stat_word.txt stat
write_json net_word.json
select -assert-none t:$dlatch
select -assert-count 1 t:$add
select -assert-count 1 t:$shl
select -assert-count 1 t:$mux
select -assert-count 1 t:$pmux
select -assert-min 1 t:$eq
techmap
tee -o stat_bits.txt stat
write_json net_bits.json
select -assert-min 1 t:$_AND_
select -assert-min 1 t:$_MUX_
log PASS: operators infer word cells and then bit cellsThe table below reads the word-level netlist cell by cell; every entry is a recorded count, not a guess from the RTL. Notice the comparator row: only one $eq compares the two buses, and the other two decode the four-valued case selector beside the $pmux. Elaboration does not share decoders between unrelated operators; each operation brings its own.
RTL operator | Recorded word cells | Reading |
|---|---|---|
| 1 | One adder on the 4-bit pair. |
| 1 of 3 $eq | One comparator; the rest decode the case. |
| 1 | One variable shifter. |
| 1 | One 4-bit multiplexer. |
four-way | 1 $pmux | One parallel mux plus its $eq decoders. |
The full word list is 1 $add, 3 $eq, 1 $logic_not, 1 $pmux, 1 $shl, 1 $mux, 1 $xor: the table rows plus 1 $xor cell for the exclusive-or branch and 1 $logic_not cell finishing the case decode. Nothing here is shared, folded, or simplified; that happens later, in optimization passes that have the whole netlist in view.
Techmap replaces every word cell with single-bit generic cells, and the recorded bit list is 28 $_XOR_, 27 $_OR_, 29 $_AND_, 9 $_NOT_, 25 $_MUX_: 118 cells in total. The adder dominates the count because addition carries across all five sum bits, while the selects become trees of bit muxes. This is the level the mapper works at, so when a later lesson quotes gates or area, this expansion is what those numbers count.
Delete the default line from the case twin and elaborate it with proc alone. The word netlist keeps its select core and gains 1 $dlatch cell holding the old byte whenever the selector parks on the uncovered value. The script below records both halves of that contrast under one PASS line: the full twin asserts no latch with select logic present, and the default-free twin elaborated with proc asserts the latch.
read_verilog <<EOV
module case_full (
input wire [1:0] sel,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output reg [7:0] y
);
always @* begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = d;
endcase
end
endmodule
EOV
hierarchy -check -top case_full
proc
tee -o stat_full.txt stat
write_json net_full.json
select -assert-none t:$dlatch
select -assert-min 1 t:$pmux t:$mux %u
design -reset
read_verilog <<EOV
module case_nodflt (
input wire [1:0] sel,
input wire [7:0] a,
input wire [7:0] b,
input wire [7:0] c,
input wire [7:0] d,
output reg [7:0] y
);
always @* begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase
end
endmodule
EOV
hierarchy -check -top case_nodflt
proc
tee -o stat_nodflt.txt stat
write_json net_nodflt.json
select -assert-count 1 t:$dlatch
log PASS: full case infers select logic with no latch while the missing branch infers one latchVariant | Recorded $dlatch cells | Verdict |
|---|---|---|
Full case with default | 0 | Combinational: every path assigns y. |
Case with no default | 1 | Latch: selector value 3 must hold y. |
That is the whole latch lesson in two rows; the latch lesson later in this topic proves the fixes three ways and explains why an always_comb block refuses the second row outright. Read on there for the missing-else shape and the SystemVerilog check.
One $add for the sum and one $eq for the comparison; the remaining recorded $eq cells decode the case selector, so each operation brings its own cells.
One $dlatch appears beside the unchanged select core: the recorded default-free netlist shows 1 latch because the missing path must hold the old value.
Every number and picture on this page comes from Yosys 0.70+post runs recorded by the course checker: the operators module runs proc and opt_clean for the word netlist plus write_json, then techmap for the bit netlist, with the expected families asserted and exactly one PASS line. A second unit contrasts the full case against its default-free twin, asserting no latch on one side and one $dlatch on the other.