Skip to content

2.6 Signed numbers, buses, literals and concatenation

Why this matters

Almost nothing in a real design is one bit wide: a switch bank, a color, a count and an address are all rows of wires. SystemVerilog has a small set of operators for taking a row apart and building a new one, and most of the bugs students write with them come from the order of the bits and from the width of a result. This section gives you both, together with the one fact about negative numbers that you need first.

  • Read a bit pattern as an unsigned number and as a signed (two's complement) number.
  • Declare a bus and select a bit or a slice of it.
  • Build a bus from other signals with concatenation and replication, and state the width of the result.
  • Write a sized literal in binary, decimal or hexadecimal, and say what the values X and Z mean.
  • Widen a bus by sign extension or by zero padding, and pick the one that matches the reading.

Picture a row of light switches on a wall, numbered from 0 at the right end. Each switch is one wire. A bus is such a row, and a name for the whole row. You can talk about one switch, a few neighbors, or the whole row. You can also build a new row by taking switches from several places and laying them side by side, and you can lay several copies of one switch next to each other.

That is all the operators in this section do: choose some wires and arrange them in a new row. They never calculate anything, so none of them costs a gate. They only change which wire goes where.

The only thing that needs thought is what the row means as a number, and that depends on how you read it. The next part explains the two readings.

Chapter 4 treats arithmetic fully. Here is just enough to read a bus as a signed number.

An unsigned bus of nn bits gives the bit in position ii the weight 2i2^i. Reading a pattern as a signed number changes only the weight of the leftmost bit.

Definition 2.6.1(signed (two's complement) reading).

In the signed, or two's complement, reading of an nn-bit pattern, the bit in position ii has weight 2i2^i for i<n−1i<n-1, and the leftmost bit, the sign bit, has weight −2n−1-2^{n-1}. The value is the sum of the weights of the bits that are 1.

The patterns do not change; only how a reader values them does. With three bits the weights are −4-4, 22 and 11, so the patterns run from −4-4 (all weight on the sign bit alone) to 33 (the sign bit off and the other two on). So the range of 3-bit signed values is −4-4 to 33, while the unsigned range is 00 to 77.

Wheel of all 8 3-bit patterns from 0 clockwise to 3, then jumping to -4 and counting up to -1 just before 0. The red line marks the jump between 3 and -4, where signed overflow happens.00000001110102201133100−44101−35110−26111−173-bittwo’s complement

Outer numbers are signed values, inner numbers are unsigned values. The red line is the jump from the largest positive value to the most negative one.

3-bit patterns
BitsUnsignedSigned
00000
00111
01022
01133
1004-4
1015-3
1106-2
1117-1

Description: A wheel of the eight 3-bit patterns, each marked with its unsigned value and its signed value.

Figure 2.6.1. All eight 3-bit patterns, with their unsigned and signed values.

A pattern with the leftmost bit 0 has the same value in both readings. A pattern with the leftmost bit 1 is the unsigned value minus 2n2^n, so the 3-bit pattern 101 is 55 unsigned and 5−8=−35-8=-3 signed.

The hardware does not know which reading you mean: a bus is just wires, and the meaning is in the designer's head, in the comparison or arithmetic that you build around it. What the two readings change is how to widen a bus. Adding wires on the left must keep the value, and that needs different bits for the two readings.

Definition 2.6.2(zero padding and sign extension).

Zero padding widens a bus by putting 0 bits on its left. It keeps the unsigned value. Sign extension widens a bus by copying its leftmost bit into every new position on the left. It keeps the signed value.

For example, 101 padded to six bits is 000101, which is 55, and sign-extended is 111101, which is −32+16+8+4+1=−3-32+16+8+4+1=-3. Each widening keeps the value only under its own reading, and the simulation further down shows both.

Definition 2.6.3(bus).

A bus is a group of wires treated as one signal. logic [N-1:0] name; declares one of NN bits. The number on the left of the colon is the index of the most significant bit and the number on the right is the index of the least significant bit, which is usually 0.

name[i] selects one bit, and name[hi:lo] selects a slice: the bits from index hi down to index lo, with hi first. A slice of hi - lo + 1 bits is a bus too. A diagram draws a bus as a line with a short slash and its width written beside it; a line with no slash is one bit.

Definition 2.6.4(concatenation).

Concatenation, {x, y, z}, builds a bus by laying its operands side by side. The first operand becomes the most significant bits, so the leftmost operand is on the left of the result. The width of the result is the sum of the widths of the operands. Every operand must have a width the compiler can know, which excludes an unsized number.

Definition 2.6.5(replication).

Replication, {n{x}}, builds a bus from n copies of x. The count comes first, then the value in its own braces, and n must be a constant. The width is n times the width of x. Replication may be used as one operand of a concatenation.

Definition 2.6.6(sized literal).

A sized literal gives a constant bus with its width spelled out: width'base digits, where the base letter is b for binary, d for decimal, o for octal or h for hexadecimal. The literals 4'b1100, 4'd12 and 4'hC are the same value. A bit can also be x, which means unknown (and is what the simulator shows when nothing has set a value yet, or when two drivers disagree), or z, which means not driven by anything at all (floating).

The module below takes one 8-bit input apart and builds new buses from its pieces. The simulation applies the value shown, and the values below the listing are the results it printed.

bus_parts.sv
// One 8-bit bus taken apart and put back together in several ways.
module bus_parts(input logic [7:0] byte_in,
output logic [3:0] high_nibble, low_nibble,
output logic bit_three,
output logic [7:0] swapped, reversed, repeated);
assign high_nibble = byte_in[7:4];
assign low_nibble = byte_in[3:0];
assign bit_three = byte_in[3];
assign swapped = {byte_in[3:0], byte_in[7:4]};
assign reversed = {byte_in[0], byte_in[1], byte_in[2], byte_in[3],
byte_in[4], byte_in[5], byte_in[6], byte_in[7]};
assign repeated = {4{byte_in[1:0]}};
endmodule

The input is 8'b11001010. Indices count from the right, so the slice byte_in[7:4] is the left half, 4'b1100, and byte_in[3:0] is the right half, 4'b1010. The single bit byte_in[3] is 1'b1. In the concatenation {byte_in[3:0], byte_in[7:4]} the right half is listed first, so it ends up on the left and the two halves swap, giving 8'b10101100. Listing the eight bits from index 0 to index 7 reverses the order of the bits, giving 8'b01010011. And {4{byte_in[1:0]}} is four copies of a two-bit slice, eight bits in all: 8'b10101010.

Example 2.6.1(selecting, concatenating and widening one value).

The constant 20 held in five bits is the pattern 10100. Three results are built from it: a 4-bit signal from one bit, a two-bit slice and a literal; a 10-bit signal widened by sign extension; and a 10-bit signal widened by zero padding.

fruit_bus.sv
// Selecting bits, building a bus with concatenation, and widening one by sign extension and by zero padding.
module fruit_bus(output logic [4:0] apple,
output logic [3:0] pear,
output logic [9:0] orange, padded);
assign apple = 5'd20;
assign pear = {1'b0, apple[2:1], apple[4]}; // 1 + 2 + 1 bits
assign orange = {{5{apple[4]}}, apple}; // five copies of the top bit, then apple
assign padded = {5'b00000, apple}; // five zeros, then apple
endmodule

The 4-bit signal is the concatenation of a 0 bit, the slice apple[2:1] and the single bit apple[4]: one, two and one bits make four. The apple is 5'b10100, so bits 2 and 1 are 10 and bit 4 is 1, and the pear is 4'b0101, which is 4'd5 in decimal.

Sign extension needs the leftmost bit of apple repeated five times, which is the replication {5{apple[4]}}, followed by apple itself. The result is 10'b1111110100. Read as signed numbers, apple is -12 and the widened orange is also -12, so the value survived. Zero padding gives 10'b0000010100, which is 20 as a signed number: the unsigned value survived, and the signed value did not.

The widening that goes with each reading can also be seen on every 3-bit value with a second module.

extend3.sv
// A 3-bit value widened to 6 bits two ways.
module extend3(input logic [2:0] x, output logic [5:0] signext, zeroext);
assign signext = {{3{x[2]}}, x};
assign zeroext = {3'b000, x};
endmodule

For the pattern 3'b101, which is 5 unsigned and -3 signed, the sign-extended signal is 6'b111101, which is -3 signed, and the zero-padded one is 6'b000101, which is 5. A separate check runs all eight input patterns and compares the signed value of the sign-extended result with the signed value of the input, and the unsigned value of the zero-padded result with the input's unsigned value.

What happens if you widen a bus by plain assignment, with no concatenation? An unsigned signal assigned to a wider signal is padded with zeros. The compiler front end used by the book's tools reports it, because it is often a mistake:

widen_implicit.sv
// Assigns a 5-bit value to a 10-bit signal with no concatenation.
module widen_implicit(input logic [4:0] apple, output logic [9:0] orange);
assign orange = apple;
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (width-expand). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
hdl/ch02/s06/widen_implicit.sv:3:19: warning: implicit conversion expands from 5 to 10 bits [-Wwidth-expand]
  assign orange = apple;
                ~ ^~~~~
Output of slang for widen_implicit
literal_demo.sv
// Sized literals in three bases, and the two values a bit can have besides 0 and 1.
module literal_demo(output logic [3:0] dec_lit, bin_lit, hex_lit, mixed, unknown, floating);
assign dec_lit = 4'd12;
assign bin_lit = 4'b1100;
assign hex_lit = 4'hC;
assign mixed = 4'b1x0z;
assign unknown = 4'bx;
assign floating = 4'bz;
endmodule

The three spellings of twelve come out the same in simulation: 4'b1100, 4'b1100 and 4'b1100. A literal with an x or z digit keeps it, bit by bit: 4'b1x0z prints as 4'b1x0z. A single x or z digit fills the whole width, so 4'bx is 4'bxxxx and 4'bz is 4'bzzzz. In a physical circuit every wire has some voltage; X is the simulator's way of saying that a value was never set or was fought over, and Z is its way of saying that nothing drives the wire.

Two ways to get a literal wrong are common. The first is writing a size with no base letter. The tools reject 8'X, and the message is not very helpful: it only points at a missing semicolon. The literal you meant is 8'bX.

x_literal_bad.sv
// A literal written with a size and no base letter.
module x_literal_bad(input logic [7:0] a, output logic [7:0] y);
assign y = a | 8'X;
endmodule
Does not compile. Slang reports an error and stops; the output is shown below the listing.
hdl/ch02/s06/x_literal_bad.sv:3:19: error: expected ';'
  assign y = a | 8'X;
                  ^
Output of slang for x_literal_bad

The second is padding with an unsized constant. In a replication, {8{0}} looks like eight zero bits, but a plain 0 is not one bit wide, and the language does not allow an unsized number inside a concatenation. The compiler front end of the book's tools reports it as warnings, and the book's checks treat any warning as a failure:

pad_bad.sv
// Zero padding written with an unsized zero inside the replication.
module pad_bad(input logic [7:0] narrow, output logic [15:0] wide);
assign wide = {{8{0}}, narrow};
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (width-trunc). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
hdl/ch02/s06/pad_bad.sv:3:17: warning: implicit conversion truncates from 264 to 16 bits [-Wwidth-trunc]
  assign wide = {{8{0}}, narrow};
              ~ ^~~~~~~~~~~~~~~~
hdl/ch02/s06/pad_bad.sv:3:21: warning: unsized integer in concat; using a width of 32 [-Wunsized-concat]
  assign wide = {{8{0}}, narrow};
                    ^
Output of slang for pad_bad

The second warning is the cause: this tool counts an unsized integer in a concatenation as 32 bits, so the right side is 8×32+8=2648\times 32 + 8 = 264 bits. The first warning is the consequence: 264 bits do not fit in wide, which has 16. Write the zeros as a sized literal, {8{1'b0}} or 8'b0.

A concatenation can also stand on the left of an assignment, where it splits the right side into pieces. The exercises use that form.

How CSE 369 writes this

In CSE 369 a bus is declared logic [N-1:0] with the most significant bit on the left, a concatenation lists the most significant operand first, and a replication is written {n{x}}. Block diagrams draw a bus as a line with a slash and the number of bits, and no slash means one bit. Sized literals always carry their width.

Common pitfall

  • Reading a bus from the left. Index 0 is the rightmost bit, the least significant one, and a slice names its higher index first.
  • Listing the parts of a concatenation in the order you think of them, not in the order they should sit. The first operand becomes the leftmost bits.
  • Writing the zeros of a padding as a bare 0. Use 1'b0 or a sized literal; a bare 0 has no fixed width, which the language forbids inside a concatenation (the tools disagree about it: slang counts it as 32 bits, Verilator warns and Icarus rejects it).
  • Leaving the base letter out of a literal, as in 8'X. The base letter comes right after the apostrophe: 8'bX.
  • Widening a signed value by padding with zeros, or an unsigned value by sign extension. Pick the one that matches how you read the bus.
Take the quiz (5 questions)

For a = 4'b1101, what does {a[0], a[3:1], 1'b1} evaluate to?

What is the range of values a 3-bit bus can hold when it is read as signed?

How wide is {3{a[1:0]}}?

In the fruit example, what is the signed value of the zero-padded 10-bit signal?

The compiler reports that {{8{0}}, narrow} is 264 bits wide, where narrow has 8 bits. What width does it give a bare 0?

Exercise 2.6.1

The 8-bit input byte_in is 8'b11001010. What is {byte_in[1:0], byte_in[7:4], byte_in[3:2]}?

Hint
Write the three pieces separately, from the left, and join them. Remember that a slice names its higher index first.
Solution

The three pieces are byte_in[1:0], which is 10; byte_in[7:4], which is 1100; and byte_in[3:2], which is 10. Joined in that order the result is 8'b10110010, which a simulation of the expression confirms.

eval_concat.sv
// Evaluates one concatenation that uses every bit of its input.
module eval_concat(input logic [7:0] byte_in, output logic [7:0] y);
assign y = {byte_in[1:0], byte_in[7:4], byte_in[3:2]};
endmodule

Tags: Warm-upSystemVerilogNumber representation

Exercise 2.6.2

A 3-bit bus holds the pattern 110. What is its unsigned value, and its signed value? Then sign-extend it to five bits and give the signed value of the result.

Hint
The weights of a 3-bit signed number are −4-4, 22 and 11.
Solution

Unsigned, 110 is 4+2=64+2=6. Signed, the leftmost bit weighs −4-4, so the value is −4+2=−2-4+2=-2. Sign extension copies the leftmost bit twice, giving 11110. With five bits the weights are −16-16, 88, 44, 22 and 11, so the value is −16+8+4+2=−2-16+8+4+2=-2, the same as before.

Wheel of all 8 3-bit patterns from 0 clockwise to 3, then jumping to -4 and counting up to -1 just before 0. The red line marks the jump between 3 and -4, where signed overflow happens. Marked values: -2.00000001110102201133100−44101−35110−26111−173-bittwo’s complement

Outer numbers are signed values, inner numbers are unsigned values. The red line is the jump from the largest positive value to the most negative one.

3-bit patterns
BitsUnsignedSigned
00000
00111
01022
01133
1004-4
1015-3
1106-2
1117-1

Description: The wheel of 3-bit patterns with the value negative two marked.

Figure 2.6.2. The same wheel with the value negative two marked.

Tags: Warm-upNumber representation

Exercise 2.6.3

A 16-bit pixel holds a red field of 5 bits on the left, a green field of 6 bits and a blue field of 5 bits on the right. Write a module split_color with input pixel and outputs red, green and blue that splits it, using a concatenation on the left of one assignment.

Hint
The fields are listed on the left side in the order they sit in the pixel, red first.
Solution

A concatenation may be the target of an assignment. The left side {red, green, blue} has 5 + 6 + 5 = 16 bits, the width of pixel, and the bits are handed out from the left: the first 5 go to red, the next 6 to green and the last 5 to blue.

split_color.sv
// Exercise solution: a concatenation on the left of the assignment splits a 16-bit pixel into three fields.
module split_color(input logic [15:0] pixel,
output logic [4:0] red, blue,
output logic [5:0] green);
assign {red, green, blue} = pixel;
endmodule

The module's testbench applies two pixels and checks all three fields.

Tags: StandardSystemVerilog

Exercise 2.6.4

Write a module shuffle with a 12-bit input code and two outputs: swapped, in which the two 6-bit halves of code have traded places, and low_reversed, the four lowest bits of code in reverse order.

Hint
List the pieces in the order they should appear from the left. For the reversal, name the bits one at a time.
Solution

For swapped, the right half code[5:0] goes first so that it lands on the left, and the left half code[11:6] goes second. For low_reversed, the bit that was least significant, code[0], becomes the leftmost bit, and code[3] becomes the rightmost.

shuffle.sv
// Exercise solution: swap the two 6-bit halves of a 12-bit bus, and reverse the order of its four lowest bits.
module shuffle(input logic [11:0] code,
output logic [11:0] swapped,
output logic [3:0] low_reversed);
assign swapped = {code[5:0], code[11:6]};
assign low_reversed = {code[0], code[1], code[2], code[3]};
endmodule

Tags: StandardSystemVerilog

Exercise 2.6.5

Write a module widen4 that takes a 4-bit input x and gives two 8-bit outputs: signext, which keeps the signed value, and zeroext, which keeps the unsigned value. What are the signed values of both outputs when x is 4'b1010?

Hint
For the sign extension you need four copies of the leftmost bit of x.
Solution

signext is four copies of x[3] followed by x, and zeroext is four zeros followed by x. For x = 4'b1010, the signed value of x is -6. The sign-extended result has the signed value -6, the same, and the zero-padded one has the signed value 10, because its unsigned value is kept and 10 is now a positive 8-bit number.

widen4.sv
// Exercise solution: a 4-bit value widened to 8 bits by sign extension and by zero padding.
module widen4(input logic [3:0] x, output logic [7:0] signext, zeroext);
assign signext = {{4{x[3]}}, x};
assign zeroext = {4'b0000, x};
endmodule

Tags: StandardSystemVerilogNumber representation

Exercise 2.6.6

This module should pad a 4-bit value with five zeros on its left. It does not compile cleanly. Explain the message and fix the module.

pad_nibble_bad.sv
// Exercise: pad a 4-bit value with five zeros on the left.
module pad_nibble_bad(input logic [3:0] nib, output logic [8:0] wide);
assign wide = {{5{0}}, nib};
endmodule
Compiles, but is rejected. This file compiles and slang only prints a warning (width-trunc). The book treats a warning as a failure, so the checks reject it; the warning is shown below the listing.
Hint
Look at how many bits the message says the right-hand side has, and ask what width a bare 0 has.
Solution

The front end of the tools says:

hdl/ch02/s06/ex/pad_nibble_bad.sv:3:17: warning: implicit conversion truncates from 164 to 9 bits [-Wwidth-trunc]
  assign wide = {{5{0}}, nib};
              ~ ^~~~~~~~~~~~~
hdl/ch02/s06/ex/pad_nibble_bad.sv:3:21: warning: unsized integer in concat; using a width of 32 [-Wunsized-concat]
  assign wide = {{5{0}}, nib};
                    ^
Output of slang for pad_nibble_bad

Five copies of an unsized 0 count as 5×32=1605\times 32=160 bits, and with the four bits of nib the right side is 164 bits, which will not fit in the 9-bit wide. The fix is to make the zero a sized literal, so each copy is one bit.

pad_nibble.sv
// Exercise solution: the zeros are a sized literal, so the replication has a known width.
module pad_nibble(input logic [3:0] nib, output logic [8:0] wide);
assign wide = {{5{1'b0}}, nib};
endmodule

Tags: StandardSystemVerilog