Verilog · Chapter 10.10 · Operators & Operands
Concatenation Operator in Verilog — Joining and Splitting Vectors
The concatenation operator joins several values into one wider vector. You list them in braces separated by commas, and they are bundled end to end, with the leftmost value becoming the most-significant bits. This is free wiring, no gates, just bundled connections, and it does the everyday work of building a wide bus from smaller fields, packing a header and payload into one word, or swapping byte order. Used on the left side of an assignment, concatenation also splits a value across several targets, which is how a carry-out and a sum are captured in a single line. Two rules govern it. The result width equals the sum of the operand widths, and every operand must have a known width, so unsized constants are not allowed. This lesson drills joining, splitting, and the common idioms.
Foundation17 min readVerilogConcatenation OperatorBus AssemblyCarry CaptureRTL Design
Chapter 10 · Section 10.10 · Operators & Operands
1. The Engineering Problem
Back in 10.2, an 8-bit add overflowed because the carry-out had nowhere to go. You can capture both the sum and that carry in one clean line — if you can join two targets into one. The concatenation operator does exactly that:
reg [7:0] a, b, sum;
reg carry;
// ...
{carry, sum} = a + b; // 9-bit result split: bit 8 → carry, bits 7:0 → sumThe left-hand side `{carry, sum}` is a 9-bit target built by joining the 1-bit carry (as the MSB) with the 8-bit sum (as the low bits). The 9-bit result of a + b is split across them — the carry-out lands in carry, the low eight bits in sum. No overflow, no lost bit, one assignment. This is concatenation used on the left side to split a value; on the right side it joins values into a wider one.
The concatenation operator joins values into a wider vector (leftmost = most-significant) — and on the left-hand side, splits a value across several targets. It is the operator of structure: assembling buses, packing fields, swapping bytes, and capturing carries.
Concatenation is, with replication (10.9), one of the two construction operators — it builds and breaks apart vectors rather than computing with them. This page teaches joining and splitting, the idioms that make it indispensable, and the one rule that surprises everyone: you cannot put an unsized constant in a concatenation.
2. Mental Model — Join Values End to End; Split on the Left
Visual A — concatenation joins values
3. The Syntax
{a, b, c} // join a (MSB) … c (LSB); width = sum of widths
{4'hA, 4'h5} // = 8'hA5
{byte3, byte2, byte1, byte0} // assemble a 32-bit word from four bytes
{1'b1, addr[14:0]} // set the top bit, keep the low 15
{carry, sum} = a + b; // LHS: split the result across carry and sum
{a, {4{1'b0}}, b} // concatenation with a nested replication (10.9)The rules:
- Comma-separated operands in braces, leftmost most-significant.
- Result width = sum of operand widths — and each operand must have a definite width (§7).
- Operands can be signals, slices, literals (sized), or nested replications/concatenations. Part-selects (
addr[14:0]) and bit-selects are common operands. - On the LHS, the target widths must sum to the source width (or the split mis-aligns — DebugLab 3).
4. Building and Splitting
The two directions of concatenation are its whole power:
// BUILD (RHS) — assemble a wide value from parts
assign packet = {header, payload}; // join header and payload
assign word = {byte_hi, byte_lo}; // two bytes → one word
assign tagged = {1'b1, addr}; // prepend a flag bit
// SPLIT (LHS) — disassemble a value into parts
{carry, sum} = a + b; // capture carry + sum (§1)
{opcode, operand} = instruction; // decode an instruction word
{byte_hi, byte_lo} = word; // unpack a word into bytes- Building on the right joins fields into a bus — packets, instruction words, tagged addresses, multi-byte words.
- Splitting on the left unpacks a value into named parts — capturing a carry, decoding an instruction into opcode and operand, separating a word into bytes. The LHS split is concatenation's most distinctive feature, and the cleanest way to route one value to several destinations in a single assignment.
5. The Key Idioms
A handful of concatenation patterns appear constantly:
// CARRY CAPTURE — keep the carry-out of an add (the §1 / 10.2 fix)
{cout, sum} = a + b;
// BYTE / WORD ASSEMBLY — build a wide word from bytes
word32 = {b3, b2, b1, b0};
// BYTE SWAP — reverse byte order (endianness conversion)
swapped = {word[7:0], word[15:8]}; // swap the two bytes of a 16-bit word
// ZERO / SIGN EXTENSION (with replication, 10.9)
zext = {8'b0, val}; // zero-extend
sext = {{8{val[7]}}, val}; // sign-extend (replicate sign bit)
// FIELD PACK / UNPACK
packed = {flags, addr, data}; // pack
{flags, addr, data} = packed; // unpack- Carry capture (
`{cout, sum} = a + b`) solves the 10.2 overflow directly — the carry-out gets its own bit instead of being dropped. - Byte assembly and swap build and reorder multi-byte words — endianness conversion is a byte-order concatenation.
- Extension uses concatenation with replication (10.9) — zero-extend by prepending
0s, sign-extend by prepending replicated sign bits. - Field pack/unpack is the same concatenation read both ways: build a packed word on the right, unpack it on the left.
Visual B — left-hand-side split
6. Nesting With Replication
Concatenation and replication (10.9) are the two brace operators, and they nest freely — replication is just a concatenation of copies, and it slots into a larger concatenation:
{a, {4{1'b0}}, b} // a, then four 0 bits, then b
{{8{val[7]}}, val} // sign extension: replicated sign bit + value
{hdr, {2{8'hFF}}, crc} // a marker of two 0xFF bytes between header and crc`{n{x}}` (replication) is shorthand for n copies of x joined by concatenation, so dropping a replication into a concatenation is natural and common — most often to insert a fill or an extension between real fields. Sign extension (`{{8{val[7]}}, val}`) is the canonical example of the two operators composing.
7. The Unsized-Constant Rule
The rule that surprises everyone: an unsized constant cannot appear in a concatenation. Because the result width is the sum of operand widths, every operand needs a definite width — and an unsized literal like `1` or `'hA` has no fixed width:
{1, addr} // ILLEGAL — '1' is an unsized constant (no width)
{1'b1, addr} // OK — '1'b1' is a 1-bit sized constant
{8'd0, data} // OK — sized zero
{'h0, data} // ILLEGAL — unsizedA bare `1` is an unsized integer (32 bits wide by default, but treated as unsized in this context), so Verilog cannot determine how many bits it should contribute to the concatenation and forbids it. Always size every literal in a concatenation: `1'b1` not `1`, `8'd0` not `0`, `4'hF` not `'hF`. This is the most common concatenation error (DebugLab 1), and it is caught at compile time — a definite-width discipline the operator enforces.
Visual C — the unsized-constant trap
Every concatenation operand needs a known width
data flow8. Worked Examples
8.1 Example 1 — capture the carry of an adder
module add_with_carry (
input [7:0] a, b,
output [7:0] sum,
output cout
);
assign {cout, sum} = a + b; // 9-bit result split: carry + 8-bit sum
endmoduleThe 9-bit result of the 8-bit add is split by `{cout, sum}`: the carry-out into cout, the low eight bits into sum. This is the 10.2 overflow fix in its cleanest form — one assignment captures the full result with the carry in its own bit. The LHS concatenation sizes everything correctly.
8.2 Example 2 — byte swap (endianness)
module swap16 (
input [15:0] data_in,
output [15:0] data_out
);
// swap the two bytes: low byte to high, high byte to low
assign data_out = {data_in[7:0], data_in[15:8]};
endmoduleReversing byte order is a concatenation of part-selects in swapped order: the low byte data_in[7:0] becomes the high byte of the output, and vice versa. This is how endianness conversion is done — pure wiring, no logic. Extending to a 32-bit swap is `{d[7:0], d[15:8], d[23:16], d[31:24]}`.
8.3 Example 3 — pack and unpack a field word
// PACK — assemble a 16-bit control word from fields
assign ctrl_word = {2'b00, mode, enable, addr}; // pad + mode + enable + addr
// UNPACK — split a received control word back into fields
wire [1:0] r_pad; wire [2:0] r_mode; wire r_en; wire [9:0] r_addr;
assign {r_pad, r_mode, r_en, r_addr} = ctrl_word;The same concatenation read both directions: build the packed word by joining fields (with a sized 2'b00 pad), and recover the fields by splitting the word on the LHS. The field widths must sum to 16 in both directions, or the packing mis-aligns. This pack/unpack pattern is how structured words (control registers, packet headers) are assembled and decoded.
9. Industry Perspective
- Bus and packet assembly is concatenation. Building wide buses, packet headers, and instruction words from fields — and decoding them back — is everyday RTL, done with concatenation on the right (pack) and left (unpack).
- Carry and flag capture use the LHS split.
`{cout, sum} = a + b`(and`{carry, borrow, result} = ...}`style flag capture) is the idiomatic way to keep arithmetic side-results, directly addressing the overflow of 10.2. - Endianness conversion is byte-order concatenation. Byte swaps for big/little-endian interfaces are concatenations of reordered part-selects — pure wiring, common at protocol boundaries.
- The unsized-literal error is a frequent compile failure. Forgetting to size a constant in a concatenation (
{1, ...}) is a routine first-compile error; teams size every literal as a habit, and lint reinforces it.
10. Common Mistakes
- Unsized constant in a concatenation —
`{1, x}`is illegal; size it as`{1'b1, x}`(§7, DebugLab 1). - Wrong operand order — leftmost is MSB; reversing fields swaps high and low (byte-order bugs, DebugLab 2).
- LHS target widths not summing to the source — the split mis-aligns; sizes must match (§4, DebugLab 3).
- Miscomputing the result width — it is the sum of operand widths, not the count.
- Zero-extending a signed value with concatenation — prepend replicated sign bits, not zeros (10.9 carryover).
11. Debugging Lab
Three concatenation-operator debug post-mortems
12. Interview Q&A
13. Exercises
Exercise 1 — Evaluate the concatenations
Give the value and width of each: (a) {4'h3, 4'hC}; (b) {2'b10, 3'b011}; (c) {8'hFF, 8'h00}; (d) {1'b1, 7'h00}; (e) {2'b01, {3{1'b1}}}.
Exercise 2 — Build and split
Write the concatenation for each: (a) capture the carry and 8-bit sum of a + b; (b) swap the two bytes of a 16-bit w; (c) build a 12-bit word from a 4-bit tag (high) and an 8-bit data (low); (d) split a 16-bit pkt into an 8-bit hdr (high) and an 8-bit body (low).
Exercise 3 — Fix the concatenations
Each line states intent. Identify the bug and fix it.
y = {1, data}; // intent: prepend a single 1 bit to 7-bit data
word = {lo, hi}; // intent: hi in high byte, lo in low byte
{op, arg} = instr; // intent: split 12-bit instr into 4-bit op + 8-bit argExercise 4 — Reason about width and order
(a) What is the width of {3'b101, 5'h0A, 1'b1}? (b) Why does {cout, sum} = a + b avoid the overflow that sum = a + b (with narrow sum) suffers? (c) For an LHS split {x, y} = z where z is 12 bits, x is 4 bits, and y is 4 bits, what happens to z's low 4 bits?
14. Summary
The concatenation operator joins values into a wider vector (leftmost most-significant) and, on the left-hand side, splits a value across several targets. With replication, it is one of the two construction operators.
The core ideas:
{a, b, c}joins — leftmost is the MSB, result width = sum of operand widths, free wiring.- On the LHS,
{a, b}splits the source across the targets — the basis of carry capture and field unpacking. - Key idioms: carry capture (
{cout, sum} = a + b), byte assembly and swap, field pack/unpack, and extension (with replication). - Every operand needs a known width — unsized constants are illegal; size every literal (
1'b1, not1). - Concatenation and replication compose —
{a, {n{b}}, c}; sign extension is the canonical pairing.
The discipline this page instils:
- Order operands most-significant first — order is the meaning; reversing swaps high and low.
- Size every literal in a concatenation — the unsized-constant error is the most common one.
- Make LHS target widths sum to the source — or bits are dropped or misaligned.
You can now assemble and disassemble vectors. One operator family remains in the chapter — the selector: Chapter 10.11 Conditional Operator drills ?: — the ternary that builds a multiplexer, choosing between two values based on a condition, and closing the operator vocabulary of combinational logic.
Related Tutorials
- Replication Operator — Chapter 10.9; the other brace operator, which nests inside concatenation for fills and extension.
- Arithmetic Operators — Chapter 10.2; the addition overflow that the carry-capture concatenation idiom solves.
- Scalar vs Vector vs Arrays — Chapter 5.2.4; the vectors and part-selects concatenation joins and splits.
- Verilog Operators & Operands — Chapter 10 overview; the operator-as-hardware model.