diff --git a/rtl/eth_mac_mii.v b/rtl/eth_mac_mii.v new file mode 100644 index 000000000..75c1f86c9 --- /dev/null +++ b/rtl/eth_mac_mii.v @@ -0,0 +1,166 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * 10M/100M Ethernet MAC with MII interface + */ +module eth_mac_mii # +( + // target ("SIM", "GENERIC", "XILINX", "ALTERA") + parameter TARGET = "GENERIC", + // Clock input style ("BUFG", "BUFR", "BUFIO", "BUFIO2") + // Use BUFR for Virtex-5, Virtex-6, 7-series + // Use BUFG for Ultrascale + // Use BUFIO2 for Spartan-6 + parameter CLOCK_INPUT_STYLE = "BUFIO2", + parameter ENABLE_PADDING = 1, + parameter MIN_FRAME_LENGTH = 64 +) +( + input wire rst, + output wire rx_clk, + output wire rx_rst, + output wire tx_clk, + output wire tx_rst, + + /* + * AXI input + */ + input wire [7:0] tx_axis_tdata, + input wire tx_axis_tvalid, + output wire tx_axis_tready, + input wire tx_axis_tlast, + input wire tx_axis_tuser, + + /* + * AXI output + */ + output wire [7:0] rx_axis_tdata, + output wire rx_axis_tvalid, + output wire rx_axis_tlast, + output wire rx_axis_tuser, + + /* + * MII interface + */ + input wire mii_rx_clk, + input wire [3:0] mii_rxd, + input wire mii_rx_dv, + input wire mii_rx_er, + input wire mii_tx_clk, + output wire [3:0] mii_txd, + output wire mii_tx_en, + output wire mii_tx_er, + + /* + * Status + */ + output wire tx_start_packet, + output wire tx_error_underflow, + output wire rx_start_packet, + output wire rx_error_bad_frame, + output wire rx_error_bad_fcs, + + /* + * Configuration + */ + input wire [7:0] ifg_delay +); + +wire [3:0] mac_mii_rxd; +wire mac_mii_rx_dv; +wire mac_mii_rx_er; +wire [3:0] mac_mii_txd; +wire mac_mii_tx_en; +wire mac_mii_tx_er; + +mii_phy_if #( + .TARGET(TARGET), + .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE) +) +mii_phy_if_inst ( + .rst(rst), + + .mac_mii_rx_clk(rx_clk), + .mac_mii_rx_rst(rx_rst), + .mac_mii_rxd(mac_mii_rxd), + .mac_mii_rx_dv(mac_mii_rx_dv), + .mac_mii_rx_er(mac_mii_rx_er), + .mac_mii_tx_clk(tx_clk), + .mac_mii_tx_rst(tx_rst), + .mac_mii_txd(mac_mii_txd), + .mac_mii_tx_en(mac_mii_tx_en), + .mac_mii_tx_er(mac_mii_tx_er), + + .phy_mii_rx_clk(mii_rx_clk), + .phy_mii_rxd(mii_rxd), + .phy_mii_rx_dv(mii_rx_dv), + .phy_mii_rx_er(mii_rx_er), + .phy_mii_tx_clk(mii_tx_clk), + .phy_mii_txd(mii_txd), + .phy_mii_tx_en(mii_tx_en), + .phy_mii_tx_er(mii_tx_er) +); + +eth_mac_1g #( + .ENABLE_PADDING(ENABLE_PADDING), + .MIN_FRAME_LENGTH(MIN_FRAME_LENGTH) +) +eth_mac_1g_inst ( + .tx_clk(tx_clk), + .tx_rst(tx_rst), + .rx_clk(rx_clk), + .rx_rst(rx_rst), + .tx_axis_tdata(tx_axis_tdata), + .tx_axis_tvalid(tx_axis_tvalid), + .tx_axis_tready(tx_axis_tready), + .tx_axis_tlast(tx_axis_tlast), + .tx_axis_tuser(tx_axis_tuser), + .rx_axis_tdata(rx_axis_tdata), + .rx_axis_tvalid(rx_axis_tvalid), + .rx_axis_tlast(rx_axis_tlast), + .rx_axis_tuser(rx_axis_tuser), + .gmii_rxd(mac_mii_rxd), + .gmii_rx_dv(mac_mii_rx_dv), + .gmii_rx_er(mac_mii_rx_er), + .gmii_txd(mac_mii_txd), + .gmii_tx_en(mac_mii_tx_en), + .gmii_tx_er(mac_mii_tx_er), + .rx_clk_enable(1'b1), + .tx_clk_enable(1'b1), + .rx_mii_select(1'b1), + .tx_mii_select(1'b1), + .tx_start_packet(tx_start_packet), + .tx_error_underflow(tx_error_underflow), + .rx_start_packet(rx_start_packet), + .rx_error_bad_frame(rx_error_bad_frame), + .rx_error_bad_fcs(rx_error_bad_fcs), + .ifg_delay(ifg_delay) +); + +endmodule diff --git a/rtl/eth_mac_mii_fifo.v b/rtl/eth_mac_mii_fifo.v new file mode 100644 index 000000000..56dd9ae99 --- /dev/null +++ b/rtl/eth_mac_mii_fifo.v @@ -0,0 +1,312 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * 10M/100M Ethernet MAC with MII interface and TX and RX FIFOs + */ +module eth_mac_mii_fifo # +( + // target ("SIM", "GENERIC", "XILINX", "ALTERA") + parameter TARGET = "GENERIC", + // Clock input style ("BUFG", "BUFR", "BUFIO", "BUFIO2") + // Use BUFR for Virtex-5, Virtex-6, 7-series + // Use BUFG for Ultrascale + // Use BUFIO2 for Spartan-6 + parameter CLOCK_INPUT_STYLE = "BUFIO2", + parameter ENABLE_PADDING = 1, + parameter MIN_FRAME_LENGTH = 64, + parameter TX_FIFO_ADDR_WIDTH = 12, + parameter TX_FRAME_FIFO = 1, + parameter TX_DROP_BAD_FRAME = TX_FRAME_FIFO, + parameter TX_DROP_WHEN_FULL = 0, + parameter RX_FIFO_ADDR_WIDTH = 12, + parameter RX_FRAME_FIFO = 1, + parameter RX_DROP_BAD_FRAME = RX_FRAME_FIFO, + parameter RX_DROP_WHEN_FULL = RX_FRAME_FIFO +) +( + input wire rst, + input wire logic_clk, + input wire logic_rst, + + /* + * AXI input + */ + input wire [7:0] tx_axis_tdata, + input wire tx_axis_tvalid, + output wire tx_axis_tready, + input wire tx_axis_tlast, + input wire tx_axis_tuser, + + /* + * AXI output + */ + output wire [7:0] rx_axis_tdata, + output wire rx_axis_tvalid, + input wire rx_axis_tready, + output wire rx_axis_tlast, + output wire rx_axis_tuser, + + /* + * MII interface + */ + input wire mii_rx_clk, + input wire [3:0] mii_rxd, + input wire mii_rx_dv, + input wire mii_rx_er, + input wire mii_tx_clk, + output wire [3:0] mii_txd, + output wire mii_tx_en, + output wire mii_tx_er, + + /* + * Status + */ + output wire tx_error_underflow, + output wire tx_fifo_overflow, + output wire tx_fifo_bad_frame, + output wire tx_fifo_good_frame, + output wire rx_error_bad_frame, + output wire rx_error_bad_fcs, + output wire rx_fifo_overflow, + output wire rx_fifo_bad_frame, + output wire rx_fifo_good_frame, + + /* + * Configuration + */ + input wire [7:0] ifg_delay +); + +wire tx_clk; +wire rx_clk; +wire tx_rst; +wire rx_rst; + +wire [7:0] tx_fifo_axis_tdata; +wire tx_fifo_axis_tvalid; +wire tx_fifo_axis_tready; +wire tx_fifo_axis_tlast; +wire tx_fifo_axis_tuser; + +wire [7:0] rx_fifo_axis_tdata; +wire rx_fifo_axis_tvalid; +wire rx_fifo_axis_tlast; +wire rx_fifo_axis_tuser; + +// synchronize MAC status signals into logic clock domain +wire tx_error_underflow_int; + +reg [0:0] tx_sync_reg_1 = 1'b0; +reg [0:0] tx_sync_reg_2 = 1'b0; +reg [0:0] tx_sync_reg_3 = 1'b0; +reg [0:0] tx_sync_reg_4 = 1'b0; + +assign tx_error_underflow = tx_sync_reg_3[0] ^ tx_sync_reg_4[0]; + +always @(posedge tx_clk or posedge tx_rst) begin + if (tx_rst) begin + tx_sync_reg_1 <= 1'b0; + end else begin + tx_sync_reg_1 <= tx_sync_reg_1 ^ {tx_error_underflow_int}; + end +end + +always @(posedge logic_clk or posedge logic_rst) begin + if (logic_rst) begin + tx_sync_reg_2 <= 1'b0; + tx_sync_reg_3 <= 1'b0; + tx_sync_reg_4 <= 1'b0; + end else begin + tx_sync_reg_2 <= tx_sync_reg_1; + tx_sync_reg_3 <= tx_sync_reg_2; + tx_sync_reg_4 <= tx_sync_reg_3; + end +end + +wire rx_error_bad_frame_int; +wire rx_error_bad_fcs_int; + +reg [1:0] rx_sync_reg_1 = 2'd0; +reg [1:0] rx_sync_reg_2 = 2'd0; +reg [1:0] rx_sync_reg_3 = 2'd0; +reg [1:0] rx_sync_reg_4 = 2'd0; + +assign rx_error_bad_frame = rx_sync_reg_3[0] ^ rx_sync_reg_4[0]; +assign rx_error_bad_fcs = rx_sync_reg_3[1] ^ rx_sync_reg_4[1]; + +always @(posedge rx_clk or posedge rx_rst) begin + if (rx_rst) begin + rx_sync_reg_1 <= 2'd0; + end else begin + rx_sync_reg_1 <= rx_sync_reg_1 ^ {rx_error_bad_frame_int, rx_error_bad_frame_int}; + end +end + +always @(posedge logic_clk or posedge logic_rst) begin + if (logic_rst) begin + rx_sync_reg_2 <= 2'd0; + rx_sync_reg_3 <= 2'd0; + rx_sync_reg_4 <= 2'd0; + end else begin + rx_sync_reg_2 <= rx_sync_reg_1; + rx_sync_reg_3 <= rx_sync_reg_2; + rx_sync_reg_4 <= rx_sync_reg_3; + end +end + +eth_mac_mii #( + .TARGET(TARGET), + .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE), + .ENABLE_PADDING(ENABLE_PADDING), + .MIN_FRAME_LENGTH(MIN_FRAME_LENGTH) +) +eth_mac_1g_mii_inst ( + .rst(rst), + .tx_clk(tx_clk), + .tx_rst(tx_rst), + .rx_clk(rx_clk), + .rx_rst(rx_rst), + .tx_axis_tdata(tx_fifo_axis_tdata), + .tx_axis_tvalid(tx_fifo_axis_tvalid), + .tx_axis_tready(tx_fifo_axis_tready), + .tx_axis_tlast(tx_fifo_axis_tlast), + .tx_axis_tuser(tx_fifo_axis_tuser), + .rx_axis_tdata(rx_fifo_axis_tdata), + .rx_axis_tvalid(rx_fifo_axis_tvalid), + .rx_axis_tlast(rx_fifo_axis_tlast), + .rx_axis_tuser(rx_fifo_axis_tuser), + .mii_rx_clk(mii_rx_clk), + .mii_rxd(mii_rxd), + .mii_rx_dv(mii_rx_dv), + .mii_rx_er(mii_rx_er), + .mii_tx_clk(mii_tx_clk), + .mii_txd(mii_txd), + .mii_tx_en(mii_tx_en), + .mii_tx_er(mii_tx_er), + .tx_error_underflow(tx_error_underflow_int), + .rx_error_bad_frame(rx_error_bad_frame_int), + .rx_error_bad_fcs(rx_error_bad_fcs_int), + .ifg_delay(ifg_delay) +); + +axis_async_fifo #( + .ADDR_WIDTH(TX_FIFO_ADDR_WIDTH), + .DATA_WIDTH(8), + .KEEP_ENABLE(0), + .LAST_ENABLE(1), + .ID_ENABLE(0), + .DEST_ENABLE(0), + .USER_ENABLE(1), + .USER_WIDTH(1), + .FRAME_FIFO(TX_FRAME_FIFO), + .USER_BAD_FRAME_VALUE(1'b1), + .USER_BAD_FRAME_MASK(1'b1), + .DROP_BAD_FRAME(TX_DROP_BAD_FRAME), + .DROP_WHEN_FULL(TX_DROP_WHEN_FULL) +) +tx_fifo ( + // Common reset + .async_rst(logic_rst | tx_rst), + // AXI input + .s_clk(logic_clk), + .s_axis_tdata(tx_axis_tdata), + .s_axis_tkeep(0), + .s_axis_tvalid(tx_axis_tvalid), + .s_axis_tready(tx_axis_tready), + .s_axis_tlast(tx_axis_tlast), + .s_axis_tid(0), + .s_axis_tdest(0), + .s_axis_tuser(tx_axis_tuser), + // AXI output + .m_clk(tx_clk), + .m_axis_tdata(tx_fifo_axis_tdata), + .m_axis_tkeep(), + .m_axis_tvalid(tx_fifo_axis_tvalid), + .m_axis_tready(tx_fifo_axis_tready), + .m_axis_tlast(tx_fifo_axis_tlast), + .m_axis_tid(), + .m_axis_tdest(), + .m_axis_tuser(tx_fifo_axis_tuser), + // Status + .s_status_overflow(tx_fifo_overflow), + .s_status_bad_frame(tx_fifo_bad_frame), + .s_status_good_frame(tx_fifo_good_frame), + .m_status_overflow(), + .m_status_bad_frame(), + .m_status_good_frame() +); + +axis_async_fifo #( + .ADDR_WIDTH(RX_FIFO_ADDR_WIDTH), + .DATA_WIDTH(8), + .KEEP_ENABLE(0), + .LAST_ENABLE(1), + .ID_ENABLE(0), + .DEST_ENABLE(0), + .USER_ENABLE(1), + .USER_WIDTH(1), + .FRAME_FIFO(TX_FRAME_FIFO), + .USER_BAD_FRAME_VALUE(1'b1), + .USER_BAD_FRAME_MASK(1'b1), + .DROP_BAD_FRAME(TX_DROP_BAD_FRAME), + .DROP_WHEN_FULL(TX_DROP_WHEN_FULL) +) +rx_fifo ( + // Common reset + .async_rst(rx_rst | logic_rst), + // AXI input + .s_clk(rx_clk), + .s_axis_tdata(rx_fifo_axis_tdata), + .s_axis_tkeep(0), + .s_axis_tvalid(rx_fifo_axis_tvalid), + .s_axis_tready(), + .s_axis_tlast(rx_fifo_axis_tlast), + .s_axis_tid(0), + .s_axis_tdest(0), + .s_axis_tuser(rx_fifo_axis_tuser), + // AXI output + .m_clk(logic_clk), + .m_axis_tdata(rx_axis_tdata), + .m_axis_tkeep(), + .m_axis_tvalid(rx_axis_tvalid), + .m_axis_tready(rx_axis_tready), + .m_axis_tlast(rx_axis_tlast), + .m_axis_tid(), + .m_axis_tdest(), + .m_axis_tuser(rx_axis_tuser), + // Status + .s_status_overflow(), + .s_status_bad_frame(), + .s_status_good_frame(), + .m_status_overflow(rx_fifo_overflow), + .m_status_bad_frame(rx_fifo_bad_frame), + .m_status_good_frame(rx_fifo_good_frame) +); + +endmodule diff --git a/rtl/mii_phy_if.v b/rtl/mii_phy_if.v new file mode 100644 index 000000000..0e8cb8ce2 --- /dev/null +++ b/rtl/mii_phy_if.v @@ -0,0 +1,137 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * MII PHY interface + */ +module mii_phy_if # +( + // target ("SIM", "GENERIC", "XILINX", "ALTERA") + parameter TARGET = "GENERIC", + // Clock input style ("BUFG", "BUFR", "BUFIO", "BUFIO2") + // Use BUFR for Virtex-5, Virtex-6, 7-series + // Use BUFG for Ultrascale + // Use BUFIO2 for Spartan-6 + parameter CLOCK_INPUT_STYLE = "BUFIO2" +) +( + input wire rst, + + /* + * MII interface to MAC + */ + output wire mac_mii_rx_clk, + output wire mac_mii_rx_rst, + output wire [3:0] mac_mii_rxd, + output wire mac_mii_rx_dv, + output wire mac_mii_rx_er, + output wire mac_mii_tx_clk, + output wire mac_mii_tx_rst, + input wire [3:0] mac_mii_txd, + input wire mac_mii_tx_en, + input wire mac_mii_tx_er, + + /* + * MII interface to PHY + */ + input wire phy_mii_rx_clk, + input wire [3:0] phy_mii_rxd, + input wire phy_mii_rx_dv, + input wire phy_mii_rx_er, + input wire phy_mii_tx_clk, + output wire [3:0] phy_mii_txd, + output wire phy_mii_tx_en, + output wire phy_mii_tx_er +); + +ssio_sdr_in # +( + .TARGET(TARGET), + .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE), + .WIDTH(6) +) +rx_ssio_sdr_inst ( + .input_clk(phy_mii_rx_clk), + .input_d({phy_mii_rxd, phy_mii_rx_dv, phy_mii_rx_er}), + .output_clk(mac_mii_rx_clk), + .output_q({mac_mii_rxd, mac_mii_rx_dv, mac_mii_rx_er}) +); + +(* IOB = "TRUE" *) +reg [3:0] phy_mii_txd_reg = 4'd0; +(* IOB = "TRUE" *) +reg phy_mii_tx_en_reg = 1'b0, phy_mii_tx_er_reg = 1'b0; + +assign phy_mii_txd = phy_mii_txd_reg; +assign phy_mii_tx_en = phy_mii_tx_en_reg; +assign phy_mii_tx_er = phy_mii_tx_er_reg; + +always @(posedge mac_mii_tx_clk) begin + phy_mii_txd_reg <= mac_mii_txd; + phy_mii_tx_en_reg <= mac_mii_tx_en; + phy_mii_tx_er_reg <= mac_mii_tx_er; +end + +generate + +if (TARGET == "XILINX") begin + BUFG + mii_bufg_inst ( + .I(phy_mii_tx_clk), + .O(mac_mii_tx_clk) + ); +end else begin + assign mac_mii_tx_clk = phy_mii_tx_clk; +end + +endgenerate + +// reset sync +reg [3:0] tx_rst_reg = 4'hf; +assign mac_mii_tx_rst = tx_rst_reg[0]; + +always @(posedge mac_mii_tx_clk or posedge rst) begin + if (rst) begin + tx_rst_reg <= 4'hf; + end else begin + tx_rst_reg <= {1'b0, tx_rst_reg[3:1]}; + end +end + +reg [3:0] rx_rst_reg = 4'hf; +assign mac_mii_rx_rst = rx_rst_reg[0]; + +always @(posedge mac_mii_rx_clk or posedge rst) begin + if (rst) begin + rx_rst_reg <= 4'hf; + end else begin + rx_rst_reg <= {1'b0, rx_rst_reg[3:1]}; + end +end + +endmodule diff --git a/syn/eth_mac_fifo.tcl b/syn/eth_mac_fifo.tcl index daa072456..69a06318e 100644 --- a/syn/eth_mac_fifo.tcl +++ b/syn/eth_mac_fifo.tcl @@ -23,7 +23,8 @@ foreach mac_inst [get_cells -hier -filter {(ORIG_REF_NAME == eth_mac_1g_fifo || REF_NAME == eth_mac_1g_fifo || \ ORIG_REF_NAME == eth_mac_10g_fifo || REF_NAME == eth_mac_10g_fifo || \ ORIG_REF_NAME == eth_mac_1g_gmii_fifo || REF_NAME == eth_mac_1g_gmii_fifo || \ - ORIG_REF_NAME == eth_mac_1g_rgmii_fifo || REF_NAME == eth_mac_1g_rgmii_fifo)}] { + ORIG_REF_NAME == eth_mac_1g_rgmii_fifo || REF_NAME == eth_mac_1g_rgmii_fifo || \ + ORIG_REF_NAME == eth_mac_mii_fifo || REF_NAME == eth_mac_mii_fifo)}] { puts "Inserting timing constraints for ethernet MAC with FIFO instance $mac_inst" set sync_ffs [get_cells -hier -regexp ".*/rx_sync_reg_\[1234\]_reg\\\[\\d+\\\]" -filter "PARENT == $mac_inst"] diff --git a/syn/mii_phy_if.tcl b/syn/mii_phy_if.tcl new file mode 100644 index 000000000..64f518200 --- /dev/null +++ b/syn/mii_phy_if.tcl @@ -0,0 +1,31 @@ +# Copyright (c) 2019 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# MII PHY IF timing constraints + +foreach if_inst [get_cells -hier -filter {(ORIG_REF_NAME == mii_phy_if || REF_NAME == mii_phy_if)}] { + puts "Inserting timing constraints for mii_phy_if instance $if_inst" + + # reset synchronization + set reset_ffs [get_cells -hier -regexp ".*/(rx|tx)_rst_reg_reg\\\[\\d\\\]" -filter "PARENT == $if_inst"] + + set_property ASYNC_REG TRUE $reset_ffs + set_false_path -to [get_pins -of_objects $reset_ffs -filter {IS_PRESET || IS_RESET}] +} diff --git a/tb/mii_ep.py b/tb/mii_ep.py new file mode 100644 index 000000000..64c4a09b9 --- /dev/null +++ b/tb/mii_ep.py @@ -0,0 +1,262 @@ +""" + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * + +class MIIFrame(object): + def __init__(self, data=b'', error=None): + self.data = b'' + self.error = None + + if type(data) is MIIFrame: + self.data = data.data + self.error = data.error + else: + self.data = bytearray(data) + + def build(self): + if self.data is None: + return + + f = list(self.data) + d = [] + er = [] + i = 0 + + assert_er = False + if (type(self.error) is int or type(self.error) is bool) and self.error: + assert_er = True + self.error = None + + while len(f) > 0: + db = f.pop(0) + d.append(db & 0x0f) + d.append(db >> 4) + if self.error is None: + er.append(0) + er.append(0) + else: + er.append(self.error[i]) + er.append(self.error[i]) + i += 1 + + if assert_er: + er[-1] = 1 + self.error = 1 + + return d, er + + def parse(self, d, er): + if d is None or er is None: + return + + # sync and repack data + odd = True + sync = False + b = 0 + be = 0 + d2 = [] + er2 = [] + for n, e in zip(d, er): + odd = not odd + b = (n & 0x0F) << 4 | b >> 4 + be |= e + if not sync and b == 0xD5: + odd = True + sync = True + if odd: + d2.append(b) + er2.append(be) + be = False + + self.data = bytearray(d2) + self.error = er2 + + def __eq__(self, other): + if type(other) is MIIFrame: + return self.data == other.data + return False + + def __repr__(self): + return 'MIIFrame(data=%s, error=%s)' % (repr(self.data), repr(self.error)) + + def __iter__(self): + return self.data.__iter__() + + +class MIISource(object): + def __init__(self): + self.has_logic = False + self.queue = [] + + def send(self, frame): + self.queue.append(MIIFrame(frame)) + + def count(self): + return len(self.queue) + + def empty(self): + return not self.queue + + def create_logic(self, + clk, + rst, + txd, + tx_en, + tx_er, + clk_enable=True, + name=None + ): + + assert not self.has_logic + + self.has_logic = True + + assert len(txd) == 4 + + @instance + def logic(): + frame = None + d = [] + er = [] + ifg_cnt = 0 + + while True: + yield clk.posedge, rst.posedge + + if rst: + frame = None + txd.next = 0 + tx_en.next = 0 + tx_er.next = 0 + d = [] + er = [] + ifg_cnt = 0 + else: + if not clk_enable: + pass + elif ifg_cnt > 0: + ifg_cnt -= 1 + txd.next = 0 + tx_er.next = 0 + tx_en.next = 0 + elif len(d) > 0: + txd.next = d.pop(0) + tx_er.next = er.pop(0) + tx_en.next = 1 + if len(d) == 0: + ifg_cnt = 12*2 + elif self.queue: + frame = MIIFrame(self.queue.pop(0)) + d, er = frame.build() + if name is not None: + print("[%s] Sending frame %s" % (name, repr(frame))) + txd.next = d.pop(0) + tx_er.next = er.pop(0) + tx_en.next = 1 + else: + txd.next = 0 + tx_er.next = 0 + tx_en.next = 0 + + return logic + + +class MIISink(object): + def __init__(self): + self.has_logic = False + self.queue = [] + self.sync = Signal(intbv(0)) + + def recv(self): + if self.queue: + return self.queue.pop(0) + return None + + def count(self): + return len(self.queue) + + def empty(self): + return not self.queue + + def wait(self, timeout=0): + if self.queue: + return + if timeout: + yield self.sync, delay(timeout) + else: + yield self.sync + + def create_logic(self, + clk, + rst, + rxd, + rx_dv, + rx_er, + clk_enable=True, + name=None + ): + + assert not self.has_logic + + self.has_logic = True + + assert len(rxd) == 4 + + @instance + def logic(): + frame = None + d = [] + er = [] + + while True: + yield clk.posedge, rst.posedge + + if rst: + frame = None + d = [] + er = [] + else: + if not clk_enable: + pass + elif rx_dv: + if frame is None: + frame = MIIFrame() + d = [] + er = [] + d.append(int(rxd)) + er.append(int(rx_er)) + elif frame is not None: + if len(d) > 0: + frame.parse(d, er) + self.queue.append(frame) + self.sync.next = not self.sync + if name is not None: + print("[%s] Got frame %s" % (name, repr(frame))) + frame = None + d = [] + er = [] + + return logic + diff --git a/tb/test_eth_mac_mii.py b/tb/test_eth_mac_mii.py new file mode 100755 index 000000000..23946b983 --- /dev/null +++ b/tb/test_eth_mac_mii.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +import axis_ep +import eth_ep +import mii_ep + +module = 'eth_mac_mii' +testbench = 'test_%s' % module + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("../rtl/lfsr.v") +srcs.append("../rtl/axis_gmii_rx.v") +srcs.append("../rtl/axis_gmii_tx.v") +srcs.append("../rtl/eth_mac_1g.v") +srcs.append("../rtl/mii_phy_if.v") +srcs.append("../rtl/ssio_sdr_in.v") +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def bench(): + + # Parameters + TARGET = "SIM" + CLOCK_INPUT_STYLE = "BUFIO2" + ENABLE_PADDING = 1 + MIN_FRAME_LENGTH = 64 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + tx_axis_tdata = Signal(intbv(0)[8:]) + tx_axis_tvalid = Signal(bool(0)) + tx_axis_tlast = Signal(bool(0)) + tx_axis_tuser = Signal(bool(0)) + mii_rx_clk = Signal(bool(0)) + mii_rxd = Signal(intbv(0)[4:]) + mii_rx_dv = Signal(bool(0)) + mii_rx_er = Signal(bool(0)) + mii_tx_clk = Signal(bool(0)) + ifg_delay = Signal(intbv(0)[8:]) + + # Outputs + rx_clk = Signal(bool(0)) + rx_rst = Signal(bool(0)) + tx_clk = Signal(bool(0)) + tx_rst = Signal(bool(0)) + tx_axis_tready = Signal(bool(0)) + rx_axis_tdata = Signal(intbv(0)[8:]) + rx_axis_tvalid = Signal(bool(0)) + rx_axis_tlast = Signal(bool(0)) + rx_axis_tuser = Signal(bool(0)) + mii_txd = Signal(intbv(0)[4:]) + mii_tx_en = Signal(bool(0)) + mii_tx_er = Signal(bool(0)) + tx_error_underflow = Signal(bool(0)) + rx_error_bad_frame = Signal(bool(0)) + rx_error_bad_fcs = Signal(bool(0)) + + # sources and sinks + axis_source_pause = Signal(bool(0)) + + + mii_source = mii_ep.MIISource() + + mii_source_logic = mii_source.create_logic( + mii_rx_clk, + rst, + txd=mii_rxd, + tx_en=mii_rx_dv, + tx_er=mii_rx_er, + name='mii_source' + ) + + mii_sink = mii_ep.MIISink() + + mii_sink_logic = mii_sink.create_logic( + mii_tx_clk, + rst, + rxd=mii_txd, + rx_dv=mii_tx_en, + rx_er=mii_tx_er, + name='mii_sink' + ) + + axis_source = axis_ep.AXIStreamSource() + + axis_source_logic = axis_source.create_logic( + mii_rx_clk, #tx_clk, + tx_rst, + tdata=tx_axis_tdata, + tvalid=tx_axis_tvalid, + tready=tx_axis_tready, + tlast=tx_axis_tlast, + tuser=tx_axis_tuser, + pause=axis_source_pause, + name='axis_source' + ) + + axis_sink = axis_ep.AXIStreamSink() + + axis_sink_logic = axis_sink.create_logic( + mii_rx_clk, + rx_rst, + tdata=rx_axis_tdata, + tvalid=rx_axis_tvalid, + tlast=rx_axis_tlast, + tuser=rx_axis_tuser, + name='axis_sink' + ) + + # DUT + if os.system(build_cmd): + raise Exception("Error running build command") + + dut = Cosimulation( + "vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + + rx_clk=rx_clk, + rx_rst=rx_rst, + tx_clk=tx_clk, + tx_rst=tx_rst, + + tx_axis_tdata=tx_axis_tdata, + tx_axis_tvalid=tx_axis_tvalid, + tx_axis_tready=tx_axis_tready, + tx_axis_tlast=tx_axis_tlast, + tx_axis_tuser=tx_axis_tuser, + + rx_axis_tdata=rx_axis_tdata, + rx_axis_tvalid=rx_axis_tvalid, + rx_axis_tlast=rx_axis_tlast, + rx_axis_tuser=rx_axis_tuser, + + mii_rx_clk=mii_rx_clk, + mii_rxd=mii_rxd, + mii_rx_dv=mii_rx_dv, + mii_rx_er=mii_rx_er, + + mii_tx_clk=mii_tx_clk, + mii_txd=mii_txd, + mii_tx_en=mii_tx_en, + mii_tx_er=mii_tx_er, + + tx_error_underflow=tx_error_underflow, + rx_error_bad_frame=rx_error_bad_frame, + rx_error_bad_fcs=rx_error_bad_fcs, + + ifg_delay=ifg_delay + ) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + + phy_clk_hp = Signal(int(20)) + + @instance + def phy_clk_gen(): + while True: + yield delay(int(phy_clk_hp)) + mii_rx_clk.next = not mii_rx_clk + mii_tx_clk.next = not mii_rx_clk + + rx_error_bad_frame_asserted = Signal(bool(0)) + rx_error_bad_fcs_asserted = Signal(bool(0)) + + @always(clk.posedge) + def monitor(): + if (rx_error_bad_frame): + rx_error_bad_frame_asserted.next = 1 + if (rx_error_bad_fcs): + rx_error_bad_fcs_asserted.next = 1 + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + yield clk.posedge + rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + ifg_delay.next = 12 + + # testbench stimulus + + for rate in [20, 200]: + phy_clk_hp.next = rate + + yield delay(1000) + + yield clk.posedge + print("test 1: test rx packet") + current_test.next = 1 + + test_frame = eth_ep.EthFrame() + test_frame.eth_dest_mac = 0xDAD1D2D3D4D5 + test_frame.eth_src_mac = 0x5A5152535455 + test_frame.eth_type = 0x8000 + test_frame.payload = bytearray(range(32)) + test_frame.update_fcs() + + axis_frame = test_frame.build_axis_fcs() + + mii_source.send(b'\x55\x55\x55\x55\x55\x55\x55\xD5'+bytearray(axis_frame)) + + yield axis_sink.wait() + rx_frame = axis_sink.recv() + + eth_frame = eth_ep.EthFrame() + eth_frame.parse_axis(rx_frame) + eth_frame.update_fcs() + + assert eth_frame == test_frame + + yield delay(100) + + yield clk.posedge + print("test 2: test tx packet") + current_test.next = 2 + + test_frame = eth_ep.EthFrame() + test_frame.eth_dest_mac = 0xDAD1D2D3D4D5 + test_frame.eth_src_mac = 0x5A5152535455 + test_frame.eth_type = 0x8000 + test_frame.payload = bytearray(range(32)) + test_frame.update_fcs() + + axis_frame = test_frame.build_axis() + + axis_source.send(axis_frame) + + yield mii_sink.wait() + rx_frame = mii_sink.recv() + + assert rx_frame.data[0:8] == bytearray(b'\x55\x55\x55\x55\x55\x55\x55\xD5') + + eth_frame = eth_ep.EthFrame() + eth_frame.parse_axis_fcs(rx_frame.data[8:]) + + print(hex(eth_frame.eth_fcs)) + print(hex(eth_frame.calc_fcs())) + + assert len(eth_frame.payload.data) == 46 + assert eth_frame.eth_fcs == eth_frame.calc_fcs() + assert eth_frame.eth_dest_mac == test_frame.eth_dest_mac + assert eth_frame.eth_src_mac == test_frame.eth_src_mac + assert eth_frame.eth_type == test_frame.eth_type + assert eth_frame.payload.data.index(test_frame.payload.data) == 0 + + yield delay(100) + + raise StopSimulation + + return instances() + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/tb/test_eth_mac_mii.v b/tb/test_eth_mac_mii.v new file mode 100644 index 000000000..a4c2d97f6 --- /dev/null +++ b/tb/test_eth_mac_mii.v @@ -0,0 +1,148 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for eth_mac_mii + */ +module test_eth_mac_mii; + +// Parameters +parameter TARGET = "SIM"; +parameter CLOCK_INPUT_STYLE = "BUFIO2"; +parameter ENABLE_PADDING = 1; +parameter MIN_FRAME_LENGTH = 64; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg [7:0] tx_axis_tdata = 0; +reg tx_axis_tvalid = 0; +reg tx_axis_tlast = 0; +reg tx_axis_tuser = 0; +reg mii_rx_clk = 0; +reg [3:0] mii_rxd = 0; +reg mii_rx_dv = 0; +reg mii_rx_er = 0; +reg mii_tx_clk = 0; +reg [7:0] ifg_delay = 0; + +// Outputs +wire rx_clk; +wire rx_rst; +wire tx_clk; +wire tx_rst; +wire tx_axis_tready; +wire [7:0] rx_axis_tdata; +wire rx_axis_tvalid; +wire rx_axis_tlast; +wire rx_axis_tuser; +wire [3:0] mii_txd; +wire mii_tx_en; +wire mii_tx_er; +wire tx_error_underflow; +wire rx_error_bad_frame; +wire rx_error_bad_fcs; + +initial begin + // myhdl integration + $from_myhdl( + clk, + rst, + current_test, + tx_axis_tdata, + tx_axis_tvalid, + tx_axis_tlast, + tx_axis_tuser, + mii_rx_clk, + mii_rxd, + mii_rx_dv, + mii_rx_er, + mii_tx_clk, + ifg_delay + ); + $to_myhdl( + rx_clk, + rx_rst, + tx_clk, + tx_rst, + tx_axis_tready, + rx_axis_tdata, + rx_axis_tvalid, + rx_axis_tlast, + rx_axis_tuser, + mii_txd, + mii_tx_en, + mii_tx_er, + tx_error_underflow, + rx_error_bad_frame, + rx_error_bad_fcs + ); + + // dump file + $dumpfile("test_eth_mac_mii.lxt"); + $dumpvars(0, test_eth_mac_mii); +end + +eth_mac_mii #( + .TARGET(TARGET), + .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE), + .ENABLE_PADDING(ENABLE_PADDING), + .MIN_FRAME_LENGTH(MIN_FRAME_LENGTH) +) +UUT ( + .rst(rst), + .rx_clk(rx_clk), + .rx_rst(rx_rst), + .tx_clk(tx_clk), + .tx_rst(tx_rst), + .tx_axis_tdata(tx_axis_tdata), + .tx_axis_tvalid(tx_axis_tvalid), + .tx_axis_tready(tx_axis_tready), + .tx_axis_tlast(tx_axis_tlast), + .tx_axis_tuser(tx_axis_tuser), + .rx_axis_tdata(rx_axis_tdata), + .rx_axis_tvalid(rx_axis_tvalid), + .rx_axis_tlast(rx_axis_tlast), + .rx_axis_tuser(rx_axis_tuser), + .mii_rx_clk(mii_rx_clk), + .mii_rxd(mii_rxd), + .mii_rx_dv(mii_rx_dv), + .mii_rx_er(mii_rx_er), + .mii_tx_clk(mii_tx_clk), + .mii_txd(mii_txd), + .mii_tx_en(mii_tx_en), + .mii_tx_er(mii_tx_er), + .tx_error_underflow(tx_error_underflow), + .rx_error_bad_frame(rx_error_bad_frame), + .rx_error_bad_fcs(rx_error_bad_fcs), + .ifg_delay(ifg_delay) +); + +endmodule diff --git a/tb/test_eth_mac_mii_fifo.py b/tb/test_eth_mac_mii_fifo.py new file mode 100755 index 000000000..1192112f0 --- /dev/null +++ b/tb/test_eth_mac_mii_fifo.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +import axis_ep +import eth_ep +import mii_ep + +module = 'eth_mac_mii_fifo' +testbench = 'test_%s' % module + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("../rtl/lfsr.v") +srcs.append("../rtl/axis_gmii_rx.v") +srcs.append("../rtl/axis_gmii_tx.v") +srcs.append("../rtl/eth_mac_1g.v") +srcs.append("../rtl/eth_mac_mii.v") +srcs.append("../rtl/mii_phy_if.v") +srcs.append("../rtl/ssio_sdr_in.v") +srcs.append("../lib/axis/rtl/axis_async_fifo.v") +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def bench(): + + # Parameters + TARGET = "SIM" + CLOCK_INPUT_STYLE = "BUFIO2" + ENABLE_PADDING = 1 + MIN_FRAME_LENGTH = 64 + TX_FIFO_ADDR_WIDTH = 9 + RX_FIFO_ADDR_WIDTH = 9 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + logic_clk = Signal(bool(0)) + logic_rst = Signal(bool(0)) + tx_axis_tdata = Signal(intbv(0)[8:]) + tx_axis_tvalid = Signal(bool(0)) + tx_axis_tlast = Signal(bool(0)) + tx_axis_tuser = Signal(bool(0)) + rx_axis_tready = Signal(bool(0)) + mii_rx_clk = Signal(bool(0)) + mii_rxd = Signal(intbv(0)[4:]) + mii_rx_dv = Signal(bool(0)) + mii_rx_er = Signal(bool(0)) + mii_tx_clk = Signal(bool(0)) + ifg_delay = Signal(intbv(0)[8:]) + + # Outputs + tx_axis_tready = Signal(bool(0)) + rx_axis_tdata = Signal(intbv(0)[8:]) + rx_axis_tvalid = Signal(bool(0)) + rx_axis_tlast = Signal(bool(0)) + rx_axis_tuser = Signal(bool(0)) + mii_txd = Signal(intbv(0)[4:]) + mii_tx_en = Signal(bool(0)) + mii_tx_er = Signal(bool(0)) + tx_error_underflow = Signal(bool(0)) + tx_fifo_overflow = Signal(bool(0)) + tx_fifo_bad_frame = Signal(bool(0)) + tx_fifo_good_frame = Signal(bool(0)) + rx_error_bad_frame = Signal(bool(0)) + rx_error_bad_fcs = Signal(bool(0)) + rx_fifo_overflow = Signal(bool(0)) + rx_fifo_bad_frame = Signal(bool(0)) + rx_fifo_good_frame = Signal(bool(0)) + + # sources and sinks + axis_source_pause = Signal(bool(0)) + axis_sink_pause = Signal(bool(0)) + + mii_source = mii_ep.MIISource() + + mii_source_logic = mii_source.create_logic( + mii_rx_clk, + rst, + txd=mii_rxd, + tx_en=mii_rx_dv, + tx_er=mii_rx_er, + name='mii_source' + ) + + mii_sink = mii_ep.MIISink() + + mii_sink_logic = mii_sink.create_logic( + mii_tx_clk, + rst, + rxd=mii_txd, + rx_dv=mii_tx_en, + rx_er=mii_tx_er, + name='mii_sink' + ) + + axis_source = axis_ep.AXIStreamSource() + + axis_source_logic = axis_source.create_logic( + logic_clk, + logic_rst, + tdata=tx_axis_tdata, + tvalid=tx_axis_tvalid, + tready=tx_axis_tready, + tlast=tx_axis_tlast, + tuser=tx_axis_tuser, + pause=axis_source_pause, + name='axis_source' + ) + + axis_sink = axis_ep.AXIStreamSink() + + axis_sink_logic = axis_sink.create_logic( + logic_clk, + logic_rst, + tdata=rx_axis_tdata, + tvalid=rx_axis_tvalid, + tready=rx_axis_tready, + tlast=rx_axis_tlast, + tuser=rx_axis_tuser, + pause=axis_sink_pause, + name='axis_sink' + ) + + # DUT + if os.system(build_cmd): + raise Exception("Error running build command") + + dut = Cosimulation( + "vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + + logic_clk=logic_clk, + logic_rst=logic_rst, + + tx_axis_tdata=tx_axis_tdata, + tx_axis_tvalid=tx_axis_tvalid, + tx_axis_tready=tx_axis_tready, + tx_axis_tlast=tx_axis_tlast, + tx_axis_tuser=tx_axis_tuser, + + rx_axis_tdata=rx_axis_tdata, + rx_axis_tready=rx_axis_tready, + rx_axis_tvalid=rx_axis_tvalid, + rx_axis_tlast=rx_axis_tlast, + rx_axis_tuser=rx_axis_tuser, + + mii_rx_clk=mii_rx_clk, + mii_rxd=mii_rxd, + mii_rx_dv=mii_rx_dv, + mii_rx_er=mii_rx_er, + + mii_tx_clk=mii_tx_clk, + mii_txd=mii_txd, + mii_tx_en=mii_tx_en, + mii_tx_er=mii_tx_er, + + tx_error_underflow=tx_error_underflow, + tx_fifo_overflow=tx_fifo_overflow, + tx_fifo_bad_frame=tx_fifo_bad_frame, + tx_fifo_good_frame=tx_fifo_good_frame, + rx_error_bad_frame=rx_error_bad_frame, + rx_error_bad_fcs=rx_error_bad_fcs, + rx_fifo_overflow=rx_fifo_overflow, + rx_fifo_bad_frame=rx_fifo_bad_frame, + rx_fifo_good_frame=rx_fifo_good_frame, + + ifg_delay=ifg_delay + ) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + logic_clk.next = not clk + + phy_clk_hp = Signal(int(20)) + + @instance + def phy_clk_gen(): + while True: + yield delay(int(phy_clk_hp)) + mii_rx_clk.next = not mii_rx_clk + mii_tx_clk.next = not mii_rx_clk + + rx_error_bad_frame_asserted = Signal(bool(0)) + rx_error_bad_fcs_asserted = Signal(bool(0)) + + @always(clk.posedge) + def monitor(): + if (rx_error_bad_frame): + rx_error_bad_frame_asserted.next = 1 + if (rx_error_bad_fcs): + rx_error_bad_fcs_asserted.next = 1 + + clk_enable_rate = Signal(int(0)) + clk_enable_div = Signal(int(0)) + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + logic_rst.next = 1 + yield clk.posedge + rst.next = 0 + logic_rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + ifg_delay.next = 12 + + # testbench stimulus + + for rate in [20, 200]: + phy_clk_hp.next = rate + + yield delay(1000) + + yield clk.posedge + print("test 1: test rx packet") + current_test.next = 1 + + test_frame = eth_ep.EthFrame() + test_frame.eth_dest_mac = 0xDAD1D2D3D4D5 + test_frame.eth_src_mac = 0x5A5152535455 + test_frame.eth_type = 0x8000 + test_frame.payload = bytearray(range(32)) + test_frame.update_fcs() + + axis_frame = test_frame.build_axis_fcs() + + mii_source.send(b'\x55\x55\x55\x55\x55\x55\x55\xD5'+bytearray(axis_frame)) + + yield axis_sink.wait() + rx_frame = axis_sink.recv() + + eth_frame = eth_ep.EthFrame() + eth_frame.parse_axis(rx_frame) + eth_frame.update_fcs() + + assert eth_frame == test_frame + + yield delay(100) + + yield clk.posedge + print("test 2: test tx packet") + current_test.next = 2 + + test_frame = eth_ep.EthFrame() + test_frame.eth_dest_mac = 0xDAD1D2D3D4D5 + test_frame.eth_src_mac = 0x5A5152535455 + test_frame.eth_type = 0x8000 + test_frame.payload = bytearray(range(32)) + test_frame.update_fcs() + + axis_frame = test_frame.build_axis() + + axis_source.send(axis_frame) + + yield mii_sink.wait() + rx_frame = mii_sink.recv() + + assert rx_frame.data[0:8] == bytearray(b'\x55\x55\x55\x55\x55\x55\x55\xD5') + + eth_frame = eth_ep.EthFrame() + eth_frame.parse_axis_fcs(rx_frame.data[8:]) + + print(hex(eth_frame.eth_fcs)) + print(hex(eth_frame.calc_fcs())) + + assert len(eth_frame.payload.data) == 46 + assert eth_frame.eth_fcs == eth_frame.calc_fcs() + assert eth_frame.eth_dest_mac == test_frame.eth_dest_mac + assert eth_frame.eth_src_mac == test_frame.eth_src_mac + assert eth_frame.eth_type == test_frame.eth_type + assert eth_frame.payload.data.index(test_frame.payload.data) == 0 + + yield delay(100) + + raise StopSimulation + + return instances() + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/tb/test_eth_mac_mii_fifo.v b/tb/test_eth_mac_mii_fifo.v new file mode 100644 index 000000000..5eb2eff45 --- /dev/null +++ b/tb/test_eth_mac_mii_fifo.v @@ -0,0 +1,167 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for eth_mac_mii_fifo + */ +module test_eth_mac_mii_fifo; + +// Parameters +parameter TARGET = "SIM"; +parameter CLOCK_INPUT_STYLE = "BUFIO2"; +parameter ENABLE_PADDING = 1; +parameter MIN_FRAME_LENGTH = 64; +parameter TX_FIFO_ADDR_WIDTH = 9; +parameter RX_FIFO_ADDR_WIDTH = 9; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg logic_clk = 0; +reg logic_rst = 0; +reg [7:0] tx_axis_tdata = 0; +reg tx_axis_tvalid = 0; +reg tx_axis_tlast = 0; +reg tx_axis_tuser = 0; +reg rx_axis_tready = 0; +reg mii_rx_clk = 0; +reg [3:0] mii_rxd = 0; +reg mii_rx_dv = 0; +reg mii_rx_er = 0; +reg mii_tx_clk = 0; +reg [7:0] ifg_delay = 0; + +// Outputs +wire tx_axis_tready; +wire [7:0] rx_axis_tdata; +wire rx_axis_tvalid; +wire rx_axis_tlast; +wire rx_axis_tuser; +wire [3:0] mii_txd; +wire mii_tx_en; +wire mii_tx_er; +wire tx_error_underflow; +wire tx_fifo_overflow; +wire tx_fifo_bad_frame; +wire tx_fifo_good_frame; +wire rx_error_bad_frame; +wire rx_error_bad_fcs; +wire rx_fifo_overflow; +wire rx_fifo_bad_frame; +wire rx_fifo_good_frame; + +initial begin + // myhdl integration + $from_myhdl( + clk, + rst, + current_test, + logic_clk, + logic_rst, + tx_axis_tdata, + tx_axis_tvalid, + tx_axis_tlast, + tx_axis_tuser, + rx_axis_tready, + mii_rx_clk, + mii_rxd, + mii_rx_dv, + mii_rx_er, + mii_tx_clk, + ifg_delay + ); + $to_myhdl( + tx_axis_tready, + rx_axis_tdata, + rx_axis_tvalid, + rx_axis_tlast, + rx_axis_tuser, + mii_txd, + mii_tx_en, + mii_tx_er, + tx_error_underflow, + tx_fifo_overflow, + tx_fifo_bad_frame, + tx_fifo_good_frame, + rx_error_bad_frame, + rx_error_bad_fcs, + rx_fifo_overflow, + rx_fifo_bad_frame, + rx_fifo_good_frame + ); + + // dump file + $dumpfile("test_eth_mac_mii_fifo.lxt"); + $dumpvars(0, test_eth_mac_mii_fifo); +end + +eth_mac_mii_fifo #( + .TARGET(TARGET), + .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE), + .ENABLE_PADDING(ENABLE_PADDING), + .MIN_FRAME_LENGTH(MIN_FRAME_LENGTH), + .TX_FIFO_ADDR_WIDTH(TX_FIFO_ADDR_WIDTH), + .RX_FIFO_ADDR_WIDTH(RX_FIFO_ADDR_WIDTH) +) +UUT ( + .rst(rst), + .logic_clk(logic_clk), + .logic_rst(logic_rst), + .tx_axis_tdata(tx_axis_tdata), + .tx_axis_tvalid(tx_axis_tvalid), + .tx_axis_tready(tx_axis_tready), + .tx_axis_tlast(tx_axis_tlast), + .tx_axis_tuser(tx_axis_tuser), + .rx_axis_tdata(rx_axis_tdata), + .rx_axis_tvalid(rx_axis_tvalid), + .rx_axis_tready(rx_axis_tready), + .rx_axis_tlast(rx_axis_tlast), + .rx_axis_tuser(rx_axis_tuser), + .mii_rx_clk(mii_rx_clk), + .mii_rxd(mii_rxd), + .mii_rx_dv(mii_rx_dv), + .mii_rx_er(mii_rx_er), + .mii_tx_clk(mii_tx_clk), + .mii_txd(mii_txd), + .mii_tx_en(mii_tx_en), + .mii_tx_er(mii_tx_er), + .tx_error_underflow(tx_error_underflow), + .tx_fifo_overflow(tx_fifo_overflow), + .tx_fifo_bad_frame(tx_fifo_bad_frame), + .tx_fifo_good_frame(tx_fifo_good_frame), + .rx_error_bad_frame(rx_error_bad_frame), + .rx_error_bad_fcs(rx_error_bad_fcs), + .rx_fifo_overflow(rx_fifo_overflow), + .rx_fifo_bad_frame(rx_fifo_bad_frame), + .rx_fifo_good_frame(rx_fifo_good_frame), + .ifg_delay(ifg_delay) +); + +endmodule