Concatenation, Replication & Conditional
{}, {N{}}, ?: — bus assembly, field packing, X-merge behavior.
Module 4 · Page 4.7
Three Operators That Do Most of the Bus Work
Engineers coming from C reach for bit-shifts and OR operations to pack fields into a word. In SystemVerilog you write {hdr, payload, crc} and it is done — cleaner, self-documenting, and impossible to misalign. That is concatenation. The result width is the exact sum of the operand widths. No padding surprises, no sign-extension ambiguity.
Replication solves the copy-paste problem. Sign-extending a 4-bit value to 32 bits the long way means writing the sign bit twenty-eight times by hand. {{28{narrow[3]}}, narrow} does it in eight characters and survives a width refactor automatically.
The conditional cond ? a : b is a 2:1 mux as an expression. It works anywhere an expression is valid — inside assign, inside constraints, inside function return statements. The part that trips people up: when cond is X or Z, the result is not a blanket X. It is a bitwise merge — bits that agree across both branches survive clean; bits that disagree become X. So x ? 8'hFF : 8'hFF gives 8'hFF with no X at all. But x ? 8'hF0 : 8'hFF gives 8'hFX — upper nibble clean, lower nibble X.
What Are These Things Actually Doing?
Concatenation — Physical Bus Assembly
Think of each signal as a bundle of wires. Concatenation physically connects those bundles end-to-end. The leftmost operand occupies the most significant positions; the rightmost occupies the least significant. The result width is exactly the sum. There is no implicit padding, no sign extension — you get exactly the wires you joined.
In protocol work, this is how you assemble a frame: {start_bit, addr[6:0], rw, data[7:0], parity} — nine fields snapped together in one expression with no possibility of mis-shifting a field by one bit.
Replication — Structured Repetition
{N{expr}} is syntactic sugar for writing expr N times inside a concatenation. {4{2'b10}} is exactly {2'b10, 2'b10, 2'b10, 2'b10} = 8'b10101010. N must be a constant expression — a literal, a parameter, or a localparam. Never a variable.
Conditional — Inline Mux
cond ? expr_true : expr_false evaluates both branches syntactically and selects one at runtime. In synthesis it becomes a 2:1 multiplexer. Nested ternaries build priority mux chains. Both branches are evaluated by the simulator on every call — there is no short-circuit evaluation like in C's &&.
- {a, b, c} — Concatenation — Joins signals MSB-first. Result width = sum of all parts. No padding. Every operand needs a defined compile-time width.
- {N{expr}} — Replication — Repeats expr exactly N times. N must be constant. Used for sign extension, test pattern generation, zero/one padding.
- cond ? a : b — Conditional — Selects a when cond is true, b when false. Maps to a 2:1 mux in RTL. X/Z condition triggers bitwise merge, not blanket X.
Syntax, Rules, and What the Compiler Won't Always Tell You
// ── CONCATENATION ────────────────────────────────────────────────────
// result = {expr1, expr2, ..., exprN}
// Width = width(expr1) + width(expr2) + ... + width(exprN)
logic [3:0] a = 4'hA;
logic [3:0] b = 4'hB;
logic [7:0] ab = {a, b}; // 8'hAB — a in upper 4 bits, b in lower 4
// Nested concatenation — braces flatten automatically
logic [11:0] abc = {a, {b, 4'hC}}; // same as {a, b, 4'hC} = 12'hABC
// ILLEGAL: unsized literal has no defined width
// logic [11:0] bad = {8, a, b}; // ERROR — use 4'd8 instead
// ── REPLICATION ──────────────────────────────────────────────────────
// result = {count{expr}} — count must be a constant expression
logic [7:0] pat = {4{2'b10}}; // 8'b10101010 — replicate 2'b10 four times
logic [7:0] ones = {8{1'b1}}; // 8'hFF
parameter PAD = 4;
logic [11:0] padded = {a, {PAD{1'b0}}}; // {4'hA, 4'h0} = 12'hA00 — uses parameter: legal
// Sign extension using replication of MSB
logic signed [3:0] s4 = -3; // 4'b1101
logic signed [7:0] s8 = {{4{s4[3]}}, s4}; // 8'b1111_1101 = -3
// ── CONDITIONAL ──────────────────────────────────────────────────────
// result = condition ? expr_true : expr_false
// Result width is determined by context (e.g., LHS width)
// Both branches are syntactically evaluated every time (no short-circuit)
logic sel = 1'b1;
logic [7:0] mux_out = sel ? 8'hFF : 8'h00; // 8'hFF
// Nested conditional — priority mux chain
logic [1:0] s;
logic [7:0] y = (s == 2'b00) ? a :
(s == 2'b01) ? b : 8'hFF; // 2-level priority mux| Operator | Key constraint | Result width | X/Z behavior |
|---|---|---|---|
{a, b, c} | All operands must have defined widths at compile time | Sum of all operand widths — exact | X/Z values pass through unchanged in their bit positions |
{N{expr}} | N must be a constant expression (literal/parameter/localparam). N=0 is illegal. | N × width(expr) | X/Z in expr replicated N times |
c ? a : b | None — both branches can be any expression | Determined by context; narrower branch zero-extended | X/Z condition → bitwise merge of a and b |
Bit-Level Assembly — Seeing the Pieces Connect
Concatenation: Field Positions in the Result
Values: a = 4'hA (1010), b = 4'h5 (0101), c = 4'h3 (0011). Expression: {a, b, c} = 12-bit result.
| Operand | Width | Binary | Occupies result bits | Comment |
|---|---|---|---|---|
a | 4 | 1010 | [11:8] | MSB side — leftmost operand = highest bits |
b | 4 | 0101 | [7:4] | Middle |
c | 4 | 0011 | [3:0] | LSB side — rightmost operand = lowest bits |
| {a, b, c} | 12 | 1010_0101_0011 | [11:0] | = 12'hA53 |
Replication: {N{expr}} Expansion
| Expression | N | expr bits | Expanded form | Result |
|---|---|---|---|---|
{2{4'b1010}} | 2 | 4 | {4'b1010, 4'b1010} | 8'b1010_1010 |
{3{2'b01}} | 3 | 2 | {2'b01, 2'b01, 2'b01} | 6'b010101 |
{4{1'b1}} | 4 | 1 | {1'b1, 1'b1, 1'b1, 1'b1} | 4'b1111 |
{4{sneg[3]}}, sneg} | 4 (sign) | 1 | MSB copied 4 times + original 4-bit | 8-bit sign-extended value |
Conditional X-Merge: Bit-by-Bit Behavior
Using val_a = 8'hF0 (1111_0000) and val_b = 8'hFF (1111_1111). When condition is X, the simulator merges both branches bit-by-bit:
| Bit | val_a bit | val_b bit | Agree? | Result (cond=1) | Result (cond=0) | Result (cond=X) |
|---|---|---|---|---|---|---|
| [7] | 1 | 1 | Yes | 1 | 1 | 1 |
| [6] | 1 | 1 | Yes | 1 | 1 | 1 |
| [5] | 1 | 1 | Yes | 1 | 1 | 1 |
| [4] | 1 | 1 | Yes | 1 | 1 | 1 |
| [3] | 0 | 1 | No | 0 | 1 | X |
| [2] | 0 | 1 | No | 0 | 1 | X |
| [1] | 0 | 1 | No | 0 | 1 | X |
| [0] | 0 | 1 | No | 0 | 1 | X |
| Result | 8'hF0 | 8'hFF | 8'bFFFF_XXXX |
Upper nibble F appears in both branches identically — it survives clean even with an X condition. Lower nibble differs (0 in F0, 1 in FF) — it collapses to X. This is why partial X on a bus often points to a conditional with an unknown select signal.
Code Examples — Field Packing to Frame Building
Example 1 — Beginner: All Three Operators
module tb_core_operators;
logic [3:0] a = 4'hA; // 1010
logic [3:0] b = 4'hB; // 1011
logic [3:0] c = 4'hC; // 1100
logic sel;
logic signed [3:0] sneg = -3; // 4'b1101
logic signed [7:0] sext;
initial begin
// ── Concatenation ──────────────────────────────────────────────
$display("[Concat] {a,b} = 0x%02h (8-bit: AB)", {a, b});
$display("[Concat] {a,b,c} = 0x%03h (12-bit: ABC)", {a, b, c});
// ── Replication ────────────────────────────────────────────────
$display("[Replic] {4{a}} = 0x%04h (16-bit: AAAA)", {4{a}});
$display("[Replic] {2{a,b}} = 0x%04h (16-bit: ABAB)", {2{a, b}});
$display("[Replic] {4{2'b10}} = %08b", {4{2'b10}});
// ── Sign extension via replication ─────────────────────────────
sext = {{4{sneg[3]}}, sneg}; // replicate MSB 4×, append original 4 bits
$display("[SignExt] -3: 4'b%04b → 8'b%08b (%0d)", sneg, sext, $signed(sext));
// ── Conditional ────────────────────────────────────────────────
sel = 1'b1;
$display("[Cond] sel=1 → 0x%h", sel ? a : b); // A
sel = 1'b0;
$display("[Cond] sel=0 → 0x%h", sel ? a : b); // B
$finish;
end
endmoduleExpected output:
[Concat] {a,b} = 0xAB (8-bit: AB)
[Concat] {a,b,c} = 0xABC (12-bit: ABC)
[Replic] {4{a}} = 0xAAAA (16-bit: AAAA)
[Replic] {2{a,b}} = 0xABAB (16-bit: ABAB)
[Replic] {4{2'b10}} = 10101010
[SignExt] -3: 4'b1101 → 8'b11111101 (-3)
[Cond] sel=1 → 0xA
[Cond] sel=0 → 0xBExample 2 — Intermediate: 32-bit Register Field Pack / Unpack
// STATUS register layout:
// [31:24] = err_code (8 bits) [23:16] = flags (8 bits)
// [15:8] = burst_len (8 bits) [7:0] = state (8 bits)
module tb_reg_packing;
logic [7:0] err_code = 8'hAB;
logic [7:0] flags = 8'b1010_0011;
logic [7:0] burst_len = 8'd15;
logic [7:0] state = 8'h05;
logic [31:0] status;
initial begin
// ── Pack all 4 fields into one 32-bit word ─────────────────────
status = {err_code, flags, burst_len, state};
$display("Status = 0x%08h", status); // 0xABA30F05
// ── Unpack by slicing ──────────────────────────────────────────
$display("err_code = 0x%02h", status[31:24]); // AB
$display("flags = 8'b%08b", status[23:16]); // 10100011
$display("burst_len = %0d", status[15:8]); // 15
$display("state = 0x%02h", status[7:0]); // 05
// ── Read-modify-write: update only the state field ─────────────
// Replace lower 8 bits, keep upper 24 unchanged
status = {status[31:8], 8'hFF};
$display("Updated = 0x%08h", status); // 0xABA30FFF
$finish;
end
endmoduleExpected output:
Status = 0xABA30F05
err_code = 0xAB
flags = 8'b10100011
burst_len = 15
state = 0x05
Updated = 0xABA30FFFExample 3 — Verification: Scoreboard Transaction Packing
// AXI-like beat: {tid[3:0], data[15:0]} = 20-bit frame
module tb_scoreboard;
// Build expected beat from known field values
function automatic logic [19:0] pack_beat(
input logic [3:0] tid,
input logic [15:0] payload
);
return {tid, payload};
endfunction
// Parse and validate received beat
task automatic check_beat(
input logic [19:0] received,
input logic [3:0] exp_tid,
input logic [15:0] exp_data
);
logic [19:0] expected = pack_beat(exp_tid, exp_data);
logic [3:0] got_tid = received[19:16];
logic [15:0] got_data = received[15:0];
if (received !== expected)
$error("MISMATCH got=0x%05h exp=0x%05h [tid:%0h→%0h data:%04h→%04h]",
received, expected, exp_tid, got_tid, exp_data, got_data);
else
$display("PASS beat=0x%05h tid=0x%h data=0x%04h",
received, got_tid, got_data);
endtask
initial begin
check_beat(20'hA_CAFE, 4'hA, 16'hCAFE); // PASS
check_beat(20'hB_CAFE, 4'hA, 16'hCAFE); // MISMATCH: tid corrupted
check_beat(20'hA_1234, 4'hA, 16'hCAFE); // MISMATCH: data wrong
$finish;
end
endmoduleExpected output:
PASS beat=0xACAFE tid=0xA data=0xCAFE
MISMATCH got=0xBCAFE exp=0xACAFE [tid:A→B data:CAFE→CAFE]
MISMATCH got=0xA1234 exp=0xACAFE [tid:A→A data:CAFE→1234]Example 4 — Corner Cases: X Merge, Nested Replication, Frame Assembly
module tb_corner_cases;
logic [7:0] val_a = 8'hF0; // 1111_0000
logic [7:0] val_b = 8'hFF; // 1111_1111
logic [7:0] result;
logic cond;
logic [15:0] pkt;
initial begin
// ── Corner 1: X condition — bitwise merge, not blanket X ──────
cond = 1'bx;
result = cond ? 8'hAA : 8'hAA; // identical branches
$display("X cond, same values : 0x%02h", result); // AA — no X!
result = cond ? val_a : val_b; // F0 vs FF
$display("X cond, F0 vs FF : %08b", result); // 1111_xxxx
result = cond ? 8'hFF : 8'h00; // all bits differ
$display("X cond, FF vs 00 : %08b", result); // xxxxxxxx
// ── Corner 2: Nested replication ──────────────────────────────
$display("{3{4'b0101}} : %012b", {3{4'b0101}}); // 010101010101
$display("{2{3{2'b01}}} : %012b", {2{3{2'b01}}}); // 010101010101
// ── Corner 3: Concat + Replication = Frame Builder ────────────
// Frame: 8-bit sync header + 4 pairs of alternating bits
pkt = {8'hA5, {4{2'b10}}};
$display("Frame (sync+pattern) : 0x%04h", pkt); // 0xA5AA
// ── Corner 4: Z condition — identical behavior to X ───────────
cond = 1'bz;
result = cond ? val_a : val_b;
$display("Z cond, F0 vs FF : %08b", result); // 1111_xxxx
$finish;
end
endmoduleExpected output:
X cond, same values : 0xAA
X cond, F0 vs FF : 1111xxxx
X cond, FF vs 00 : xxxxxxxx
{3{4'b0101}} : 010101010101
{2{3{2'b01}}} : 010101010101
Frame (sync+pattern) : 0xA5AA
Z cond, F0 vs FF : 1111xxxxSimulation Behavior — Width Rules and the X You Didn't Expect
Concatenation Width Is Self-Determined
A concatenation expression has a self-determined width equal to the sum of its operand widths. It does not grow to match its context. This matters when you assign a narrow concatenation to a wider variable — the lower bits of the destination receive the concatenation value, and the upper bits are zero-padded by the assignment, not by the concatenation itself. Conversely, if the concat is wider than the destination, the upper bits of the concat are silently dropped.
logic [3:0] a = 4'hF;
logic [3:0] b = 4'hF;
logic [7:0] result8;
logic [9:0] result10;
result8 = {a, b}; // {4'hF, 4'hF} = 8'hFF — exact fit
result10 = {a, b}; // 8'hFF assigned to 10-bit → zero-extended: 10'h0FF
// Truncation: concat wider than destination
logic [5:0] narrow;
narrow = {a, b}; // 8'hFF → truncated to 6'b11_1111 = 6'h3F — MSBs lost!
// Safe approach: always match concat width to destination width
logic [7:0] pkt;
pkt = {4'h0, a}; // explicit zero-pad to reach 8 bits: 8'h0F
$display("pkt = 0x%02h", pkt); // 0FSynthesis: What Hardware Do These Generate?
| Expression | Hardware generated | Area / Timing | Notes |
|---|---|---|---|
{a, b} | Pure wiring — connect bus A to upper outputs, bus B to lower | Zero gates, zero delay | Most efficient operation in SV — just re-routes wires |
{N{expr}} constant N | N copies of the expr wires connected in parallel | Zero gates — only fanout load | Synthesis duplicates the net; no logic added |
cond ? a : b | 2:1 multiplexer | 1 MUX cell × bus width | Nested ternary = priority MUX chain (use case for equal priority) |
| Nested ternary 4-way | 3 cascaded 2:1 MUXes | 3× MUX delay — timing-critical if deep | Synthesis may restructure; check timing report |
Where You'll Use These in Real Projects
// ── 1. MONITOR: parse incoming AHB address phase ──────────────────
// Raw bus: {haddr[31:0], htrans[1:0], hsize[2:0], hwrite} = 36 bits
logic [35:0] raw_beat;
logic [31:0] mon_addr = raw_beat[35:4];
logic [1:0] mon_trans = raw_beat[3:2];
logic [2:0] mon_size = {2'b00, raw_beat[1]}; // zero-extend 1-bit size field
logic mon_write = raw_beat[0];
// ── 2. SCOREBOARD: build expected output for comparison ───────────
function automatic logic [35:0] build_ahb(
input logic [31:0] addr, input logic [1:0] trans,
input logic wr, input logic [1:0] size
);
return {addr, trans, size, wr};
endfunction
// ── 3. DRIVER: conditional data selection ────────────────────────
logic [7:0] drive_data = is_error_injection ? err_pattern : normal_data;
// ── 4. CONSTRAINT: conditional distribution ──────────────────────
// In a class:
// rand logic [31:0] addr;
// constraint addr_align { addr == {addr[31:2], 2'b00}; } // force 4-byte align
// ── 5. ASSERTION: check field value in packed word ────────────────
// Check that the opcode field [15:12] in a 16-bit command is never 0
// assert property (@(posedge clk) cmd_valid |-> cmd[15:12] != 4'h0);
// ── 6. COVERAGE: combine fields for cross coverage ────────────────
// coverpoint {prot_type, burst_len} covers all combinations
// cross {wr_rd, beat_count} cross-covers read/write vs burst depth
// ── 7. ERROR INJECTION: flip a specific bit using concat ──────────
function automatic logic [31:0] inject_bit_error(
input logic [31:0] data,
input int bit_pos
);
// flip bit at bit_pos using conditional rebuild
logic [31:0] mask = 32'h1 << bit_pos;
return data ^ mask;
endfunctionBugs Engineers Actually Hit — With Waveform Thinking
Bug 1 — Unsized Literal in Concatenation
logic [7:0] data = 8'hAB;
logic [11:0] pkt;
// BUGGY: 5 is an unsized integer — no defined width
pkt = {4, data}; // COMPILE ERROR: unsized literal in concatenation
// FIXED: always size the literal explicitly
pkt = {4'd4, data}; // 4-bit value 4 = 4'b0100, then 8'hAB → 12'h4AB
// If you want to prepend 4 zero bits (zero-extend data to 12 bits):
pkt = {4'h0, data}; // 12'h0AB
// Common alternate: use streaming assignment or cast
pkt = {12'(data)}; // zero-extends data to 12 bits — same as {4'h0, data}Bug 2 — Width Miscalculation: MSBs Silently Truncated
logic [3:0] opcode = 4'hA;
logic [2:0] mode = 3'b110; // only 3 bits wide
logic [7:0] cmd;
// BUGGY: packing 7 bits (4+3) into 8-bit cmd
// Concat result = 7'b1010_110, zero-padded to 8 bits → cmd = 8'b0_1010_110 = 8'h56
// Engineer expects opcode in bits [7:4] but it ends up in bits [6:3] — off by one!
cmd = {opcode, mode};
// CORRECT: ensure total operand widths equal destination width
logic [3:0] mode4 = {1'b0, mode}; // widen mode to 4 bits first
cmd = {opcode, mode4}; // 4+4 = 8 bits: 8'hA6
// Or pad explicitly inside the concat:
cmd = {opcode, 1'b0, mode}; // 4+1+3 = 8: same result, intent is clearBug 3 — X on Conditional Condition at Simulation Startup
// RTL: output mux gated by 'valid' signal
logic valid; // not initialized — starts as X at time 0
logic [7:0] data_in = 8'hFF;
logic [7:0] data_out;
assign data_out = valid ? data_in : 8'h00;
// At time 0: valid=X, data_in=8'hFF, 8'h00
// Bit merge: 8'hFF vs 8'h00 → all bits differ → data_out = 8'hXX
// Every downstream consumer of data_out is now also X
// FIX 1: initialize in declaration
logic valid = 1'b0; // data_out starts at 8'h00 — clean
// FIX 2: asynchronous reset drives known state
always_ff @(posedge clk or posedge rst)
if (rst) valid <= 1'b0;
else valid <= next_valid;
// FIX 3: testbench — drive before time 0 using initial block
initial begin
valid = 1'b0; // immediate assignment — active before any clock edge
@(posedge clk);
valid = 1'b1;
endBug 4 — Variable Replication Count
int pad_n = 8; // runtime variable
logic [7:0] data = 8'hAB;
logic [15:0] padded;
// BUGGY: pad_n is a variable — ILLEGAL, will not compile
// padded = {data, {pad_n{1'b0}}}; // ERROR
// FIXED option 1: use a parameter (most common solution)
parameter PAD_BITS = 8;
padded = {data, {PAD_BITS{1'b0}}}; // legal: PAD_BITS is a compile-time constant
// FIXED option 2: if runtime width is genuinely needed, use shift
padded = {8'h00, data} << pad_n; // variable shift — legal, generates barrel shifter
// Note: semantics differ from padding — understand what you need
// FIXED option 3: use localparam in a generate block
// for parameterized modules — set pad width via module parameterBug 5 — Conditional Branch Width Asymmetry
logic [7:0] wide_val = 8'hFF;
logic [3:0] narrow_val = 4'hF; // 4'b1111
logic [7:0] result;
logic sel = 1'b0;
// BUGGY EXPECTATION: engineer expects sel=0 → result = 8'hFF (all ones)
result = sel ? wide_val : narrow_val;
// narrow_val=4'hF in an 8-bit context → zero-extended to 8'h0F — NOT 8'hFF!
$display("result = 0x%02h", result); // 0x0F — upper nibble is ZERO
// FIX 1: if intent is to replicate the nibble to fill 8 bits:
result = sel ? wide_val : {2{narrow_val}}; // {4'hF, 4'hF} = 8'hFF
// FIX 2: if intent is sign extension (narrow is signed):
logic signed [3:0] s_narrow = 4'hF; // -1 in signed 4-bit
result = sel ? wide_val : $unsigned({{4{s_narrow[3]}}, s_narrow});
// s_narrow[3]=1 → sign extend: 8'b1111_1111 = 8'hFF
// FIX 3: always match branch widths explicitly to make intent clear
result = sel ? wide_val : {4'h0, narrow_val}; // explicit zero-extend: 8'h0FInterview Questions
Beginner Level
Q1: What is the result width of {4'b1010, 3'b101, 1'b1}? 8 bits (4 + 3 + 1 = 8). The result is 8'b1010_1011. The concatenation width is always the exact sum of all operand widths — no padding or extension is added. Q2: Can you write {0, data} to prepend a zero bit? No. 0 is an unsized integer literal with no defined bit width. SystemVerilog requires every operand in a concatenation to have a defined compile-time width. Use 1'b0 instead: {1'b0, data}. Q3: What does {2{4'b1010}} evaluate to?8'b1010_1010. The replication {2{4'b1010}} is exactly equivalent to {4'b1010, 4'b1010}. Result width = 2 × 4 = 8 bits.
Intermediate Level
Q4: What is the result of 1'bx ? 8'hF0 : 8'hFF?8'bFFFF_XXXX. When the condition is X, the simulator performs a bitwise merge: 8'hF0 = 8'b1111_0000 vs 8'hFF = 8'b1111_1111. Bits [7:4] are 1 in both — result is 1. Bits [3:0] are 0 vs 1 — they disagree — result is X. It is not a blanket 8'hXX. Q5: How do you swap the upper and lower bytes of a 16-bit word in one expression?swapped = {word[7:0], word[15:8]}; Concatenate the lower byte in the upper position and the upper byte in the lower position. No intermediate variable needed. This is one of the clearest demonstrations of concatenation's power — swapping byte lanes in a single, self-documenting expression.
Experienced Engineer Level
Q6: A scoreboard receives a 20-bit AXI beat and finds partial X on the data field [15:0] but the tid field [19:16] is clean. What is the most likely cause? The DUT has an output mux (conditional expression) on the data path whose select signal is X. Because X-merge is bitwise, bits that agree between the mux branches stay clean — if the tid field happens to carry the same value in both branches (or comes from a different path without a mux), it stays clean. Trace the signal driving the select of the output mux for the data path back to find the undriven or uninitialized signal. Q7: What hardware does a nested ternary chain generate in synthesis, and when should you prefer case? A nested ternary generates a priority MUX chain — the first condition is checked first, and subsequent conditions only matter if earlier ones fail. This creates a series of cascaded 2:1 MUXes with increasing delay toward the final output. Use case (or unique case) when all select values are mutually exclusive and equal priority — synthesis can implement them as a parallel decoder and a single-level MUX tree, which is faster and uses less area. Nested ternary is correct for priority encoding; case is correct for 1-of-N selection.
Best Practices & Coding Guidelines
- Always size concatenation literals — Never use unsized integers in {}. Use 4'd5 not 5. Catches errors at compile time instead of simulation.
- Comment field positions — When packing protocol frames, add a comment block documenting which bits hold which field. Future-you and reviewers will thank you when the spec changes.
- Match branch widths in conditionals — Always make both ?: branches the same width explicitly. Relying on implicit zero-extension of the narrow branch invites silent width bugs.
- Use parameters for replication counts — Hardcoding {28{bit}} in a 32-bit design creates a maintenance hazard. Use parameter DATA_W = 32 and write {(DATA_W-4){bit}}.
| Task | Correct approach | Common mistake |
|---|---|---|
| Prepend zero bits to widen a bus | {4'h0, narrow_bus} | {4, narrow_bus} — unsized, compile error |
| Sign-extend a signed value | {{N{val[MSB]}}, val} or $signed(val) in wider context | {N{val}} — replicates the whole value, not just the sign bit |
| Read-modify-write a register field | {reg[31:8], new_byte} | Shift-and-mask: error-prone, harder to read |
| Byte swap a 32-bit word | {w[7:0], w[15:8], w[23:16], w[31:24]} | Multiple shift+OR operations — less readable, same result |
| Generate N-wide all-ones mask | {N{1'b1}} or '1 | ~0 — depends on expression width context, can behave unexpectedly |
| Conditional with default in RTL | assign y = en ? data : '0; | Missing default in always block → inferred latch |
Summary
Concatenation, replication, and conditional are the three operators you reach for when assembling or decomposing data. Concatenation does the structural work — no other operator packs a protocol frame as cleanly. Replication eliminates repetition and keeps parameterized sign-extension correct even when widths change. The conditional is a mux — write it in RTL where you would draw one, and use it in testbenches for inline value selection.
- Every operand in
{}must have a defined compile-time width. Unsized literals cause compile errors. Always use sized literals:4'd5,8'hFF,1'b0. - Replication count N must be a constant. Use a parameter or localparam. If you need runtime repetition, use a variable shift instead — and understand that the semantics differ.
- X/Z condition → bitwise merge, not blanket X. Bits that agree across both branches stay clean. Bits that disagree become X. Partial X on a bus almost always traces back to a conditional with an unknown select signal.
- Concatenation and replication are free in hardware — pure wiring, zero gates. Only the conditional generates actual logic (a MUX).
- Match branch widths explicitly in conditionals. Implicit zero-extension of a narrow branch is valid SV but creates subtle value bugs when the engineer expected sign-extension or replication.