3
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

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
\$\endgroup\$
2
  • \$\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
    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
    Jan 28, 2014 at 16:14
0
\$\begingroup\$

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.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.