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.