Implement generic MAX2771 raw Ethernet recorder
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
`timescale 1ns/1ps
|
||||
`default_nettype none
|
||||
|
||||
module tb_async_fifo_gray;
|
||||
localparam int DEPTH = 16;
|
||||
logic w_clk=0, r_clk=0, w_rst=1, r_rst=1;
|
||||
logic w_en=0, r_en=0;
|
||||
logic [15:0] w_data=0, r_data;
|
||||
logic w_full, r_empty, r_valid;
|
||||
logic [$clog2(DEPTH):0] w_level, r_level;
|
||||
logic [63:0] overflow_count, underflow_count;
|
||||
logic [15:0] expected [0:20000];
|
||||
integer writes=0, reads=0, errors=0;
|
||||
integer cycles;
|
||||
|
||||
always #3.5 w_clk = ~w_clk;
|
||||
always #5.5 r_clk = ~r_clk;
|
||||
|
||||
async_fifo_gray #(.DATA_WIDTH(16), .DEPTH(DEPTH)) dut (
|
||||
.w_clk(w_clk), .w_rst(w_rst), .w_en(w_en), .w_data(w_data),
|
||||
.w_full(w_full), .w_level(w_level), .overflow_attempt_count(overflow_count),
|
||||
.r_clk(r_clk), .r_rst(r_rst), .r_en(r_en), .r_data(r_data),
|
||||
.r_valid(r_valid), .r_empty(r_empty), .r_level(r_level),
|
||||
.underflow_attempt_count(underflow_count)
|
||||
);
|
||||
|
||||
always @(posedge w_clk) begin
|
||||
if (!w_rst && w_en && !w_full) begin
|
||||
expected[writes] = w_data;
|
||||
writes = writes + 1;
|
||||
end
|
||||
end
|
||||
always @(posedge r_clk) begin
|
||||
if (!r_rst && r_valid) begin
|
||||
if (r_data !== expected[reads]) begin
|
||||
$error("FIFO mismatch at read %0d: got %04x expected %04x", reads, r_data, expected[reads]);
|
||||
errors = errors + 1;
|
||||
end
|
||||
reads = reads + 1;
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
repeat (4) @(posedge w_clk);
|
||||
w_rst = 0;
|
||||
repeat (3) @(posedge r_clk);
|
||||
r_rst = 0;
|
||||
|
||||
// Fill through the full boundary and intentionally attempt overflow.
|
||||
@(negedge w_clk); w_en=1;
|
||||
repeat (DEPTH+8) begin
|
||||
@(negedge w_clk); w_data = w_data + 16'd1;
|
||||
end
|
||||
@(negedge w_clk); w_en=0;
|
||||
if (!w_full) $fatal(1, "FIFO did not assert full");
|
||||
|
||||
// Drain through empty and intentionally attempt underflow.
|
||||
@(negedge r_clk); r_en=1;
|
||||
repeat (DEPTH+12) @(negedge r_clk);
|
||||
@(negedge r_clk); r_en=0;
|
||||
repeat (4) @(posedge r_clk);
|
||||
if (!r_empty) $fatal(1, "FIFO did not assert empty");
|
||||
|
||||
// Random unrelated-clock traffic for wrap-around and simultaneous use.
|
||||
fork
|
||||
begin
|
||||
for (cycles=0; cycles<5000; cycles=cycles+1) begin
|
||||
@(negedge w_clk);
|
||||
w_en = ($urandom_range(0,99) < 68);
|
||||
w_data = w_data + 16'd1;
|
||||
end
|
||||
@(negedge w_clk); w_en=0;
|
||||
end
|
||||
begin
|
||||
repeat (5000) begin
|
||||
@(negedge r_clk);
|
||||
r_en = ($urandom_range(0,99) < 54);
|
||||
end
|
||||
@(negedge r_clk); r_en=0;
|
||||
end
|
||||
join
|
||||
|
||||
// Final drain.
|
||||
r_en=1;
|
||||
while (reads < writes) @(negedge r_clk);
|
||||
r_en=0;
|
||||
repeat (4) @(posedge r_clk);
|
||||
if (errors != 0) $fatal(1, "FIFO scoreboard saw %0d errors", errors);
|
||||
if (overflow_count == 0) $fatal(1, "overflow attempt counter stayed zero");
|
||||
if (underflow_count == 0) $fatal(1, "underflow attempt counter stayed zero");
|
||||
if (reads != writes) $fatal(1, "writes=%0d reads=%0d", writes, reads);
|
||||
$display("PASS tb_async_fifo_gray: %0d ordered words, overflow=%0d underflow=%0d",
|
||||
reads, overflow_count, underflow_count);
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#500000;
|
||||
$fatal(1, "FIFO test timeout");
|
||||
end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,49 @@
|
||||
`timescale 1ns/1ps
|
||||
`default_nettype none
|
||||
|
||||
module tb_capture_overflow;
|
||||
localparam int DEPTH=16;
|
||||
logic rf_clk=0, r_clk=0, rst=1;
|
||||
logic [3:0] rf_data=0;
|
||||
logic fifo_clk,w_en,w_full,r_en=0,r_valid,r_empty;
|
||||
logic [68:0] w_data,r_data;
|
||||
logic [$clog2(DEPTH):0] wl,rl;
|
||||
logic [63:0] seen,captured,dropped,ovf,udf;
|
||||
logic loss,activity;
|
||||
integer i,read_count=0;
|
||||
always #15.2735 rf_clk=~rf_clk;
|
||||
always #4 r_clk=~r_clk;
|
||||
|
||||
max2771_capture cap(.rf_clk_pmod(rf_clk),.rst(rst),.rf_data_pmod(rf_data),
|
||||
.fifo_full(w_full),.fifo_w_clk(fifo_clk),.fifo_w_en(w_en),.fifo_w_data(w_data),
|
||||
.samples_seen(seen),.samples_captured(captured),.samples_dropped(dropped),
|
||||
.loss_latched(loss),.sample_activity_pulse(activity));
|
||||
async_fifo_gray #(.DATA_WIDTH(69),.DEPTH(DEPTH)) fifo(
|
||||
.w_clk(fifo_clk),.w_rst(rst),.w_en(w_en),.w_data(w_data),.w_full(w_full),
|
||||
.w_level(wl),.overflow_attempt_count(ovf),.r_clk(r_clk),.r_rst(rst),
|
||||
.r_en(r_en),.r_data(r_data),.r_valid(r_valid),.r_empty(r_empty),
|
||||
.r_level(rl),.underflow_attempt_count(udf));
|
||||
|
||||
always @(posedge r_clk) if(r_valid) begin
|
||||
if(r_data[67:4]!==read_count || r_data[3:0]!==((read_count+1)&15))
|
||||
$fatal(1,"overwrite/order failure read=%0d entry index=%0d data=%x",read_count,r_data[67:4],r_data[3:0]);
|
||||
read_count=read_count+1;
|
||||
end
|
||||
|
||||
initial begin
|
||||
repeat(3) @(posedge rf_clk); rst=0;
|
||||
for(i=0;i<48;i=i+1) begin @(negedge rf_clk); rf_data=(i+1)&15; end
|
||||
@(posedge rf_clk); #1;
|
||||
if(!loss || dropped==0 || ovf==0 || dropped!==ovf)
|
||||
$fatal(1,"overflow status failure loss=%b dropped=%0d attempts=%0d",loss,dropped,ovf);
|
||||
r_en=1;
|
||||
wait(read_count==DEPTH);
|
||||
r_en=0;
|
||||
if(captured<DEPTH) $fatal(1,"captured count %0d smaller than FIFO depth",captured);
|
||||
$display("PASS tb_capture_overflow: no overwrite, dropped=%0d and loss latched",dropped);
|
||||
$finish;
|
||||
end
|
||||
initial begin #100000; $fatal(1,"capture overflow timeout"); end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,32 @@
|
||||
`timescale 1ns/1ps
|
||||
`default_nettype none
|
||||
|
||||
module tb_ethernet_crc32;
|
||||
logic [31:0] crc_in;
|
||||
logic [7:0] data_in;
|
||||
logic [31:0] crc_out;
|
||||
byte vector [0:8];
|
||||
integer i;
|
||||
|
||||
ethernet_crc32 dut (.crc_in(crc_in), .data_in(data_in), .crc_out(crc_out));
|
||||
|
||||
initial begin
|
||||
vector[0]="1"; vector[1]="2"; vector[2]="3"; vector[3]="4"; vector[4]="5";
|
||||
vector[5]="6"; vector[6]="7"; vector[7]="8"; vector[8]="9";
|
||||
crc_in = 32'hffffffff;
|
||||
data_in = 8'd0;
|
||||
#1;
|
||||
for (i = 0; i < 9; i = i + 1) begin
|
||||
data_in = vector[i];
|
||||
#1;
|
||||
crc_in = crc_out;
|
||||
#1;
|
||||
end
|
||||
if ((~crc_in) !== 32'hcbf43926)
|
||||
$fatal(1, "CRC check vector failed: got %08x expected cbf43926", ~crc_in);
|
||||
$display("PASS tb_ethernet_crc32: CRC-32('123456789') = cbf43926");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,100 @@
|
||||
`timescale 1ns/1ps
|
||||
`default_nettype none
|
||||
|
||||
module tb_ethernet_tx_mac;
|
||||
logic clk=0, rst=1;
|
||||
logic frame_start=0;
|
||||
logic [10:0] payload_length=11'd4;
|
||||
logic [7:0] payload_data;
|
||||
logic payload_valid, payload_ready;
|
||||
logic [7:0] gmii_txd;
|
||||
logic gmii_tx_en, gmii_tx_er, busy, frame_done;
|
||||
logic [63:0] frames, payload_bytes;
|
||||
byte payload [0:3];
|
||||
byte captured [0:255];
|
||||
byte body [0:59];
|
||||
integer payload_pos=0, cap_count=0, i;
|
||||
logic [31:0] reference_crc;
|
||||
|
||||
always #4 clk=~clk;
|
||||
assign payload_data = payload[payload_pos];
|
||||
assign payload_valid = (payload_pos < 4);
|
||||
|
||||
ethernet_tx_mac dut (
|
||||
.clk(clk), .rst(rst), .frame_start(frame_start), .payload_length(payload_length),
|
||||
.payload_data(payload_data), .payload_valid(payload_valid), .payload_ready(payload_ready),
|
||||
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
|
||||
.busy(busy), .frame_done(frame_done), .frames_transmitted(frames),
|
||||
.payload_bytes_transmitted(payload_bytes)
|
||||
);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!rst && gmii_tx_en) begin
|
||||
captured[cap_count] = gmii_txd;
|
||||
cap_count = cap_count + 1;
|
||||
if (gmii_tx_er) $fatal(1, "unexpected GMII TX error");
|
||||
end
|
||||
if (!rst && payload_ready && payload_valid)
|
||||
payload_pos <= payload_pos + 1;
|
||||
end
|
||||
|
||||
function automatic [31:0] crc_byte(input [31:0] cin, input [7:0] din);
|
||||
reg [31:0] c;
|
||||
integer b;
|
||||
begin
|
||||
c=cin;
|
||||
for (b=0;b<8;b=b+1)
|
||||
c=(c[0]^din[b]) ? ((c>>1)^32'hedb88320) : (c>>1);
|
||||
crc_byte=c;
|
||||
end
|
||||
endfunction
|
||||
|
||||
initial begin
|
||||
payload[0]=8'hde; payload[1]=8'had; payload[2]=8'hbe; payload[3]=8'hef;
|
||||
repeat(4) @(posedge clk); rst=0;
|
||||
@(posedge clk); frame_start=1;
|
||||
@(posedge clk); frame_start=0;
|
||||
wait(frame_done);
|
||||
@(posedge clk);
|
||||
|
||||
if (cap_count != 72) $fatal(1, "frame byte count %0d expected 72", cap_count);
|
||||
for (i=0;i<7;i=i+1) if(captured[i]!==8'h55) $fatal(1,"bad preamble byte %0d",i);
|
||||
if(captured[7]!==8'hd5) $fatal(1,"bad SFD");
|
||||
for (i=0;i<6;i=i+1) body[i]=8'hff;
|
||||
body[6]=8'h02; body[7]=8'h27; body[8]=8'h71; body[9]=8'h00; body[10]=8'h00; body[11]=8'h01;
|
||||
body[12]=8'h88; body[13]=8'hb5;
|
||||
body[14]=8'hde; body[15]=8'had; body[16]=8'hbe; body[17]=8'hef;
|
||||
for(i=18;i<60;i=i+1) body[i]=8'h00;
|
||||
for(i=0;i<60;i=i+1) if(captured[8+i]!==body[i])
|
||||
$fatal(1,"body mismatch byte %0d got %02x expected %02x",i,captured[8+i],body[i]);
|
||||
reference_crc=32'hffffffff;
|
||||
for(i=0;i<60;i=i+1) reference_crc=crc_byte(reference_crc,body[i]);
|
||||
reference_crc=~reference_crc;
|
||||
if(captured[68]!==reference_crc[7:0] || captured[69]!==reference_crc[15:8] ||
|
||||
captured[70]!==reference_crc[23:16] || captured[71]!==reference_crc[31:24])
|
||||
$fatal(1,"FCS mismatch got %02x%02x%02x%02x expected %08x",
|
||||
captured[71],captured[70],captured[69],captured[68],reference_crc);
|
||||
if(frames!==64'd1 || payload_bytes!==64'd4) $fatal(1,"MAC counters incorrect");
|
||||
|
||||
// Reset while idle, then send another complete frame. No TX_EN fragment
|
||||
// may appear during reset or before the new preamble.
|
||||
rst=1; payload_pos=0; cap_count=0;
|
||||
repeat(3) begin @(posedge clk); if(gmii_tx_en) $fatal(1,"TX_EN asserted during idle reset"); end
|
||||
rst=0;
|
||||
repeat(2) @(posedge clk);
|
||||
frame_start=1; @(posedge clk); frame_start=0;
|
||||
wait(frame_done); @(posedge clk);
|
||||
if(cap_count!=72) $fatal(1,"post-reset frame malformed: %0d bytes",cap_count);
|
||||
for(i=0;i<7;i=i+1) if(captured[i]!==8'h55) $fatal(1,"post-reset preamble byte %0d",i);
|
||||
reference_crc=32'hffffffff;
|
||||
for(i=0;i<60;i=i+1) reference_crc=crc_byte(reference_crc,body[i]);
|
||||
reference_crc=~reference_crc;
|
||||
if({captured[71],captured[70],captured[69],captured[68]}!==reference_crc)
|
||||
$fatal(1,"post-reset FCS mismatch");
|
||||
$display("PASS tb_ethernet_tx_mac: frame/padding/FCS/IFG and reset-between-frames verified");
|
||||
$finish;
|
||||
end
|
||||
initial begin #10000; $fatal(1,"MAC test timeout"); end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,125 @@
|
||||
`timescale 1ns/1ps
|
||||
`default_nettype none
|
||||
|
||||
module tb_recorder_stream;
|
||||
localparam int FIFO_DEPTH=8192;
|
||||
localparam int FRAME_BYTES=1526;
|
||||
logic w_clk=0, clk=0, w_rst=1, rst=1;
|
||||
logic w_en=0, w_full, r_en, r_valid, r_empty;
|
||||
logic [68:0] w_data=0, r_data;
|
||||
logic [$clog2(FIFO_DEPTH):0] w_level, r_level;
|
||||
logic [63:0] ovf, udf;
|
||||
logic frame_start, payload_valid, payload_ready, mac_busy, frame_done;
|
||||
logic [10:0] payload_length;
|
||||
logic [7:0] payload_data, gmii_txd;
|
||||
logic gmii_tx_en, gmii_tx_er;
|
||||
logic [31:0] packet_sequence;
|
||||
logic activity;
|
||||
logic [63:0] frames, payload_bytes;
|
||||
byte captured [0:4095];
|
||||
integer cap_count=0;
|
||||
integer produced=0;
|
||||
integer i, f, base, data_base;
|
||||
logic [31:0] crc_ref, fcs_seen;
|
||||
logic [31:0] seq_seen;
|
||||
logic [63:0] index_seen;
|
||||
logic [15:0] count_seen;
|
||||
|
||||
always #5 w_clk=~w_clk; // 100 MHz producer, unrelated to reader
|
||||
always #4 clk=~clk; // 125 MHz Ethernet
|
||||
|
||||
async_fifo_gray #(.DATA_WIDTH(69),.DEPTH(FIFO_DEPTH)) fifo (
|
||||
.w_clk(w_clk),.w_rst(w_rst),.w_en(w_en),.w_data(w_data),.w_full(w_full),
|
||||
.w_level(w_level),.overflow_attempt_count(ovf),.r_clk(clk),.r_rst(rst),
|
||||
.r_en(r_en),.r_data(r_data),.r_valid(r_valid),.r_empty(r_empty),
|
||||
.r_level(r_level),.underflow_attempt_count(udf)
|
||||
);
|
||||
ethernet_packetizer #(.FIFO_DEPTH(FIFO_DEPTH),.FULL_SAMPLES(2936)) packetizer (
|
||||
.clk(clk),.rst(rst),.link_ready(1'b1),.dropped_samples_low(32'd5),
|
||||
.fifo_empty(r_empty),.fifo_level(r_level),.fifo_rd_en(r_en),
|
||||
.fifo_rd_data(r_data),.fifo_rd_valid(r_valid),.mac_frame_start(frame_start),
|
||||
.mac_payload_length(payload_length),.mac_payload_data(payload_data),
|
||||
.mac_payload_valid(payload_valid),.mac_payload_ready(payload_ready),
|
||||
.mac_busy(mac_busy),.mac_frame_done(frame_done),
|
||||
.packet_sequence(packet_sequence),.frame_activity_pulse(activity)
|
||||
);
|
||||
ethernet_tx_mac mac (
|
||||
.clk(clk),.rst(rst),.frame_start(frame_start),.payload_length(payload_length),
|
||||
.payload_data(payload_data),.payload_valid(payload_valid),.payload_ready(payload_ready),
|
||||
.gmii_txd(gmii_txd),.gmii_tx_en(gmii_tx_en),.gmii_tx_er(gmii_tx_er),
|
||||
.busy(mac_busy),.frame_done(frame_done),.frames_transmitted(frames),
|
||||
.payload_bytes_transmitted(payload_bytes)
|
||||
);
|
||||
|
||||
always @(posedge clk) if (!rst && gmii_tx_en) begin
|
||||
captured[cap_count]=gmii_txd;
|
||||
cap_count=cap_count+1;
|
||||
if(gmii_tx_er) $fatal(1,"GMII underflow/error in stream test");
|
||||
end
|
||||
|
||||
function automatic [31:0] crc_byte(input [31:0] cin,input [7:0] din);
|
||||
reg[31:0] c; integer b;
|
||||
begin c=cin; for(b=0;b<8;b=b+1)
|
||||
c=(c[0]^din[b])?((c>>1)^32'hedb88320):(c>>1); crc_byte=c; end
|
||||
endfunction
|
||||
|
||||
initial begin
|
||||
repeat(5) @(posedge w_clk); w_rst=0;
|
||||
repeat(5) @(posedge clk); rst=0;
|
||||
// Supply exactly two contiguous normal frames plus CDC slack.
|
||||
for(produced=0;produced<5872;produced=produced+1) begin
|
||||
@(negedge w_clk);
|
||||
while(w_full) @(negedge w_clk);
|
||||
w_en=1;
|
||||
w_data={1'b0,64'(produced),4'(produced & 15)};
|
||||
end
|
||||
@(negedge w_clk); w_en=0;
|
||||
wait(packet_sequence==32'd2);
|
||||
repeat(4) @(posedge clk);
|
||||
if(cap_count!=2*FRAME_BYTES) $fatal(1,"captured %0d bytes expected %0d",cap_count,2*FRAME_BYTES);
|
||||
|
||||
for(f=0;f<2;f=f+1) begin
|
||||
base=f*FRAME_BYTES;
|
||||
for(i=0;i<7;i=i+1) if(captured[base+i]!==8'h55) $fatal(1,"frame %0d preamble",f);
|
||||
if(captured[base+7]!==8'hd5 || captured[base+20]!==8'h88 || captured[base+21]!==8'hb5)
|
||||
$fatal(1,"frame %0d L2 header",f);
|
||||
if(captured[base+22]!=="M" || captured[base+23]!=="X" ||
|
||||
captured[base+24]!=="2" || captured[base+25]!=="7") $fatal(1,"frame %0d magic",f);
|
||||
if(captured[base+26]!==8'd1 || captured[base+28]!==8'd0 || captured[base+29]!==8'd32)
|
||||
$fatal(1,"frame %0d protocol constants",f);
|
||||
seq_seen={captured[base+30],captured[base+31],captured[base+32],captured[base+33]};
|
||||
index_seen={captured[base+34],captured[base+35],captured[base+36],captured[base+37],
|
||||
captured[base+38],captured[base+39],captured[base+40],captured[base+41]};
|
||||
count_seen={captured[base+42],captured[base+43]};
|
||||
if(seq_seen!==f || index_seen!==64'(f*2936) || count_seen!==16'd2936)
|
||||
$fatal(1,"frame %0d metadata seq=%0d index=%0d count=%0d",f,seq_seen,index_seen,count_seen);
|
||||
if(captured[base+44]!==8'd4 || captured[base+45]!==8'd1)
|
||||
$fatal(1,"frame %0d packing metadata",f);
|
||||
// First frame carries reset+link flags; second carries none.
|
||||
if((f==0 && captured[base+27]!==8'h07) || (f==1 && captured[base+27]!==8'h00))
|
||||
$fatal(1,"frame %0d flags %02x",f,captured[base+27]);
|
||||
if ({captured[base+46],captured[base+47],captured[base+48],captured[base+49]} !== 32'd5)
|
||||
$fatal(1,"frame %0d dropped-count header",f);
|
||||
data_base=base+54;
|
||||
for(i=0;i<2936;i=i+1) begin
|
||||
if((i&1)==0) begin
|
||||
if(captured[data_base+(i>>1)][7:4]!==((f*2936+i)&15))
|
||||
$fatal(1,"frame %0d sample %0d high nibble",f,i);
|
||||
end else if(captured[data_base+(i>>1)][3:0]!==((f*2936+i)&15))
|
||||
$fatal(1,"frame %0d sample %0d low nibble",f,i);
|
||||
end
|
||||
crc_ref=32'hffffffff;
|
||||
for(i=8;i<1522;i=i+1) crc_ref=crc_byte(crc_ref,captured[base+i]);
|
||||
crc_ref=~crc_ref;
|
||||
fcs_seen={captured[base+1525],captured[base+1524],captured[base+1523],captured[base+1522]};
|
||||
if(fcs_seen!==crc_ref) $fatal(1,"frame %0d FCS got %08x expected %08x",f,fcs_seen,crc_ref);
|
||||
end
|
||||
if(frames!==64'd2 || payload_bytes!==64'd3000 || udf!==64'd0)
|
||||
$fatal(1,"counter failure frames=%0d payload=%0d underflow=%0d",frames,payload_bytes,udf);
|
||||
$display("PASS tb_recorder_stream: two full frames, 5872 samples, headers/index/sequence/FCS verified");
|
||||
$finish;
|
||||
end
|
||||
initial begin #1000000; $fatal(1,"stream test timeout"); end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
Reference in New Issue
Block a user