/* * tb.sv * * Copyright (C) 2025 Private Island Networks Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * function: test bench for Betsy * * Notes: * * RX data is from the TB to the DUT * TX data is from the DUT to the TB * */ `timescale 1ns / 1ps //`define TEST_ETOE `define ML_ENGINE //`define TEST_CONTROLLER `define INCLUDED `include "../../../../../src/rgmii_params.v" `include "../../../../../src/ethernet_params.v" `undef INCLUDED module tb; parameter NUM_PHYS = 3; // System clocks parameter PERIOD_CLK_25 = 40; // 25 MHz parameter PERIOD_CLK_125 = 8; // 125 MHz parameter PERIOD_CLK_250 = 4; // 250 MHz to support RGMII DDR localparam IDLE_SHIFT = 4; // Used to multiple the specified # of idle clocks `ifdef ML_ENGINE localparam RX0_CLK_CNT_START = 'd400; localparam RX1_CLK_CNT_START = 'd4000_0000; localparam RX2_CLK_CNT_START = 'd4000_0000; `else localparam RX0_CLK_CNT_START = 'd400; localparam RX1_CLK_CNT_START = 'd400; localparam RX2_CLK_CNT_START = 'd400; `endif // FPGA I/O reg rstn; reg clk_125, clk_25, clk_phy, clk_phyx2; wire phy0_rx_clk, phy1_rx_clk, phy2_rx_clk; reg phy0_rx_ctl, phy1_rx_ctl, phy2_rx_ctl; reg [3:0] phy0_rx_d, phy1_rx_d, phy2_rx_d; wire phy0_tx_clk, phy1_tx_clk, phy2_tx_clk; wire phy0_tx_ctl, phy1_tx_ctl, phy2_tx_ctl; wire [3:0] phy0_tx_d, phy1_tx_d, phy2_tx_d; wire phy0_mdc, phy0_mdio, phy0_resetn, phy0_intn; wire phy1_mdc, phy1_mdio, phy1_resetn, phy1_intn; wire phy2_mdc, phy2_mdio, phy2_resetn, phy2_intn; wire [2:0] led; // sim only I/O wire pclk; wire pll_lock; wire [2:0] phy_up; betsy dut( .rstn(rstn), .clk_i(clk_25), .phy0_clk(clk_25), .phy1_clk(), // Sim Only .pll_locked_o(pll_lock), .pclk_o(pclk), .phy_up_o(phy_up), .phy0_rstn(phy0_resetn), .phy0_rx_clk(phy0_rx_clk), .phy0_rx_ctl(phy0_rx_ctl), .phy0_rx_d(phy0_rx_d), .phy0_tx_clk(phy0_tx_clk), .phy0_tx_ctl(phy0_tx_ctl), .phy0_tx_d(phy0_tx_d), .phy0_mdc(phy0_mdc), .phy0_mdio(phy0_mdio), .phy0_intn(phy0_intn), .phy1_rstn(phy1_resetn), .phy1_rx_clk(phy1_rx_clk), .phy1_rx_ctl(phy1_rx_ctl), .phy1_rx_d(phy1_rx_d), .phy1_tx_clk(phy1_tx_clk), .phy1_tx_ctl(phy1_tx_ctl), .phy1_tx_d(phy1_tx_d), .phy1_mdc(phy1_mdc), .phy1_mdio(phy1_mdio), .phy1_intn(phy1_intn), .phy2_rstn(phy2_resetn), .phy2_rx_clk(phy2_rx_clk), .phy2_rx_ctl(phy2_rx_ctl), .phy2_rx_d(phy2_rx_d), .phy2_tx_clk(phy2_tx_clk), .phy2_tx_ctl(phy2_tx_ctl), .phy2_tx_d(phy2_tx_d), .phy2_mdc(phy2_mdc), .phy2_mdio(phy2_mdio), .phy2_intn(phy2_intn), .flash_clk(), .flash_dqs(), .flash_seln(), .flash_d(), .fpga_led(led) ); reg [23:0] rx_clk_cnt; reg [23:0] rx0_clk_cnt_start, rx1_clk_cnt_start, rx2_clk_cnt_start; reg [13:0] rx0_idle_cnt, rx1_idle_cnt, rx2_idle_cnt; reg [13:0] rx0_data_cnt, rx1_data_cnt, rx2_data_cnt; reg rx0_last_byte, rx1_last_byte, rx2_last_byte; reg [8:0] rx0_d[0:16383]; // 2**14 reg [8:0] rx1_d[0:16383]; // reg [8:0] rx2_d[0:16383]; // `ifdef ML_ENGINE initial begin $readmemh("../data/cont_mle_w.dat",rx0_d); $display("[%0t ns] ==INFO== Load memory from file for rx0: %0s.", $time, "cont_mle_w.dat"); end `endif `ifdef TEST_ETOE initial begin $readmemh("../data/etoe0.dat",rx0_d); $display("[%0t ns] ==INFO== Load memory from file for rx0: %0s.", $time, "etoe0.dat"); $readmemh("../data/etoe1.dat",rx1_d); $display("[%0t ns] ==INFO== Load memory from file for rx1: %0s.", $time, "etoe1.dat"); $readmemh("../data/etoe2.dat",rx2_d); $display("[%0t ns] ==INFO== Load memory from file for rx2: %0s.", $time, "etoe2.dat"); end `endif `ifdef TEST_CONTROLLER initial begin $readmemh("../data/cont_query.dat",rx0_d); $display("[%0t ns] ==INFO== Load memory from file for rx0: %0s.", $time, "cont_query.dat"); end `endif initial begin rstn = 1'b0; clk_25 = 1'b0; clk_125 = 1'b0; clk_phy = 0; clk_phyx2 = 0; #25 rstn = 1'b1; end // Clocks always #(PERIOD_CLK_125/2) clk_125 = ~clk_125; always #(PERIOD_CLK_25/2) clk_25 = ~clk_25; always #(PERIOD_CLK_125/2) clk_phy = ~clk_phy; always #(PERIOD_CLK_250/2) clk_phyx2 = ~clk_phyx2; assign #2 phy0_rx_clk = ~clk_phy; // 2.0 ns of delay assign #2 phy1_rx_clk = ~clk_phy; assign #2 phy2_rx_clk = ~clk_phy; // DDR count. Use bit 0 to indicate rising edge always @(posedge clk_phyx2, negedge rstn) if (!rstn) rx_clk_cnt <= 24'd0; else rx_clk_cnt <= rx_clk_cnt + 1'b1; // PHY0 always @(posedge clk_phy, negedge rstn) if (!rstn) rx0_idle_cnt <= 14'd0; else if (rx0_d[rx0_data_cnt][8]) rx0_idle_cnt <= rx0_d[rx0_data_cnt+1][7:0] << IDLE_SHIFT; always @(posedge clk_phy, negedge rstn) if (!rstn) rx0_clk_cnt_start <= RX0_CLK_CNT_START; else if (rx0_d[rx0_data_cnt][8]) rx0_clk_cnt_start <= rx_clk_cnt + (rx0_d[rx0_data_cnt+1][7:0] << IDLE_SHIFT) + 1'b1; always @(negedge clk_phyx2, negedge rstn) if (!rstn) rx0_last_byte <= 1'b0; else if (rx0_d[rx0_data_cnt][8] && rx_clk_cnt[0]) rx0_last_byte <= 1'b1; else rx0_last_byte <= 1'b0; always @(negedge clk_phyx2, negedge rstn) if (!rstn) begin phy0_rx_ctl <= 1'b0; phy0_rx_d <= 4'hD; rx0_data_cnt <= 24'd0; end else if (rx_clk_cnt >= rx0_clk_cnt_start && !rx0_last_byte) begin if (!rx_clk_cnt[0]) begin phy0_rx_ctl <= 1'b1; phy0_rx_d <= rx0_d[rx0_data_cnt][3:0]; end else begin phy0_rx_ctl <= 1'b1; phy0_rx_d <= rx0_d[rx0_data_cnt][7:4]; rx0_data_cnt <= rx0_data_cnt + 1'b1; end end else if (rx0_last_byte) begin phy0_rx_ctl <= 1'b0; phy0_rx_d <= 4'hD; rx0_data_cnt <= rx0_data_cnt + 1'b1; end else begin phy0_rx_ctl <= 1'b0; phy0_rx_d <= 4'hD; end // PHY1 // rx1_idle_cnt: when flag is set, update idle count always @(posedge clk_phy, negedge rstn) if (!rstn) rx1_idle_cnt <= 14'd0; else if (rx1_d[rx1_data_cnt][8]) rx1_idle_cnt <= rx1_d[rx1_data_cnt+1][7:0] << IDLE_SHIFT; // rx1_clk_cnt_start: always @(posedge clk_phy, negedge rstn) if (!rstn) rx1_clk_cnt_start <= RX1_CLK_CNT_START; else if (rx1_d[rx1_data_cnt][8]) rx1_clk_cnt_start <= rx_clk_cnt + (rx1_d[rx1_data_cnt+1][7:0] << IDLE_SHIFT) + 1'b1; always @(negedge clk_phyx2, negedge rstn) if (!rstn) rx1_last_byte <= 1'b0; else if (rx1_d[rx1_data_cnt][8] && rx_clk_cnt[0]) rx1_last_byte <= 1'b1; else rx1_last_byte <= 1'b0; always @(negedge clk_phyx2, negedge rstn) if (!rstn) begin phy1_rx_ctl <= 1'b0; phy1_rx_d <= 4'hD; rx1_data_cnt <= 24'd0; end else if (rx_clk_cnt >= rx1_clk_cnt_start && !rx1_last_byte) begin if (!rx_clk_cnt[0]) begin phy1_rx_ctl <= 1'b1; phy1_rx_d <= rx1_d[rx1_data_cnt][3:0]; end else begin phy1_rx_ctl <= 1'b1; phy1_rx_d <= rx1_d[rx1_data_cnt][7:4]; rx1_data_cnt <= rx1_data_cnt + 1'b1; end end else if (rx1_last_byte) begin phy1_rx_ctl <= 1'b0; phy1_rx_d <= 4'hD; rx1_data_cnt <= rx1_data_cnt + 1'b1; end else begin phy1_rx_ctl <= 1'b0; phy1_rx_d <= 4'hD; end // PHY2 // rx1_idle_cnt: when flag is set, update idle count always @(posedge clk_phy, negedge rstn) if (!rstn) rx2_idle_cnt <= 14'd0; else if (rx2_d[rx2_data_cnt][8]) rx2_idle_cnt <= rx2_d[rx2_data_cnt+1][7:0] << IDLE_SHIFT; always @(posedge clk_phy, negedge rstn) if (!rstn) rx2_clk_cnt_start <= RX2_CLK_CNT_START; else if (rx2_d[rx2_data_cnt][8]) rx2_clk_cnt_start <= rx_clk_cnt + (rx2_d[rx2_data_cnt+1][7:0] << IDLE_SHIFT) + 1'b1; // always @(negedge clk_phyx2, negedge rstn) if (!rstn) rx2_last_byte <= 1'b0; else if (rx2_d[rx2_data_cnt][8] && rx_clk_cnt[0]) rx2_last_byte <= 1'b1; else rx2_last_byte <= 1'b0; always @(negedge clk_phyx2, negedge rstn) if (!rstn) begin phy2_rx_ctl <= 1'b0; phy2_rx_d <= 4'hD; rx2_data_cnt <= 24'd0; end else if (rx_clk_cnt >= rx2_clk_cnt_start && !rx2_last_byte) begin if (!rx_clk_cnt[0]) begin phy2_rx_ctl <= 1'b1; phy2_rx_d <= rx2_d[rx2_data_cnt][3:0]; end else begin phy2_rx_ctl <= 1'b1; phy2_rx_d <= rx2_d[rx2_data_cnt][7:4]; rx2_data_cnt <= rx2_data_cnt + 1'b1; end end else if (rx2_last_byte) begin phy2_rx_ctl <= 1'b0; phy2_rx_d <= 4'hD; rx2_data_cnt <= rx2_data_cnt + 1'b1; end else begin phy2_rx_ctl <= 1'b0; phy2_rx_d <= 4'hD; end endmodule