I have a Verilog test bench that monitors a 64-bit bus and should randomly schedule a flipped bit (packet corruption) to happen every 1-in-X packets. I was surprised to find it not injecting any corruption, and seemingly this is due to patterns in $random ?
Verilog module, placed inline with bus:
module test_cable #(
parameter EMPTY_BITS = 3,
parameter DATA_BITS = 64
) (
input wire clk,
input wire [DATA_BITS-1:0] eth_c2cable_data,
input wire eth_c2cable_valid,
input wire eth_c2cable_sop,
input wire [EMPTY_BITS-1:0] eth_c2cable_empty,
input wire eth_c2cable_eop,
input wire eth_c2cable_error,
output wire eth_c2cable_ready,
output wire [DATA_BITS-1:0] eth_cable2s_data,
output wire eth_cable2s_valid,
output wire eth_cable2s_sop,
output wire [EMPTY_BITS-1:0] eth_cable2s_empty,
output wire eth_cable2s_eop,
output wire eth_cable2s_error,
input wire eth_cable2s_ready
);
// Corruption control task
// 2'b00 clean
// 2'b01 corrupt 1 pkt in 2^6
// 2'b10 corrupt 1 pkt in 2^7
// 2'b11 corrupt 1 pkt in 2^8
reg [1:0] ready_control = 2'b00;
reg [31:0] random_next = 0;
task setCorruptControl;
input [1:0] bitf;
begin
ready_control <= bitf;
end
endtask
// ready bit control
reg [7:0] corrupt_at_cycle = 0;
wire [31:0] random_mask = (1'b1 << (6+ready_control))-1;
always @(posedge clk) begin
random_next <= $random;
if ((eth_c2cable_sop && eth_c2cable_valid) && ready_control > 0 && (random_next & random_mask) == 32'b0) begin
// corrupt this packet at some point. we don't know how big packet is, so somewhere in first 16 cycles
// if the packet stops before we corrupt, we'll hit the next one
corrupt_at_cycle <= random_next[24 +: 4];
$display("Scheduling corruption %t for +%d cycles %08x", $time, random_next[24 +: 4], random_next);
end else if (corrupt_at_cycle > 0 && eth_c2cable_valid) begin
corrupt_at_cycle <= corrupt_at_cycle - 1'b1;
end
if (eth_c2cable_corruption != 0) begin
$display("Corrupting wire packet with noise %016x", eth_c2cable_corruption);
end
end
// corrupt by flipping single bit in this cycle
wire [63:0] eth_c2cable_corruption = corrupt_at_cycle == 1 ? { 1'b1 << random_next[24 +: 5] } : 64'b0;
assign eth_cable2s_data = eth_c2cable_data ^ eth_c2cable_corruption;
assign eth_cable2s_valid = eth_c2cable_valid;
assign eth_cable2s_sop = eth_c2cable_sop;
assign eth_cable2s_eop = eth_c2cable_eop;
assign eth_cable2s_empty = eth_c2cable_empty;
assign eth_cable2s_error = eth_c2cable_error;
assign eth_c2cable_ready = eth_cable2s_ready;
endmodule
Looking at the simulation output corruption is always scheduled to occur in zero cycles, so never happens:
...
Scheduling corruption 882531000 for + 0 cycles 40002780
Scheduling corruption 887190000 for + 0 cycles 002edf00
Scheduling corruption 894192000 for + 0 cycles 4049c380
Scheduling corruption 907395000 for + 0 cycles c0270a80
Scheduling corruption 948746000 for + 0 cycles 4010dd80
Scheduling corruption 959389000 for + 0 cycles 40155b80
Scheduling corruption 964957000 for + 0 cycles c0357a80
...
^ ^
This seems to be caused by bits random_next[24 +: 4] and random_next[0 +: 6] being zero coincidently. I arbitrarily chose the bits from 24 hoping they would have no correlation with the low bits. Is this a weakness with the Verilog pseudo-random number algorithm, or a problem with the Icarus Verilog simulator?
After much bug-hunting, I modified occurrences of random_next[24 +: 4]
to random_next[28 +: 4]
and the test bench works.
2 Answers 2
I have written a little testbench that demonstrates the problem stand-alone:
module testbench;
integer i, j = 0;
reg [31:0] randval;
initial begin
for (i = 0; i < 100000; i = i+1) begin
randval = $random;
if (randval[0 +: 6] == 0) begin
$display("%8d %6d: %x -> %d %d", i, j, randval, randval[0 +: 6], randval[24 +: 4]);
if (randval[24 +: 4] != 0) $finish;
j = j + 1;
end
end
$display("IEEE Std 1364-2005 $random (rtl_dist_uniform(seed, LONG_MIN, LONG_MAX)) sucks!");
end
endmodule
The really interesting thing here is that the exact behavior of $random is defined in IEEE Std 1364-2005. There is actually C code in the standard (pages 317 and 318). I don't want to quote it here in full, but a copy of the code can be found for example in Icarus Verilog:
https://github.com/steveicarus/iverilog/blob/master/vpi/sys_random.c
Calling $random in Verilog equals to calling rtl_dist_uniform(&a_seed, INT_MIN, INT_MAX)
in this C file.
So what you demonstrated here is not only a weakness in $random of just one particular simulator, but a weakness in $random in general.
One solution would be to simply implement your own random number generator. I often use the Xorshift family of random number generators because they work fairly well and are easy to implement:
http://en.wikipedia.org/wiki/Xorshift
The following example uses a 64 bit xorshift to generate 32 bit numbers:
module testbench;
reg [63:0] xorshift64_state = 64'd88172645463325252;
task xorshift64_next;
begin
// see page 4 of Marsaglia, George (July 2003). "Xorshift RNGs". Journal of Statistical Software 8 (14).
xorshift64_state = xorshift64_state ^ (xorshift64_state << 13);
xorshift64_state = xorshift64_state ^ (xorshift64_state >> 7);
xorshift64_state = xorshift64_state ^ (xorshift64_state << 17);
end
endtask
integer i;
initial begin
for (i = 0; i < 100; i = i+1) begin
xorshift64_next;
$display("%x", xorshift64_state[31:0]);
end
end
endmodule
-
\$\begingroup\$ Thanks for the succinct testbench and confirmation! I observed the same behaviour in ModelSim and agree the weakness is in $random as defined in the Verilog spec. Thanks for the xorshit64 module, I'll give that a go - clearly fuzz testing with vanilla $random could have subtle bugs. \$\endgroup\$shuckc– shuckc2014年01月17日 14:40:24 +00:00Commented Jan 17, 2014 at 14:40
-
\$\begingroup\$ I was going to raise a Mantis bug report for this but it's not clear who to contact. Anyone on the IEEE P1800 Working Group? \$\endgroup\$Chiggs– Chiggs2014年01月28日 16:14:45 +00:00Commented Jan 28, 2014 at 16:14
Worth mentioning I have found Icarus Verilog includes a non-standard random number generator as a built-in:
$mti_random()
$mti_dist_uniform
These functions are similar to the IEEE1364 standard $random
functions, but they use the Mersenne Twister (MT19937)
algorithm. This is considered an excellent random number
generator, but does not generate the same sequence as the
standardized $random.