Shift and Concatenation Operators
Section 2.3 built the barrel shifter from stages of muxes. In Verilog it is one operator, <<, and the synthesiser builds the stages. This article covers the four shift operators, the one case where the choice between them matters, and the concatenation and replication operators used as tools for taking vectors apart and putting them together.
The four shifts
| Operator | Name | Fills with |
|---|---|---|
<< |
logical shift left | 0 |
>> |
logical shift right | 0 |
<<< |
arithmetic shift left | 0, same as << |
>>> |
arithmetic shift right | the sign bit |
Only one of the four differs from its logical twin, the arithmetic shift right.
wire [7:0] data = 8'b1010_1100; // 172
wire [7:0] lsl1 = data << 1; // 8'b0101_1000: times 2, top bit lost
wire [7:0] lsl3 = data << 3; // 8'b0110_0000: times 8, three bits lost
wire [7:0] lsr1 = data >> 1; // 8'b0101_0110: 86, unsigned divide by 2
wire [7:0] lsr3 = data >> 3; // 8'b0001_0101: 21
A shift left by $k$ multiplies by $2^k$ as long as nothing falls off the top. A shift right by $k$ divides by $2^k$, rounding down, for unsigned values.
For signed values, dividing by a shift has to keep the sign, which is what >>> does, and only when the operand is declared signed:
wire signed [7:0] sdata = 8'b1010_1100; // -84
wire signed [7:0] asr1 = sdata >>> 1; // 8'b1101_0110: -42
wire signed [7:0] asr2 = sdata >>> 2; // 8'b1110_1011: -21
wire signed [7:0] bad = sdata >> 1; // 8'b0101_0110: +86, the sign is gone
wire signed [7:0] asl1 = sdata <<< 1; // same as << 1
Using >> on a signed value is a silent bug; both operators are legal on anything and the compiler says nothing. And >>> on an unsigned operand behaves exactly like >>, so declaring the signal signed is part of the fix, not an optional extra. This is the LSR against ASR distinction of section 2.3 in language form.
Variable shifts
With a constant shift amount the shift is wiring. With a variable amount the tool builds a barrel shifter:
wire [7:0] data;
wire [2:0] amt; // 0 to 7
wire [7:0] shifted = data << amt;
For 8 bits and a 3-bit amount that is three stages of 2-to-1 muxes, shifting by 1, 2 and 4 under control of the three amount bits, and the delay is three mux delays whatever the amount. Every processor has one of these in its ALU, and in ARM’s case the shift is folded into most arithmetic instructions for free.
Concatenation
Braces join vectors end to end, leftmost most significant:
wire [3:0] a = 4'b1010;
wire [3:0] b = 4'b0101;
wire [7:0] ab = {a, b}; // 8'b1010_0101
wire [11:0] abc = {a, b, 4'h3};
wire [8:0] apb = {1'b0, a, b}; // a zero on the front to widen it
It also works on the left of an assignment, to unpack:
wire [7:0] byte_val;
wire [3:0] hi, lo;
assign {hi, lo} = byte_val; // hi = byte_val[7:4], lo = byte_val[3:0]
And in a clocked block it makes a swap a single statement:
always @(posedge clk) begin
{b_reg, a_reg} <= {a_reg, b_reg};
end
Concatenation and part-selects cost nothing: they are wires, resolved when the design is elaborated. Prefer {a[7:4], new_nibble} over four separate bit assignments; it is shorter and it says what it means.
Replication
{N{expr}} repeats a pattern:
wire [7:0] all_ones = {8{1'b1}}; // 8'hFF
wire [15:0] doubled = {2{data}}; // data twice
wire [31:0] quad = {4{8'hAB}}; // 32'hABABABAB
Its main job is sign extension, replicating the sign bit into the new high bits:
wire signed [7:0] narrow = 8'sb1010_1100; // -84
wire signed [15:0] wide = {{8{narrow[7]}}, narrow}; // 16'hFFAC, still -84
wire [15:0] wide_u = {8'h00, narrow}; // 16'h00AC, 172 as unsigned
A RISC-V instruction
An R-type instruction is six fields packed into 32 bits, and concatenation is how you assemble one and part-selects are how you take it apart:
// [31:25] funct7 | [24:20] rs2 | [19:15] rs1 | [14:12] funct3 | [11:7] rd | [6:0] opcode
wire [6:0] opcode = 7'b011_0011; // register-register operation
wire [4:0] rd = 5'd10;
wire [2:0] funct3 = 3'b000; // ADD
wire [4:0] rs1 = 5'd5;
wire [4:0] rs2 = 5'd6;
wire [6:0] funct7 = 7'b000_0000; // ADD rather than SUB
wire [31:0] instruction = {funct7, rs2, rs1, funct3, rd, opcode}; // 7+5+5+3+5+7 = 32
wire [6:0] dec_funct7 = instruction[31:25];
wire [4:0] dec_rs2 = instruction[24:20];
wire [4:0] dec_rs1 = instruction[19:15];
wire [2:0] dec_funct3 = instruction[14:12];
wire [4:0] dec_rd = instruction[11:7];
wire [6:0] dec_opcode = instruction[6:0];
That is a real instruction decoder’s front end, and it contains no gates at all.
Practice Exercise
Write an 8-bit rotate left by a variable amount, using only continuous assignments. Rotating 8'b1010_0101 by one gives 8'b0100_1011: the top bit comes round to the bottom.
Solution
`timescale 1ns / 1ps
`default_nettype none
module rotate_left_8 (
input wire [7:0] data,
input wire [2:0] amt,
output wire [7:0] result
);
// A rotate by 1 alone is {data[6:0], data[7]}.
// For a variable amount, double the word and take a sliding 8-bit window.
wire [15:0] doubled = {data, data};
assign result = doubled[7 + amt -: 8]; // 8 bits ending at bit 7 + amt
endmodule
With amt 0 the window is bits 7:0, the data unchanged; with 1 it is bits 8:1, which is {data[6:0], data[7]}; with 7 it is bits 14:7. The indexed part-select with a variable base is what makes this a single line.
Next is the shortest operator with the most hardware behind it: the conditional ?:, which is a multiplexer.