Implement generic MAX2771 raw Ethernet recorder

This commit is contained in:
onur
2026-08-01 01:15:47 +03:00
commit 79d07fdda7
57 changed files with 4810 additions and 0 deletions
+103
View File
@@ -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
+49
View File
@@ -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
+32
View File
@@ -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
+100
View File
@@ -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
+125
View File
@@ -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