Initialisation
At time zero every reg is x, and the simulator carries x through every gate and mux honestly. Leave one input undriven and x spreads like ink in water, and the failure is quiet: the run finishes, the checker prints FAIL on everything, or, with the wrong comparison operator, PASS on everything because it stopped comparing anything real.
One undriven input
module mux4to1 (
input wire [3:0] d,
input wire [1:0] sel,
output reg y
);
always @(*) begin
case (sel)
2'b00: y = d[0];
2'b01: y = d[1];
2'b10: y = d[2];
2'b11: y = d[3];
endcase
end
endmodule
module tb_mux4to1();
reg [3:0] d;
reg [1:0] sel;
wire y;
mux4to1 dut (.d(d), .sel(sel), .y(y));
initial begin
d = 4'b1010; // sel is never driven
#10;
$display("sel=%b d=%b y=%b", sel, d, y);
$finish;
end
endmodule
sel=xx d=1010 y=x
The mux does not know which input to pick, so the output is x. A checker using !== reports a failure; one using != gets x from the comparison, treats it as false, and reports a pass. Either way the result means nothing. When a waveform is full of x, or a testbench fails no matter what the design does, check that every input is driven before the first edge before you look at the design.
The order
- Declare every driven signal as
reg, with an inline value as a backstop:reg [7:0] data = 0;. - At the top of the
initialblock, assign every input its idle value explicitly, reset asserted. - Let the clock run; the design ignores it while in reset.
- Hold reset for
RESET_CYCLESedges. - Release it 2 ns after an edge.
- Wait one or two more edges for state machines to reach idle and registered outputs to show their reset values.
- Then, and only then, start the tests.
Inline and procedural
reg clk = 0; // inline
reg clk;
initial clk = 0; // procedural
Both give the value before any event runs. The procedural form is easier to audit, because the whole start-up is in one block at the top of the file; the inline form is a backstop in case an assignment is later dropped from that block. Use both:
reg [7:0] data = 8'h00;
reg valid = 1'b0;
initial begin
data = 8'h00;
valid = 1'b0;
// ...
end
Complete
`timescale 1ns / 1ps
module tb_reg8();
parameter CLK_PERIOD = 10;
parameter RESET_CYCLES = 4;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg [7:0] data_in = 8'h00;
reg wr_en = 1'b0;
wire [7:0] data_out;
reg8 dut (.clk(clk), .rst_n(rst_n), .data_in(data_in), .wr_en(wr_en), .data_out(data_out));
always #(CLK_PERIOD/2) clk = ~clk;
initial begin
$dumpfile("tb_reg8.vcd");
$dumpvars(0, tb_reg8);
rst_n = 1'b0; // idle values, reset asserted
data_in = 8'h00;
wr_en = 1'b0;
repeat (RESET_CYCLES) @(posedge clk); // hold
#2;
rst_n = 1'b1; // release
@(posedge clk); // settle
@(posedge clk);
$display("ready at %0t ns, data_out=%h", $time, data_out);
@(posedge clk); #2; // write 0xAB
data_in = 8'hAB;
wr_en = 1'b1;
@(posedge clk); #2;
wr_en = 1'b0;
@(posedge clk);
$display("after write: data_out=%h (expected AB)", data_out);
rst_n = 1'b0; // reset clears it
@(posedge clk);
#2;
rst_n = 1'b1;
@(posedge clk);
$display("after reset: data_out=%h (expected 00)", data_out);
$finish;
end
endmodule
Driving x on purpose
Sometimes you want an x on an input, to see what the design does with an illegal or unknown value:
signal = 1'bx;
bus = {8{1'bx}};
A mode input that allows 00, 01 and 10 should be tried at 11, and at xx:
@(posedge clk); #2;
mode = 2'b11;
@(posedge clk);
$display("illegal mode: error_flag=%b (expected 1)", dut.error_flag);
@(posedge clk); #2;
mode = 2'bxx;
@(posedge clk);
$display("unknown mode: error_flag=%b", dut.error_flag);
An x out for an x in may be acceptable. A design that stays corrupted afterwards, on valid inputs, is a bug to find here rather than on a board.
x in a printout
reg [7:0] val = 8'hxx;
$display("%b", val); // xxxxxxxx
$display("%h", val); // xx
$display("%d", val); // x
reg [7:0] val2 = 8'b1010_xx10;
$display("%b", val2); // 1010xx10
$display("%h", val2); // ax
Learn to see an x in output as a pointer: something was not initialised, or the design drove something undefined, and both are worth chasing.
The last article of this section is the case where the design has two clocks and the testbench has to know which one governs which signal.