`timescale 1ns / 1ps
module tb_fpga_core_udp;
// clocks & resets
reg clk = 0;
reg rst = 1;
reg qsfp_tx_clk_1 = 0;
reg qsfp_rx_clk_1 = 0;
reg qsfp_tx_rst_1 = 1;
reg qsfp_rx_rst_1 = 1;
// XGMII inputs to DUT (default idle)
reg [63:0] qsfp_rxd_1 = 64'h0707070707070707;
reg [7:0] qsfp_rxc_1 = 8'hFF;
// XGMII outputs from DUT
wire [63:0] qsfp_txd_1;
wire [7:0] qsfp_txc_1;
// other lanes idle
reg qsfp_tx_clk_2 = 0, qsfp_rx_clk_2 = 0, qsfp_tx_rst_2 = 1, qsfp_rx_rst_2 = 1;
reg [63:0] qsfp_rxd_2 = 64'h0707070707070707;
reg [7:0] qsfp_rxc_2 = 8'hFF;
wire [63:0] qsfp_txd_2;
wire [7:0] qsfp_txc_2;
reg qsfp_tx_clk_3 = 0, qsfp_rx_clk_3 = 0, qsfp_tx_rst_3 = 1, qsfp_rx_rst_3 = 1;
reg [63:0] qsfp_rxd_3 = 64'h0707070707070707;
reg [7:0] qsfp_rxc_3 = 8'hFF;
wire [63:0] qsfp_txd_3;
wire [7:0] qsfp_txc_3;
reg qsfp_tx_clk_4 = 0, qsfp_rx_clk_4 = 0, qsfp_tx_rst_4 = 1, qsfp_rx_rst_4 = 1;
reg [63:0] qsfp_rxd_4 = 64'h0707070707070707;
reg [7:0] qsfp_rxc_4 = 8'hFF;
wire [63:0] qsfp_txd_4;
wire [7:0] qsfp_txc_4;
reg [7:0] pkt [0:63];
// 156.25 MHz clocks
always #3.2 clk = ~clk;
always #3.2 qsfp_tx_clk_1 = ~qsfp_tx_clk_1;
always #3.2 qsfp_rx_clk_1 = ~qsfp_rx_clk_1;
// DUT
fpga_core UUT (
.clk(clk),
.rst(rst),
.qsfp_tx_clk_1(qsfp_tx_clk_1),
.qsfp_tx_rst_1(qsfp_tx_rst_1),
.qsfp_txd_1(qsfp_txd_1),
.qsfp_txc_1(qsfp_txc_1),
.qsfp_rx_clk_1(qsfp_rx_clk_1),
.qsfp_rx_rst_1(qsfp_rx_rst_1),
.qsfp_rxd_1(qsfp_rxd_1),
.qsfp_rxc_1(qsfp_rxc_1),
.qsfp_tx_clk_2(qsfp_tx_clk_2),
.qsfp_tx_rst_2(qsfp_tx_rst_2),
.qsfp_txd_2(qsfp_txd_2),
.qsfp_txc_2(qsfp_txc_2),
.qsfp_rx_clk_2(qsfp_rx_clk_2),
.qsfp_rx_rst_2(qsfp_rx_rst_2),
.qsfp_rxd_2(qsfp_rxd_2),
.qsfp_rxc_2(qsfp_rxc_2),
.qsfp_tx_clk_3(qsfp_tx_clk_3),
.qsfp_tx_rst_3(qsfp_tx_rst_3),
.qsfp_txd_3(qsfp_txd_3),
.qsfp_txc_3(qsfp_txc_3),
.qsfp_rx_clk_3(qsfp_rx_clk_3),
.qsfp_rx_rst_3(qsfp_rx_rst_3),
.qsfp_rxd_3(qsfp_rxd_3),
.qsfp_rxc_3(qsfp_rxc_3),
.qsfp_tx_clk_4(qsfp_tx_clk_4),
.qsfp_tx_rst_4(qsfp_tx_rst_4),
.qsfp_txd_4(qsfp_txd_4),
.qsfp_txc_4(qsfp_txc_4),
.qsfp_rx_clk_4(qsfp_rx_clk_4),
.qsfp_rx_rst_4(qsfp_rx_rst_4),
.qsfp_rxd_4(qsfp_rxd_4),
.qsfp_rxc_4(qsfp_rxc_4)
);
// -----------------------------
//
// -----------------------------
integer f_xgmii, f_axis;
initial begin
f_xgmii = $fopen("xgmii_rx_log.txt","w");
if (!f_xgmii) begin
$display("ERROR: cannot open xgmii_rx_log.txt");
$stop;
end
f_axis = $fopen("axis_rx_log.txt","w");
if (!f_axis) begin
$display("ERROR: cannot open axis_rx_log.txt");
$stop;
end
end
always @(posedge qsfp_rx_clk_1) begin
$fwrite(f_xgmii, "%0t ns | rxc=%02x rxd=%016h\n",
$time, qsfp_rxc_1, qsfp_rxd_1);
end
//
//
always @(posedge clk) begin
$fwrite(f_axis, "%0t ns | tvalid=%b tready=%b tlast=%b tuser=%b tkeep=%02x tdata=%016h\n",
$time,
UUT.rx_axis_tvalid,
UUT.rx_axis_tready,
UUT.rx_axis_tlast,
UUT.rx_axis_tuser,
UUT.rx_axis_tkeep,
UUT.rx_axis_tdata);
end
// -----------------------------
// IPv4 header checksum(
// -----------------------------
task automatic ipv4_header_checksum_calc;
inout [7:0] pkt [0:63];
integer j;
integer sum;
begin
sum = 0;
pkt[24] = 8'h00; pkt[25] = 8'h00; // 先清零
for (j = 14; j < 34; j = j+2) begin
sum = sum + {pkt[j], pkt[j+1]};
end
// 折返進位
while (sum >> 16)
sum = (sum & 16'hFFFF) + (sum >> 16);
sum = ~sum & 16'hFFFF;
pkt[24] = sum[15:8];
pkt[25] = sum[7:0];
end
endtask
// -----------------------------
// UDP over IPv4
// -----------------------------
task automatic send_udp_frame;
integer i;
integer payload_len;
integer ip_tot_len;
integer udp_len;
integer length; // 整個 Ethernet payload 長度(IP header + UDP header + payload)
integer rem; // 最後一拍剩餘 bytes
reg [63:0] w;
reg [7:0] c;
begin
//
for (i = 0; i < 64; i = i+1) pkt[i] = 8'h00;
// Ethernet header
{pkt[0],pkt[1],pkt[2],pkt[3],pkt[4],pkt[5]} = 48'h02_00_00_00_00_00; // Dst MAC
{pkt[6],pkt[7],pkt[8],pkt[9],pkt[10],pkt[11]} = 48'h5A_51_52_53_54_55; // Src MAC
pkt[12] = 8'h08; pkt[13] = 8'h00; // Ethertype IPv4
// Payload
payload_len = 4;
pkt[42] = "H";
pkt[43] = "I";
pkt[44] = "!";
pkt[45] = 8'h00;
// IPv4 header
ip_tot_len = 20 + 8 + payload_len; // IP header (20) + UDP header (8) + payload
pkt[14] = 8'h45; pkt[15] = 8'h00; // Version/IHL, DSCP/ECN
pkt[16] = ip_tot_len[15:8]; pkt[17] = ip_tot_len[7:0]; // Total length
pkt[18] = 8'h00; pkt[19] = 8'h01; // ID
pkt[20] = 8'h00; pkt[21] = 8'h00; // Flags/Fragment
pkt[22] = 8'h40; pkt[23] = 8'h11; // TTL=64, Protocol=UDP(17)
pkt[24] = 8'h00; pkt[25] = 8'h00; // Header checksum (先清零,待計算)
pkt[26] = 8'hC0; pkt[27] = 8'hA8; pkt[28] = 8'h01; pkt[29] = 8'h01; // Src IP 192.168.1.1
pkt[30] = 8'hC0; pkt[31] = 8'hA8; pkt[32] = 8'h01; pkt[33] = 8'h80; // Dst IP 192.168.1.128
// UDP header
udp_len = 8 + payload_len;
pkt[34] = 8'h04; pkt[35] = 8'hD2; // Src port 1234
pkt[36] = 8'h04; pkt[37] = 8'hD2; // Dst port 1234
pkt[38] = udp_len[15:8]; pkt[39] = udp_len[7:0]; // UDP length
pkt[40] = 8'h00; pkt[41] = 8'h00; // UDP checksum=0(允許)
//
ipv4_header_checksum_calc(pkt);
// Ethernet payload
length = 14 + ip_tot_len - 14; // 就是 ip_tot_len(Ethernet payload=IP封包)
length = 20 + 8 + payload_len; // 等同 ip_tot_len,保險起見再指定
// ====================
//+ 6x55 + D5
qsfp_rxd_1 <= {8'hD5, 8'h55, 8'h55, 8'h55, 8'h55, 8'h55, 8'h55, 8'hFB};
qsfp_rxc_1 <= 8'b00000001; // lane0 control
@(posedge qsfp_rx_clk_1);
//
i = 0;
while (length - i >= 8) begin
qsfp_rxd_1 <= {pkt[i+7], pkt[i+6], pkt[i+5], pkt[i+4],
pkt[i+3], pkt[i+2], pkt[i+1], pkt[i]};
qsfp_rxc_1 <= 8'h00;
i = i + 8;
@(posedge qsfp_rx_clk_1);
end
//rem bytes + Terminate + Idle
rem = length - i;
w = 64'h0707070707070707; //
c = 8'hFF; //
case (rem)
0: begin
w[7:0] = 8'hFD; // lane0 terminate
end
1: begin
w[7:0] = pkt[i];
w[15:8] = 8'hFD;
c = 8'b11111110;
end
2: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = 8'hFD;
c = 8'b11111100;
end
3: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = pkt[i+2];
w[31:24] = 8'hFD;
c = 8'b11111000;
end
4: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = pkt[i+2];
w[31:24] = pkt[i+3];
w[39:32] = 8'hFD;
c = 8'b11110000;
end
5: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = pkt[i+2];
w[31:24] = pkt[i+3];
w[39:32] = pkt[i+4];
w[47:40] = 8'hFD;
c = 8'b11100000;
end
6: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = pkt[i+2];
w[31:24] = pkt[i+3];
w[39:32] = pkt[i+4];
w[47:40] = pkt[i+5];
w[55:48] = 8'hFD;
c = 8'b11000000;
end
7: begin
w[7:0] = pkt[i];
w[15:8] = pkt[i+1];
w[23:16] = pkt[i+2];
w[31:24] = pkt[i+3];
w[39:32] = pkt[i+4];
w[47:40] = pkt[i+5];
w[55:48] = pkt[i+6];
w[63:56] = 8'hFD;
c = 8'b10000000;
end
endcase
qsfp_rxd_1 <= w;
qsfp_rxc_1 <= c;
@(posedge qsfp_rx_clk_1);
// Idle
qsfp_rxd_1 <= 64'h0707070707070707;
qsfp_rxc_1 <= 8'hFF;
@(posedge qsfp_rx_clk_1);
qsfp_rxd_1 <= 64'h0707070707070707;
qsfp_rxc_1 <= 8'hFF;
@(posedge qsfp_rx_clk_1);
end
endtask
// -----------------------------
// Test sequence
// -----------------------------
initial begin
// 上電:保持 reset
rst = 1;
qsfp_rx_rst_1 = 1;
qsfp_tx_rst_1 = 1;
//
repeat (10) @(posedge clk);
rst = 0; //
//XGMII resets
repeat (4) @(posedge qsfp_rx_clk_1); qsfp_rx_rst_1 = 0;
repeat (4) @(posedge qsfp_tx_clk_1); qsfp_tx_rst_1 = 0;
//
repeat (200) @(posedge qsfp_rx_clk_1);
//
// wait (UUT.rx_axis_tready === 1'b1);
//
send_udp_frame();
//
repeat (2000) @(posedge clk);
//
$fclose(f_xgmii);
$fclose(f_axis);
$stop;
end
endmodule
這個模組有哪裡需要改進的嗎