Vectors and Arrays
A one-bit signal is a wire. Most of a design moves buses: a 32-bit address, an 8-bit byte, a 16-bit instruction. In Verilog a bus is a vector, and slicing, joining and indexing vectors is most of what bus-level RTL consists of.
Declaring vectors
A vector is a wire or reg with a range, most significant index first:
`timescale 1ns / 1ps
`default_nettype none
module vector_declarations (
input wire [31:0] data_in,
output reg [15:0] data_out
);
wire [7:0] byte_a;
wire [15:0] halfword;
wire [63:0] doubleword;
reg [3:0] nibble;
assign byte_a = data_in[7:0];
assign halfword = data_in[15:0];
assign doubleword = {32'h0, data_in}; // zero-extend to 64 bits
always @(*) begin
data_out = data_in[31:16];
end
endmodule
The width is MSB minus LSB plus one, so [7:0] is 8 bits and [8:0] is 9. Writing [8:0] for a byte is a classic mistake; count the indices. Always write ranges descending, [N-1:0]. Ascending ranges like [0:7] are legal, put the most significant bit at index 0, and cause confusion whenever they meet a descending one.
Selecting bits
A single bit by index:
wire [31:0] word;
assign msb = word[31];
assign lsb = word[0];
assign bit7 = word[7];
The index can be a variable, and synthesis handles that as a mux.
A part-select with constant bounds:
assign byte3 = word[31:24];
assign byte0 = word[7:0];
assign nibble_high = word[7:4];
assign upper_half = word[31:16];
Both bounds must be constants. When the position depends on a signal, for instance picking byte N of a word, Verilog-2001 has the indexed part-select:
wire [31:0] word;
wire [1:0] byte_sel;
wire [7:0] selected_byte;
assign selected_byte = word[byte_sel*8 +: 8]; // 8 bits starting at bit byte_sel*8
x[base +: width] takes width bits upward from base, that is x[base+width-1 : base]; x[base -: width] takes them downward, x[base : base-width+1]. The width is a constant, the base can be anything. This is the tool for pulling lanes out of a wide bus:
`timescale 1ns / 1ps
`default_nettype none
module byte_extractor (
input wire [31:0] word,
input wire [1:0] byte_sel, // 0 is the least significant byte
output wire [7:0] byte_out
);
assign byte_out = word[byte_sel*8 +: 8];
endmodule
Concatenation
Braces join signals and literals into a wider vector, most significant first:
wire [3:0] high_nibble, low_nibble;
wire [7:0] full_byte;
assign full_byte = {high_nibble, low_nibble}; // [7:4] and [3:0]
The result is as wide as the sum of the parts. Literals are fine inside it, which is how you widen a value to keep a carry:
wire [7:0] a;
wire [8:0] sum_no_overflow;
assign sum_no_overflow = {1'b0, a} + {1'b0, 8'd200}; // 9-bit add
And it is the natural way to write a shift register:
always @(posedge clk) begin
shift_reg <= {shift_reg[6:0], data_in}; // shift left, new bit at the bottom
end
Replication
{N{expr}} repeats an expression N times, inside an outer set of braces. Its main job is sign extension:
wire [7:0] byte_val;
wire [31:0] sign_extended;
assign sign_extended = {{24{byte_val[7]}}, byte_val};
// 8'b1000_0000, which is -128, becomes 32'hFFFFFF80
wire [15:0] pattern;
assign pattern = {4{4'hA}}; // 16'hAAAA
$signed() will make the synthesiser extend the sign for you in arithmetic, but writing it out with replication is clearer to whoever reviews the code.
Arrays
An array is declared with a second range after the name:
reg [7:0] memory [0:255]; // 256 entries of 8 bits
reg [31:0] reg_file [0:31]; // 32 registers of 32 bits
The range before the name is the width of each element, the packed dimension. The range after it is the number of elements, the unpacked dimension. You index the unpacked dimension first:
`timescale 1ns / 1ps
`default_nettype none
module memory_model (
input wire clk,
input wire we,
input wire [7:0] addr,
input wire [7:0] data_in,
output reg [7:0] data_out
);
reg [7:0] mem [0:255];
always @(posedge clk) begin
if (we)
mem[addr] <= data_in;
data_out <= mem[addr]; // registered read
end
endmodule
A whole array cannot be used in an expression; you index an element first, and then you can select bits of it:
reg [7:0] arr [0:3];
assign foo = arr; // illegal
assign foo = arr[2]; // an 8-bit element
assign bar = arr[1][3]; // bit 3 of element 1
A ROM from a file
$readmemh fills an array from a text file of hex values, one per line, at the start of simulation:
`timescale 1ns / 1ps
`default_nettype none
module rom_256x8 (
input wire [7:0] addr,
output reg [7:0] data
);
reg [7:0] rom_mem [0:255];
initial begin
$readmemh("rom_contents.hex", rom_mem);
end
always @(*) begin
data = rom_mem[addr];
end
endmodule
$readmemb is the binary version. This is the one initial block that belongs in a design file: the FPGA tools recognise an array with an initial $readmemh as a block RAM with initial contents and build exactly that.
Practice Exercise
With a 32-bit bus axi_data and a 2-bit sel: extract byte 2 with a static part-select; extract the byte chosen by sel; swap the two 16-bit halves; and sign-extend byte 0 to 32 bits.
Solution
`timescale 1ns / 1ps
`default_nettype none
module axi_manipulations (
input wire [31:0] axi_data,
input wire [1:0] sel,
output wire [7:0] byte2_static,
output wire [7:0] byte_dynamic,
output wire [31:0] swapped,
output wire [31:0] sign_ext
);
assign byte2_static = axi_data[23:16];
assign byte_dynamic = axi_data[sel*8 +: 8];
assign swapped = {axi_data[15:0], axi_data[31:16]};
assign sign_ext = {{24{axi_data[7]}}, axi_data[7:0]};
endmodule
Vectors handle any width you write down. The next article is about not writing the width down at all: parameters, which let one module serve at 8, 16 and 32 bits from a single source file.