$readmemh and $readmemb
An 8-bit ALU with four operations needs operand pairs at the edges: zeros, all ones, overflow, carry, sign boundaries. Fifty such vectors as literals in an initial block is a wall of hex nobody can review, and changing one means editing Verilog. The vectors are data and the testbench is infrastructure, and $readmemh keeps them apart: the vectors live in a text file, editable, scriptable, diffable, and the testbench loads them at the start with one line.
The file
One hex value per whitespace-separated field, // comments allowed, blank lines ignored:
// counter_vectors.hex
0a
ff
80
00
@address jumps:
@0000
aa bb cc dd // addresses 0 to 3
@0010
11 22 33 // addresses 16 to 18; 4 to 15 stay x
$readmemb is the same with binary digits.
Loading
reg [7:0] test_vectors [0:255];
initial begin
$readmemh("vectors.hex", test_vectors);
end
Values fill the array from index 0 until the file or the array runs out. Entries the file does not reach stay x; a file with more values than the array holds produces a warning. Optional start and end addresses load a range:
$readmemh("data.hex", mem, 256, 511); // only mem[256] to mem[511]
Records
For vectors with several fields, pack them into one wide word and unpack in the loop, or keep parallel arrays, one for inputs and one for expected outputs:
reg [17:0] inputs [0:99]; // {op[1:0], a[7:0], b[7:0]}
reg [7:0] expected [0:99];
initial begin
$readmemh("alu_inputs.hex", inputs);
$readmemh("alu_expected.hex", expected);
end
Parallel arrays let you regenerate the expected file from a fixed reference model without touching the inputs.
In design files
$readmemh is the one initial-block idiom that FPGA synthesis honours. An array loaded from a file becomes block RAM with those contents in the bitstream, so the same file is the truth for simulation and for the hardware:
module sine_lut (
input wire [7:0] addr,
output reg [7:0] data
);
reg [7:0] rom [0:255];
initial begin
$readmemh("sine_table.hex", rom);
end
always @(*)
data = rom[addr];
endmodule
An ALU testbench from a file
Each line is one 32-bit word: the top byte holds the opcode in its low two bits, then a, then b, then the expected result.
// alu_vectors.hex: op a b expected
000A050F // ADD 10 + 5 = 15
00FF0100 // ADD 255 + 1 wraps to 0
00807FFF // ADD 128 + 127 = 255
00000000
010A0505 // SUB 10 - 5 = 5
010001FF // SUB 0 - 1 wraps to 255
01FFFF00
02F00F00 // AND
02FFAAAA
03F00FFF // OR
03000000
03AA55FF
`timescale 1ns / 1ps
module alu_8bit (
input wire [1:0] op,
input wire [7:0] a,
input wire [7:0] b,
output reg [7:0] result
);
always @(*) begin
case (op)
2'b00: result = a + b;
2'b01: result = a - b;
2'b10: result = a & b;
2'b11: result = a | b;
default: result = 8'hxx;
endcase
end
endmodule
module tb_alu_readmem();
reg [1:0] op;
reg [7:0] a, b;
wire [7:0] result;
reg [31:0] vectors [0:255];
integer error_count, test_count;
integer log_mcd, log_and_console;
integer i;
alu_8bit dut (.op(op), .a(a), .b(b), .result(result));
initial begin
#50_000;
$fatal(1, "TIMEOUT");
end
initial begin
$dumpfile("tb_alu_readmem.vcd");
$dumpvars(0, tb_alu_readmem);
$timeformat(-9, 0, " ns", 8);
log_mcd = $fopen("alu_results.log");
if (log_mcd == 0)
$fatal(1, "cannot open alu_results.log");
log_and_console = log_mcd | 32'h1;
$readmemh("alu_vectors.hex", vectors);
error_count = 0;
test_count = 0;
for (i = 0; i < 256; i = i + 1) begin
if (vectors[i] !== 32'hxxxxxxxx) begin // stop at the unfilled entries
op = vectors[i][25:24];
a = vectors[i][23:16];
b = vectors[i][15:8];
#10;
test_count = test_count + 1;
if (result !== vectors[i][7:0]) begin
$fdisplay(log_and_console, "%t op=%0d a=%02h b=%02h expected=%02h got=%02h FAIL",
$time, op, a, b, vectors[i][7:0], result);
error_count = error_count + 1;
end else begin
$fdisplay(log_mcd, "%t op=%0d a=%02h b=%02h expected=%02h got=%02h pass",
$time, op, a, b, vectors[i][7:0], result);
end
end
end
if (error_count == 0)
$fdisplay(log_and_console, "RESULT: PASSED, all %0d tests.", test_count);
else
$fdisplay(log_and_console, "RESULT: FAILED, %0d of %0d tests.", error_count, test_count);
$fclose(log_mcd);
$finish;
end
endmodule
The guard vectors[i] !== 32'hxxxxxxxx stops the loop at the end of the real data, so adding lines to the file adds tests with no change to the Verilog. It has to be !==: 4'hx == 4'hx is x, not 1, and only the case-equality operators compare x as a value.
This is the start of data-driven testing: a script writes the file and the testbench reads it, which is the same idea that scales up to constrained-random sequences later. The last article of this section is about the three ways a testbench waits.