Module 4: Introduction to Verilog HDL ยท 4.3 Operators

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.

โ† Bitwise and Reduction Operators The Conditional Operator โ†’