diff --git a/hw/top_chip/chip_mocha_genesys2.core b/hw/top_chip/chip_mocha_genesys2.core index 36bf76283..737fe2c88 100644 --- a/hw/top_chip/chip_mocha_genesys2.core +++ b/hw/top_chip/chip_mocha_genesys2.core @@ -9,7 +9,7 @@ filesets: depend: - lowrisc:mocha:top_chip_system - lowrisc:prim_xilinx:all - - lowrisc::ethernet + - lowrisc:ethernet:axi_top - lowrisc:mocha:rng - pulp-platform.org::axi files: diff --git a/hw/top_chip/data/placement_genesys2.xdc b/hw/top_chip/data/placement_genesys2.xdc index d82a310d6..a987747a4 100644 --- a/hw/top_chip/data/placement_genesys2.xdc +++ b/hw/top_chip/data/placement_genesys2.xdc @@ -1,7 +1,3 @@ ## Copyright lowRISC contributors (COSMIC project). ## Licensed under the Apache License, Version 2.0, see LICENSE for details. ## SPDX-License-Identifier: Apache-2.0 - -## Ethernet MAC Placement Constraints -## Helps to pass timing reliably for a path with 2ns setup requirement -set_property LOC SLICE_X152Y75 [get_cells u_eth_wrapper/u_framing_top/rgmii_soc1/core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_2_reg] diff --git a/hw/top_chip/data/timing_genesys2.xdc b/hw/top_chip/data/timing_genesys2.xdc index 06c730812..9676c16e4 100644 --- a/hw/top_chip/data/timing_genesys2.xdc +++ b/hw/top_chip/data/timing_genesys2.xdc @@ -16,6 +16,12 @@ create_clock -period 8.000 -waveform {0 4} -name eth_rxck_pin [get_ports eth_rx_ ## Ethernet PHY Rx Clock Asynchronous with All Other Clocks set_clock_groups -asynchronous -group [get_clocks eth_rxck_pin -include_generated_clocks]; +## Ethernet logic/TX clock +create_clock -period 8.000 -waveform {0 4} -name eth_main_clk [get_nets clk_125m]; + +# Prevent synthesis tool from trying to time the crossing from clk_125m to the main system clock (50MHz) +set_clock_groups -asynchronous -group [get_clocks eth_main_clk -include_generated_clocks]; + ## Tag Controller to MIG AXI CDC Constraints ## Removed since the custom attribute async cannot be used to select pins, ## and the CDC paths can be timed without difficulty diff --git a/hw/top_chip/ip/gen_xilinx_ip.tcl b/hw/top_chip/ip/gen_xilinx_ip.tcl index e68a704d6..5879ba7d5 100644 --- a/hw/top_chip/ip/gen_xilinx_ip.tcl +++ b/hw/top_chip/ip/gen_xilinx_ip.tcl @@ -2,8 +2,14 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +# Enable XPMs to be used for instantiating Xilinx FPGA primitives +auto_detect_xpm + +## MIG IP +# Generate the MIG IP for the Genessys2 board to interface with off-chip memory create_ip -name mig_7series -vendor xilinx.com -library ip -version 4.2 -module_name u_xlnx_mig_7_ddr3 +# Workaround because FuseSoC expects a different file structure to Vivado exec cp ./src/lowrisc_mocha_chip_mocha_genesys2_0/ip/mig_genesys2_mocha.prj ./lowrisc_mocha_chip_mocha_genesys2_0.srcs/sources_1/ip/u_xlnx_mig_7_ddr3/mig_genesys2_mocha.prj set_property -dict [list \ diff --git a/hw/top_chip/rtl/chip_mocha_genesys2.sv b/hw/top_chip/rtl/chip_mocha_genesys2.sv index 0d533c525..d8b7c6c60 100644 --- a/hw/top_chip/rtl/chip_mocha_genesys2.sv +++ b/hw/top_chip/rtl/chip_mocha_genesys2.sv @@ -572,10 +572,6 @@ module chip_mocha_genesys2 #( .default_mst_port_i ('0) ); - // Async reset for ethernet PHY - // NOTE: Using eth_rst_n_sync_125m to reset the PHY breaks Tx - assign eth_phyrst_n = fpga_rst_n_sync_cfg; - // Ethernet MAC wrapper ethernet_wrapper u_eth_wrapper ( // Clocking and reset @@ -601,7 +597,10 @@ module chip_mocha_genesys2 #( .eth_rgmii_tx_en_o (eth_tx_en), .eth_rgmii_tx_d_o (eth_tx_d), .eth_rgmii_mdio_io (eth_mdio), - .eth_rgmii_mdc_o (eth_mdc) + .eth_rgmii_mdc_o (eth_mdc), + + // Ethernet PHY reset + .eth_phy_reset_no (eth_phyrst_n) ); // SW-DV window: read-only HW_ID register. diff --git a/hw/top_chip/rtl/ethernet_wrapper.sv b/hw/top_chip/rtl/ethernet_wrapper.sv index f2f4a834e..cb494728f 100644 --- a/hw/top_chip/rtl/ethernet_wrapper.sv +++ b/hw/top_chip/rtl/ethernet_wrapper.sv @@ -28,28 +28,20 @@ module ethernet_wrapper ( output logic eth_rgmii_tx_en_o, output logic [3:0] eth_rgmii_tx_d_o, inout logic eth_rgmii_mdio_io, - output logic eth_rgmii_mdc_o + output logic eth_rgmii_mdc_o, + + // PHY reset + output logic eth_phy_reset_no ); // AXI signals from CDC FIFO to axi_to_mem, synchronous to clk_125m - top_pkg::axi_dev_req_t eth_125m_req; - top_pkg::axi_dev_resp_t eth_125m_resp; - - // Framing top 64-bit memory format signals - logic eth_en; - logic [ top_pkg::AxiAddrWidth-1:0] eth_addr; - logic [ top_pkg::AxiDataWidth-1:0] eth_wdata; - logic [top_pkg::AxiDataWidth/8-1:0] eth_be; - logic eth_we_d; - logic eth_we_q; - logic eth_rvalid; - logic [ top_pkg::AxiDataWidth-1:0] eth_rdata; + top_pkg::axi_dev_req_t axi_125m_req; + top_pkg::axi_dev_resp_t axi_125m_rsp; - // MDIO buffer control signals - logic eth_mdio_i; - logic eth_mdio_o; - logic eth_mdio_oe; + /////////////////////////////// + // CDC to the Ethernet clock // + /////////////////////////////// - // Async AXI FIFO from clk_axi_i to clk_125m_i + // Synchronise AXI bus from clk_axi_i to clk_125m_i axi_cdc #( .aw_chan_t ( top_pkg::axi_dev_aw_chan_t ), .w_chan_t ( top_pkg::axi_w_chan_t ), @@ -67,93 +59,76 @@ module ethernet_wrapper ( .src_resp_o (axi_resp_o), .dst_clk_i (clk_125m_i), .dst_rst_ni (rst_eth_ni), - .dst_req_o (eth_125m_req), - .dst_resp_i (eth_125m_resp) + .dst_req_o (axi_125m_req), + .dst_resp_i (axi_125m_rsp) ); - // AXI to mem for framing top - axi_to_mem #( - .axi_req_t ( top_pkg::axi_dev_req_t ), - .axi_resp_t ( top_pkg::axi_dev_resp_t ), - .AddrWidth ( top_pkg::AxiAddrWidth ), - .DataWidth ( top_pkg::AxiDataWidth ), - .IdWidth ( top_pkg::AxiDevIdWidth ), - .NumBanks ( 1 ) - ) u_eth_axi_to_mem ( + // The interrupt line needs to be synchronised back to the main clock domain + logic ethernet_irq_125m_q; // First flop the IRQ on source domain to deglitch + prim_flop u_eth_irq_flop ( .clk_i (clk_125m_i), .rst_ni (rst_eth_ni), - - // AXI interface. - .busy_o ( ), - .axi_req_i (eth_125m_req), - .axi_resp_o (eth_125m_resp), - - // Memory interface. - .mem_req_o (eth_en), - .mem_gnt_i (1'b1), - .mem_addr_o (eth_addr), - .mem_wdata_o (eth_wdata), - .mem_strb_o (eth_be), - .mem_atop_o ( ), - .mem_we_o (eth_we_d), - .mem_rvalid_i (eth_rvalid), - .mem_rdata_i (eth_we_q ? 64'hBADDBADDBADDBADD : eth_rdata) + .d_i (ethernet_irq_125m), + .q_o (ethernet_irq_125m_q) + ); + prim_flop_2sync #( + .Width (1), + .ResetValue (1'b0) + ) u_eth_irq_sync ( + .clk_i (clk_axi_i), + .rst_ni (rst_axi_ni), + .d_i (ethernet_irq_125m_q), + .q_o (ethernet_irq_o) ); - // Single-cycle read response. - always_ff @(posedge clk_125m_i or negedge rst_eth_ni) begin - if (!rst_eth_ni) begin - eth_rvalid <= '0; - eth_we_q <= '0; - end else begin - eth_rvalid <= eth_en; // Generate rvalid strobes even for writes - eth_we_q <= eth_we_d; - end - end - - // Packet framing top - framing_top u_framing_top ( - // Memory interface. - .msoc_clk (clk_125m_i), // Some internal logic assumes msoc_clk == clk_int - .core_lsu_addr (eth_addr[14:0]), - .core_lsu_wdata (eth_wdata), - .core_lsu_be (eth_be), - .ce_d (eth_en), - .we_d (eth_en & eth_we_d), - .framing_sel (eth_en), - .framing_rdata (eth_rdata), - .rst_int (!rst_eth_ni), - - // Clocks. - .clk_int (clk_125m_i), // 125 MHz in-phase - .clk90_int (clk_125m_quad_i), // 125 MHz quadrature - .clk_200_int (clk_200m_i), + ////////////////////////////// + // Instantiate ethernet_top // + /////////////////////////////// - // 1000BASE-T RGMII PHY interface. - .phy_rx_clk (eth_rgmii_rx_clk_i), - .phy_rxd (eth_rgmii_rx_d_i), - .phy_rx_ctl (eth_rgmii_rx_ctl_i), - .phy_tx_clk (eth_rgmii_tx_clk_o), - .phy_txd (eth_rgmii_tx_d_o), - .phy_tx_ctl (eth_rgmii_tx_en_o), - .phy_reset_n ( ), // Do not use rst_int for PHY reset - .phy_int_n ( ), - .phy_pme_n ( ), - .phy_mdc (eth_rgmii_mdc_o), - .phy_mdio_i (eth_mdio_i), - .phy_mdio_o (eth_mdio_o), - .phy_mdio_oe (eth_mdio_oe), + ethernet_pkg::eth_rgmii_rx_t eth_rgmii_rx; + ethernet_pkg::eth_rgmii_tx_t eth_rgmii_tx; + ethernet_pkg::eth_rgmii_mdio_in_t eth_mdio_in; // actually just an alias for logic but I like the consistent naming convention + ethernet_pkg::eth_rgmii_mdio_out_t eth_mdio_out; - // Interrupt out. - .eth_irq (ethernet_irq) + ethernet_top_axi #( + .TARGET ( "XILINX" ), + .axi_req_t ( top_pkg::axi_dev_req_t ), + .axi_rsp_t ( top_pkg::axi_dev_resp_t ) + ) ethernet_inst ( + // Clocking and reset + .clk_125M_i (clk_125m_i), // Main clock - used by memory interface and as 125 MHz ethernet in-phase clock + .rst_ni (rst_eth_ni), // Main reset, deassertion synchronous to clk_125m_i + .clk_125M_quad_i (clk_125m_quad_i), // 125 MHz ethernet quadrature clock (used by MAC) + .clk_200M_i (clk_200m_i), // 200 MHz IDELAYCTRL reference clock + .axi_req_i (axi_125m_req), // Synchronous to clk_125M_i + .axi_rsp_o (axi_125m_rsp), // Synchronous to clk_125M_i + .eth_rgmii_rx_i (eth_rgmii_rx), + .eth_rgmii_tx_o (eth_rgmii_tx), + .eth_rgmii_mdio_i (eth_mdio_in), + .eth_rgmii_mdio_o (eth_mdio_out), + .ethernet_irq_o (ethernet_irq_125m), + .phy_reset_no (eth_phy_reset_no) ); - // MDIO bidirectional IO buffer + ////////////////////////// + // RGMII signal mapping // + ////////////////////////// + assign eth_rgmii_rx.clk = eth_rgmii_rx_clk_i; + assign eth_rgmii_rx.ctl = eth_rgmii_rx_ctl_i; + assign eth_rgmii_rx.d = eth_rgmii_rx_d_i; + assign eth_rgmii_tx_clk_o = eth_rgmii_tx.clk; + assign eth_rgmii_tx_en_o = eth_rgmii_tx.en; + assign eth_rgmii_tx_d_o = eth_rgmii_tx.d; + + ////////////////////////////////// + // MDIO bidirectional IO buffer // + ////////////////////////////////// IOBUF u_mdio_iobuf ( - .O (eth_mdio_i), // Buffer output + .O (eth_mdio_in), // Buffer output .IO (eth_rgmii_mdio_io), // Buffer inout port (connect directly to top-level port) - .I (eth_mdio_o), // Buffer input - .T (~eth_mdio_oe) // 3-state enable input, high=input, low=output + .I (eth_mdio_out.o), // Buffer input + .T (~eth_mdio_out.oen) // 3-state enable input, high=input, low=output ); + assign eth_rgmii_mdc_o = eth_mdio_out.c; endmodule diff --git a/hw/vendor/ethernet.core b/hw/vendor/ethernet.core deleted file mode 100644 index 815ee0244..000000000 --- a/hw/vendor/ethernet.core +++ /dev/null @@ -1,35 +0,0 @@ -CAPI=2: -# Copyright lowRISC contributors (COSMIC project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -name: "lowrisc::ethernet" -description: "1GbE ethernet MAC" - -filesets: - files_rtl: - depend: - - lowrisc:prim_generic:flop_2sync - files: - - ethernet/axis_async_fifo.sv - - ethernet/axis_gmii_rx.sv - - ethernet/axis_gmii_tx.sv - - ethernet/dualmem_widen.sv - - ethernet/dualmem_widen8.sv - - ethernet/eth_mac_1g.sv - - ethernet/eth_mac_1g_rgmii.sv - - ethernet/eth_mac_1g_rgmii_fifo.sv - - ethernet/framing_top.sv - - ethernet/iddr.sv - - ethernet/oddr.sv - - ethernet/rgmii_core.sv - - ethernet/rgmii_lfsr.sv - - ethernet/rgmii_phy_if.sv - - ethernet/rgmii_soc.sv - - ethernet/ssio_ddr_in.sv - file_type: systemVerilogSource - -targets: - default: - filesets: - - files_rtl diff --git a/hw/vendor/ethernet.lock.hjson b/hw/vendor/ethernet.lock.hjson index eaef405de..df9f0b9f2 100644 --- a/hw/vendor/ethernet.lock.hjson +++ b/hw/vendor/ethernet.lock.hjson @@ -8,7 +8,7 @@ { upstream: { - url: https://github.com/lowRISC/ariane-ethernet.git - rev: 1a14e8d98a0160f06e31e7a903082d0c8d293720 + url: https://github.com/lowRISC/ethernet.git + rev: 2dc3a8b12da4006f343d6be82872dd80990cab6f } } diff --git a/hw/vendor/ethernet.vendor.hjson b/hw/vendor/ethernet.vendor.hjson index 6a2f312be..9c83ee1f0 100644 --- a/hw/vendor/ethernet.vendor.hjson +++ b/hw/vendor/ethernet.vendor.hjson @@ -2,13 +2,26 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 { - name: "ethernet", - target_dir: "ethernet", + name: "ethernet", + target_dir: "ethernet", - upstream: { - url: "https://github.com/lowRISC/ariane-ethernet.git", - rev: "master", - }, + upstream: { + url: "https://github.com/lowRISC/ethernet.git", + rev: "master", + }, - exclude_from_upstream: [] + # Only bring in the hardware, RDL, and README + # This excludes software, dev utilities, tests, and documentation + mapping: [ + {from: "src/hw", to: "hw"}, + {from: "src/ethernet.rdl", to: "ethernet.rdl"}, + {from: "README.md", to: "README.md"} + ], + + exclude_from_upstream: [ + "*.svg", + "*.md", + "*hw/*_eth/synth", # exclude trial synth flow + "*hw/*_eth/verif", # exclude verif + ] } diff --git a/hw/vendor/ethernet/.gitignore b/hw/vendor/ethernet/.gitignore deleted file mode 100644 index 225f9711e..000000000 --- a/hw/vendor/ethernet/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*~ -test/ diff --git a/hw/vendor/ethernet/README.md b/hw/vendor/ethernet/README.md index a1c5e911b..716f05b87 100644 --- a/hw/vendor/ethernet/README.md +++ b/hw/vendor/ethernet/README.md @@ -1,7 +1,311 @@ -# DEPRECATION NOTICE +This Ethernet project provides a memory-mapped Ethernet device for FPGA. +The device handles MAC and exposes an RGMII interface to connect to a PHY off-chip. -**This repository is deprecated and unsupported by lowRISC, we keep the repository around for archival purposes only.** +Two memories are included: a write-only TX buffer and a read-only RX buffer. +In addition to a set of registers for control, status, and configuration. -# Original README content -This is a port of the Alex Forencich GHz RGMII Ethernet MAC to the Digilent Genesys2 board. -It is intended for use with https://github.com/pulp-platform/ariane (a RISCV Linux-capable soft core). +The top level uses a simple memory interface with address spaces for each buffer and for the registers. + + +> **Block diagram of ```ethernet_top``` and ```ethernet_top_axi```** +> ![Ethernet top block diagram](doc/block_diagram.svg) +> Bidirectional arrows indicate addressable buses with a host and device end. +> +> The ```mem_*``` interfaces adhere to the simple memory interface described in ```mem_if_utils```. Each has a ```req``` (request) and ```rsp``` (response) struct. + +# Structure +The design is broadly composed of seven top-level blocks as shown above. These are: + +|Block|Top File|Description| +|---|---|---| +| AXI to Mem | `axi_to_detailed_mem.sv` | Accept an AXI4+ATOPs interface and translate to a simple memory interface. Provided by Pulp platform. | +| Memory map | `ethernet_mem_map.sv` | Accept one memory interface and map to four downstream memory interface according to the address MSBs. | +| TX Framing | `ethernet_tx_framing.sv` | Exposes a write-only memory interface for writing to the data buffer. Config/status signals are mapped to registers in the CSRs block. Also includes an FSM to begin sending packet data over AXI-Stream -- the FSM's is started by a pulse on the `tx_kick` line. | +| RX Framing | `ethernet_rx_framing.sv` | Wrapper around `axis_pkt_ring_buffer` which receives an AXI Stream and splits it into packets. Stream data is stored in a ring buffer and packet metadata (including pointers to the ring buffer) is stored in a FIFO. A pulse on `rx_pop` will remove one entry from this FIFO. Two read-only memory interfaces provide access to the ring buffer and the metadata table. | +| Loopback | `ethernet_loopback.sv` | Wrapper around `axis_pkt_mux`. This is slightly more complicated than a simple MUX because it has to guarantee that a packet is never spliced when a switch happens. Therefore, any in-progress packet on the current stream must complete before disabling that stream, then any in-progress packet on the new stream complete before enabling that stream. | +| MAC | `mac_wrapper.sv` | Wraps the Alex Forencich MAC `rgmii_soc.sv`, with modifications. Translates the two AXI Streams (TX/RX) to an RGMII interface. This block is quite technology-dependent because of precise interface timing and DDR. | + +# Basic Operation + +The design is broadly split into TX (transmit) and RX (receive) portions. Each has a buffer for storing packets. + +The table below shows sections of an Ethernet frame and which should appear in the RX/TX buffers. + +| Section | Length (bytes) | Visible to Host | +|---------------------------|----------------|--------------------| +| Preamble | 7 | No | +| Start Frame Delimeter | 1 | No | +| Destination MAC Address | 6 | Yes | +| Source MAC Address | 6 | Yes | +| 802.1Q Tag | 0 or 4 | Yes | +| EtherType or length | 2 | Yes | +| Payload | 42-1500 | Yes | +| Frame Check Sequence | 4 | No | +| Inter-Packet Gap | 12 | No | + +Items which say 'no' will be automatically appended to the TX and removed from the RX by the hardware. Items which say 'yes' should be written to the TX buffer when transmitting, and will appear in the RX buffer for received packets. + +Simultaneous transmission and receiving is supported by the Ethernet IP, provided the PHY supports it as well. + +## Transmission (TX) +TX is operated as follows: +- Data is written in 64-bit (8-byte) words to the TX buffer +- A register write to ```TX_CTRL``` triggers the TX to begin (from byte 0 in the buffer) - the write data includes the length of the transmission in bytes. The TX begins as a write side effect. +- A ```tx_busy``` flag will be set in the status register while transmitting. +- A ```tx_done``` flag will be set in the ```INTR_STATE``` register when done (write a 1 to clear this). + +> **NOTE:** Data is written to the TX buffer in 64-bit words, but the packet length should always be indicated in bytes. Any extra bytes in the final written word will be ignored. + +## Receiving (RX) +The Ethernet interface is always monitoring for incoming packets. + +Packets will be dropped unless any of the following criteria are met: +- The first 6 bytes (destination MAC address) are the broadcast MAC address +- The first 6 bytes (destination MAC address) are a multicast MAC address (i.e. the first three bytes are 0x01005E) +- The first 6 bytes (destination MAC address) match the value in the ```mac_addr``` CSR +- The receiver is in 'promiscuous' mode + +Incoming payload data is stored in a ring buffer with a size of 8192 bytes (enough for 5.4 max-size Ethernet packets). + +A second memory region stores metadata about each packet - it has capacity for 8 packets. The metadata captured includes: +- A pointer to the payload data in the ring buffer +- The length (in bytes) of the received data +- The reason for capturing the packet (one of the four above) + +> **NOTE:** Packets must be **explicitly** popped once the controller has seen them. This is accomplished by writing 1 to the ```pop``` register. This allows the hardware to reuse the memory location. You can only pop the oldest packet. + +> **NOTE:** The oldest packet in the buffer is always stored at memory location 0 in the metadata table. Usually, the software should always read the metadata for packet 0, then pop that packet, and read the new packet 0, however you can also 'peek' at newer packets by reading later indices. + +## Loopback +The Ethernet IP supports a loopback mode for testability. The loopback path includes the TX/RX framing mechanisms but not the MAC and PHY. + +The loopback multiplexes the RX path. Therefore, while in loopback mode, RX packets arriving from the PHY will be ignored. + +The TX path is still NOT masked during loopback i.e. packets you write in loopback mode are still exposed to the PHY. + +## Interrupts +The interrupt state register has four status-type interrupts and three event-type interrupts. + +### Status-type interrupts +These can only be cleared by servicing them (or masking them). + +- ```rx_not_empty``` - one or more packets are available in the RX buffer. +- ```rx_table_almost_full``` - 6 or more packets are present in the RX metadata table. +- ```rx_table_full``` - the RX metadata table is full (8 packets). +- ```rx_buf_almost_full``` - the RX data buffer is almost full (remaining space is less than required to fit a max-size packet). + +### Event-type interrupts +These are set by hardware events and cleared by writing a 1 to the corresponding bit positions in the interrupt state register. + +- ```packet_lost``` - set when a packet is dropped due to full buffer +- ```tx_done``` - set when a transmission completes +- ```manual_irq``` - set by writing 1 to the interrupt test register + +### Interrupt Mask +The interrupt mask register allows the controller to configure which interrupts it wishes to respond to. + +A common configuration would be to: +- enable interrupts for the metadata table or RX buffer being almost full +- enable the manual interrupt for testing +- disable other interrupts + +Then write an interrupt service routine to handle and pop all packets in the buffer whenever it is almost full. + +The interrupt state register will always reflect the statuses outlined above regardless of the interrupt mask. However, the interrupt request line will only be asserted subject to the mask register. + + + +## Registers +The up-to-date description of the register map is given in ```ethernet.rdl```. This serves as a specification, documentation, and the input for generators. + +Additionally, note the following behaviours: + +### RX Reconfiguring +Reconfiguring the RX path includes: +* Writing the MAC address register. +* Modifying the ```promiscuous``` or ```loopback``` fields. + +Popping a packet in the buffer is NOT a reconfiguration and can be done safely at any time. + +If RX is reconfigured during a TX, the TX will not be affected. + +**Reconfiguration Hazards:** +* If the MAC address or 'promiscuous' config is changed while a packet is mid-receipt, the behaviour for that packet is not defined (it will depend on specifc internal timing) i.e. it may arrive or not arrive in the RX buffer. +* When the 'loopback' configuration changes, it may not take effect immediately. Any packets on the previously selected mode will be allowed to complete first, then any packets which are ongoing on the newly select path will complete before the RX begins listening on the new path. In the worst case, this could result in a delay of just under two packets (~3000 clock cycles) before the change takes effect. + +A spliced or incomplete packet should never appear in the RX buffer. + +### TX Reconfiguring +Reconfiguring the TX path includes: +* Writing the ```TX_CTRL``` register (where you specify the length of the TX packet) + +The MAC address registers do not affect TX. Source MAC addresses must be included in the payload. + +A write to ```TX_CTRL``` while another TX is in progress is forbidden. The host should check that the TX is free before writing to it, or guarantee this through other means. Violating this rule may result in a bad packet being transmitted. + +Similarly, a write to the TX buffer while a TX is in progress is forbidden. + + + + +## Hardware Abstraction Layer +A hardware abstraction layer is provided for this design under `src/sw/ethernet.c`. + +Documentation for it is included in the [programmer's guide](doc/programmers_guide.md). + +# Development + +## Requirements +- Ensure Vivado is on your PATH (including 'xsim' and 'xelab'). +- Ensure Verilator is on your PATH + +This project has dependencies from lowRISC's ```OpenTitan``` for primitives. FuseSoC will automatically retrieve these based on the library configured in ```fusesoc.conf```, but you can override this behaviour if you prefer to manage your own FuseSoC libraries. + +## Targets +The table below outlines two top-level cores and various targets available for them. + +| SV Module | `ethernet_top` | `ethernet_top_axi` | +|---------------------|--------------------------|------------------------------| +| FuseSoC Core | `lowrisc:ethernet:top` | `lowrisc:ethernet:axi_top` | +| Description | Basic Ethernet top with a simple memory interface | Wrapper around `ethernet_top` with an AXI interface | +| **FuseSoC Targets** | | | +| `target=sim` | Vivado simulation | Verilator/CocoTB simulation | +| `target=trial_synth`| - | Vivado synthesis | +| `target=lint` | Verilator lint | Verilator lint | + +To invoke a flow, run: +```sh +uv run fusesoc run --target= +``` + +For example: +```sh +uv run fusesoc run --target=sim lowrisc:ethernet:axi_top +``` + +Make sure you run FuseSoC from the same directory that contains ```fusesoc.conf```. FuseSoC will look at ```fusesoc.conf``` to find the Ethernet core and its dependencies. + +## Testbenches +There are two testbenches available for this project: +* ```ethernet_top``` can be tested with a SystemVerilog testbench. Currently this test suite is not comprehensive but is fast and useful for smoke tests. +* ```ethernet_top_axi``` has a testbench driven using CocoTB and an AXI driver library. This is intended to be the main test suite and targets 100% coverage. + +It is recommended you run both test suites because there can be differences in behaviour between different simulators. + +### CocoTB Tests +The DUT is typically accessed through three layers: + +- an AXI driver instance is bound to the DUT's AXI interface to it, allowing easy access to read/write operations to a specified address +- an ```EthernetTopWrapper``` instance wraps this, providing conveniently-named methods for accessing each register or buffer +- an ```EthernetTopDutWithMirror``` instance wraps that, providing the same methods but also instantiating a Python model of the DUT. After each ```get``` call, the model and the DUT's output are asserted to be equal. + +A typical test flow is: +* Instantiate ```EthernetTopDutWithMirror``` +* Set the mode and MAC address +* Perform the behaviour being tested +* Get the DUT status and read the data buffers. The ```get``` calls will prompt the testbench to compare the DUT with the model. + +## Test configurations +`test_utils.py` allows test `TestConfiguration` parameters to be modified. These are: + +* `randomise_test_order` (boolean). Recommended to enable. +* `between_tests` (`"REINITIALISE"` or `"RESET"`). `"RESET"` will assert the DUT between tests whereas `"REINITIALISE"` will use register writes to return to a known state. The former is faster but the latter may catch more bugs. When using `REINITIALISE`, it is possible (though uncommon) for state to carry over between tests, meaning one failure may cascade across several tests. +* `health_test_all` (boolean). Run a 'health test' on the DUT after each test case -- the health test ensures the DUT is not stuck in a bad state and can still perform a basic loopback and register accesses. +* `tag_matcher` (callable). Callable of form `tag_matcher(tags_list)`, returns a boolean indicating whether a test should run or not according to its tags. Can be modified to select a subset of tests. + +Each test is tagged with **one** of the following tags: + +|Tag|Approx. Simulation Time*|Purpose| +|---|---|---| +|`Tags.MICRO` | 20-30 us | A 'micro' test suite, primarily useful for checking the DUT elaborates, and connection is established between the testbench and the DUT. Very little coverage of the DUT's features. | +|`Tags.SMOKE` | 350-400 us | Covers hot paths of the DUT and touches each feature at least once. Does not cover edge cases or credible stress testing. | +|`Tags.MAIN` | 500-600 us | The majority of test cases including some more unusual behaviours, back-to-back TX/RX, and error conditions. | +|`Tags.EDGE` | 900-1000 us | Adds a large number of tests targetting particular edge cases for different features. | +|`Tags.LONG` | 2500-3000 us | Adds a small number of long-running randomised tests. | + +\* The ratio of simulation time to real time will depend on your machine. On a AMD Ryzen AI 7 350 it is approximately 50 us/s. + +The random seed for each test run is printed near the top of the log file. To set the seed for a run, set the environment variable: +```bash +export RANDOM_SEED=123 +``` +Remember to unset this later. + +## Coverage +The AXI top supports coverage tracking (the Vivado simulation does not). The table below shows three kinds of coverage. + +| Coverage type | FuseSoC argument | Note | +|-----------------------|------------------|------| +| FSM coverage | `--flag fcov` | FSMs are infered by Verilator since Verilator 5.048 (Apr. 2026). Not yet a mature feature. | +| Toggle coverage | `--flag tcov` | Track toggling of all nets in the design. | +| User-defined coverage | `--flag pcov` | Includes 'covergroup' instances (since Verilator 5.050 (July 2026)) and 'cover property' statements. | + +Flags should be placed after the 'run' argument, and can be stacked. For example: +```sh +uv run fusesoc run --flag pcov --flag tcov --target=sim lowrisc:ethernet:axi_top +``` + +To analyse coverage, you should first run the simulator with coverage (as above) then run `report_coverage.sh`. This will create: +* `reports/coverage_gaps.txt` - a list of nets/coverpoints which have coverage gaps +* `reports/coverage_report.txt` - a hierarchical report of coverage by module/covergroup +* Copies of source files in the `build/` directory which are annotated with coverage metrics + + + +## Debugging Waveforms +Both simulators used in this project allow waveforms to be inspected for debugging. Verilator is the preferred simulator, but Vivado is also available. + +When using Verilator to run the `ethernet_top_axi`, waveforms are dumped into a `sim.fst` file. This can be opened in Surfer (or similar) for debugging. + +When reading these waveforms, you will see the entire test suite executing. To differentiate one test from the next, it is useful to add the three counters `tests_done`, `tests_passed`, `tests_failed` to your wave window. These are in the testbench toplevel. + +When using Vivado to run the `ethernet_top`, you can open a GUI for waveforms by `cd`-ing into `build//sim-xsim` and running `make run-gui`. + +## Macro \`DEFINEs +The following is a non-exhaustive list of macros defined or used in this codebase. + +| Name | Note | +|-|-| +| `VERILATOR` | Defined by Verilator when a Verilator sim is run. Not used in this code but may be used by dependencies for disabling unsupported features by Verilator. | +| `COVERAGE` | Defined by FuseSoC when the user-defined coverage flag is passed in. Creates coverage properties and cover groups which will otherwise be removed by the preprocessor. | +| `ASSERT` | Defined by a dependency `prim_assert.sv`. Creates an assertion which samples on `clk_i` (by default) and disables on `rst_ni` (by default). | + + + +## Directory structure + +``` +src +├── sw +└── hw +    ├── axi_eth +    │   ├── cov +    │   ├── synth +    │   └── verif +    ├── core_eth +    │   ├── csr +    │   ├── loopback +    │   ├── mac +    │   ├── rx_framing +    │   ├── tx_framing +    │   ├── top +    │   └── verif +    └── util +    ├── axis_pkt_ring_buffer +    ├── mem_if_utils +    └── random_access_fifo +``` + +The contents of ```hw/util``` are decoupled from the Ethernet with the intention that they can be reused in future projects. + +## Non-RTL files +The table below explains the contents of a selection of non-RTL files. + +| Path | Description | +|--|--| +| [ethernet.rdl](src/ethernet.rdl) | A SystemRDL (register description language) which the memory map and register map for this design. | +| [ethernet.drawio](doc/ethernet.drawio) | A collection of drawio diagrams providing both intra-module block diagrams and module-level templates for pasting into larger diagrams. | +| [ethernet.c](src/sw/ethernet.c) | Provides hardware-abstraction layer methods for reading/writing the TX/RX buffers and control/status registers. | + +# Credits +The MAC for this project was adapted from an older version of the Alex Forencich RGMII Ethernet MAC. It was forked in January 2019 and subsequently modified for lowRISC. That version of the MAC is under an MIT license. diff --git a/hw/vendor/ethernet/axis_async_fifo.sv b/hw/vendor/ethernet/axis_async_fifo.sv deleted file mode 100644 index 697223aa8..000000000 --- a/hw/vendor/ethernet/axis_async_fifo.sv +++ /dev/null @@ -1,453 +0,0 @@ -/* - -Copyright (c) 2014-2018 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 - -/* - * AXI4-Stream asynchronous FIFO - */ -module axis_async_fifo # -( - parameter ADDR_WIDTH = 12, - parameter DATA_WIDTH = 8, - parameter KEEP_ENABLE = (DATA_WIDTH>8), - parameter KEEP_WIDTH = (DATA_WIDTH/8), - parameter LAST_ENABLE = 1, - parameter ID_ENABLE = 0, - parameter ID_WIDTH = 8, - parameter DEST_ENABLE = 0, - parameter DEST_WIDTH = 8, - parameter USER_ENABLE = 1, - parameter USER_WIDTH = 1, - parameter FRAME_FIFO = 0, - parameter USER_BAD_FRAME_VALUE = 1'b1, - parameter USER_BAD_FRAME_MASK = 1'b1, - parameter DROP_BAD_FRAME = 0, - parameter DROP_WHEN_FULL = 0 -) -( - /* - * Common asynchronous reset - */ - input wire async_rst, - - /* - * AXI input - */ - input wire s_clk, - input wire [DATA_WIDTH-1:0] s_axis_tdata, - input wire [KEEP_WIDTH-1:0] s_axis_tkeep, - input wire s_axis_tvalid, - output wire s_axis_tready, - input wire s_axis_tlast, - input wire [ID_WIDTH-1:0] s_axis_tid, - input wire [DEST_WIDTH-1:0] s_axis_tdest, - input wire [USER_WIDTH-1:0] s_axis_tuser, - - /* - * AXI output - */ - input wire m_clk, - output wire [DATA_WIDTH-1:0] m_axis_tdata, - output wire [KEEP_WIDTH-1:0] m_axis_tkeep, - output wire m_axis_tvalid, - input wire m_axis_tready, - output wire m_axis_tlast, - output wire [ID_WIDTH-1:0] m_axis_tid, - output wire [DEST_WIDTH-1:0] m_axis_tdest, - output wire [USER_WIDTH-1:0] m_axis_tuser, - - /* - * Status - */ - output wire s_status_overflow, - output wire s_status_bad_frame, - output wire s_status_good_frame, - output wire m_status_overflow, - output wire m_status_bad_frame, - output wire m_status_good_frame -); - -// check configuration -initial begin - if (FRAME_FIFO && !LAST_ENABLE) begin - $error("Error: FRAME_FIFO set requires LAST_ENABLE set"); - $finish; - end - - if (DROP_BAD_FRAME && !FRAME_FIFO) begin - $error("Error: DROP_BAD_FRAME set requires FRAME_FIFO set"); - $finish; - end - - if (DROP_WHEN_FULL && !FRAME_FIFO) begin - $error("Error: DROP_WHEN_FULL set requires FRAME_FIFO set"); - $finish; - end - - if (DROP_BAD_FRAME && (USER_BAD_FRAME_MASK & {USER_WIDTH{1'b1}}) == 0) begin - $error("Error: Invalid USER_BAD_FRAME_MASK value"); - $finish; - end -end - -localparam KEEP_OFFSET = DATA_WIDTH; -localparam LAST_OFFSET = KEEP_OFFSET + (KEEP_ENABLE ? KEEP_WIDTH : 0); -localparam ID_OFFSET = LAST_OFFSET + (LAST_ENABLE ? 1 : 0); -localparam DEST_OFFSET = ID_OFFSET + (ID_ENABLE ? ID_WIDTH : 0); -localparam USER_OFFSET = DEST_OFFSET + (DEST_ENABLE ? DEST_WIDTH : 0); -localparam WIDTH = USER_OFFSET + (USER_ENABLE ? USER_WIDTH : 0); - -reg [ADDR_WIDTH:0] wr_ptr_reg = {ADDR_WIDTH+1{1'b0}}, wr_ptr_next; -reg [ADDR_WIDTH:0] wr_ptr_cur_reg = {ADDR_WIDTH+1{1'b0}}, wr_ptr_cur_next; -reg [ADDR_WIDTH:0] wr_ptr_gray_reg = {ADDR_WIDTH+1{1'b0}}, wr_ptr_gray_next; -reg [ADDR_WIDTH:0] wr_ptr_cur_gray_reg = {ADDR_WIDTH+1{1'b0}}, wr_ptr_cur_gray_next; -reg [ADDR_WIDTH:0] wr_addr_reg = {ADDR_WIDTH+1{1'b0}}; -reg [ADDR_WIDTH:0] rd_ptr_reg = {ADDR_WIDTH+1{1'b0}}, rd_ptr_next; -reg [ADDR_WIDTH:0] rd_ptr_gray_reg = {ADDR_WIDTH+1{1'b0}}, rd_ptr_gray_next; -reg [ADDR_WIDTH:0] rd_addr_reg = {ADDR_WIDTH+1{1'b0}}; - -reg [ADDR_WIDTH:0] wr_ptr_gray_sync1_reg = {ADDR_WIDTH+1{1'b0}}; -reg [ADDR_WIDTH:0] wr_ptr_gray_sync2_reg = {ADDR_WIDTH+1{1'b0}}; -reg [ADDR_WIDTH:0] rd_ptr_gray_sync1_reg = {ADDR_WIDTH+1{1'b0}}; -reg [ADDR_WIDTH:0] rd_ptr_gray_sync2_reg = {ADDR_WIDTH+1{1'b0}}; - -reg s_rst_sync1_reg = 1'b1; -reg s_rst_sync2_reg = 1'b1; -reg s_rst_sync3_reg = 1'b1; -reg m_rst_sync1_reg = 1'b1; -reg m_rst_sync2_reg = 1'b1; -reg m_rst_sync3_reg = 1'b1; - -reg [WIDTH-1:0] mem[(2**ADDR_WIDTH)-1:0]; -reg [WIDTH-1:0] mem_read_data_reg; -reg mem_read_data_valid_reg = 1'b0, mem_read_data_valid_next; - -wire [WIDTH-1:0] s_axis; - -reg [WIDTH-1:0] m_axis_reg; -reg m_axis_tvalid_reg = 1'b0, m_axis_tvalid_next; - -// full when first TWO MSBs do NOT match, but rest matches -// (gray code equivalent of first MSB different but rest same) -wire full = ((wr_ptr_gray_reg[ADDR_WIDTH] != rd_ptr_gray_sync2_reg[ADDR_WIDTH]) && - (wr_ptr_gray_reg[ADDR_WIDTH-1] != rd_ptr_gray_sync2_reg[ADDR_WIDTH-1]) && - (wr_ptr_gray_reg[ADDR_WIDTH-2:0] == rd_ptr_gray_sync2_reg[ADDR_WIDTH-2:0])); -wire full_cur = ((wr_ptr_cur_gray_reg[ADDR_WIDTH] != rd_ptr_gray_sync2_reg[ADDR_WIDTH]) && - (wr_ptr_cur_gray_reg[ADDR_WIDTH-1] != rd_ptr_gray_sync2_reg[ADDR_WIDTH-1]) && - (wr_ptr_cur_gray_reg[ADDR_WIDTH-2:0] == rd_ptr_gray_sync2_reg[ADDR_WIDTH-2:0])); -// empty when pointers match exactly -wire empty = rd_ptr_gray_reg == wr_ptr_gray_sync2_reg; -// overflow within packet -wire full_wr = ((wr_ptr_reg[ADDR_WIDTH] != wr_ptr_cur_reg[ADDR_WIDTH]) && - (wr_ptr_reg[ADDR_WIDTH-1:0] == wr_ptr_cur_reg[ADDR_WIDTH-1:0])); - -// control signals -reg write; -reg read; -reg store_output; - -reg drop_frame_reg = 1'b0, drop_frame_next; -reg overflow_reg = 1'b0, overflow_next; -reg bad_frame_reg = 1'b0, bad_frame_next; -reg good_frame_reg = 1'b0, good_frame_next; - -reg overflow_sync1_reg = 1'b0; -reg overflow_sync2_reg = 1'b0; -reg overflow_sync3_reg = 1'b0; -reg overflow_sync4_reg = 1'b0; -reg bad_frame_sync1_reg = 1'b0; -reg bad_frame_sync2_reg = 1'b0; -reg bad_frame_sync3_reg = 1'b0; -reg bad_frame_sync4_reg = 1'b0; -reg good_frame_sync1_reg = 1'b0; -reg good_frame_sync2_reg = 1'b0; -reg good_frame_sync3_reg = 1'b0; -reg good_frame_sync4_reg = 1'b0; - -assign s_axis_tready = (FRAME_FIFO ? (!full_cur || full_wr || DROP_WHEN_FULL) : !full) && !s_rst_sync3_reg; - -generate - assign s_axis[DATA_WIDTH-1:0] = s_axis_tdata; - if (KEEP_ENABLE) assign s_axis[KEEP_OFFSET +: KEEP_WIDTH] = s_axis_tkeep; - if (LAST_ENABLE) assign s_axis[LAST_OFFSET] = s_axis_tlast; - if (ID_ENABLE) assign s_axis[ID_OFFSET +: ID_WIDTH] = s_axis_tid; - if (DEST_ENABLE) assign s_axis[DEST_OFFSET +: DEST_WIDTH] = s_axis_tdest; - if (USER_ENABLE) assign s_axis[USER_OFFSET +: USER_WIDTH] = s_axis_tuser; -endgenerate - -assign m_axis_tvalid = m_axis_tvalid_reg; - -assign m_axis_tdata = m_axis_reg[DATA_WIDTH-1:0]; -assign m_axis_tkeep = KEEP_ENABLE ? m_axis_reg[KEEP_OFFSET +: KEEP_WIDTH] : {KEEP_WIDTH{1'b1}}; -assign m_axis_tlast = LAST_ENABLE ? m_axis_reg[LAST_OFFSET] : 1'b1; -assign m_axis_tid = ID_ENABLE ? m_axis_reg[ID_OFFSET +: ID_WIDTH] : {ID_WIDTH{1'b0}}; -assign m_axis_tdest = DEST_ENABLE ? m_axis_reg[DEST_OFFSET +: DEST_WIDTH] : {DEST_WIDTH{1'b0}}; -assign m_axis_tuser = USER_ENABLE ? m_axis_reg[USER_OFFSET +: USER_WIDTH] : {USER_WIDTH{1'b0}}; - -assign s_status_overflow = overflow_reg; -assign s_status_bad_frame = bad_frame_reg; -assign s_status_good_frame = good_frame_reg; - -assign m_status_overflow = overflow_sync3_reg ^ overflow_sync4_reg; -assign m_status_bad_frame = bad_frame_sync3_reg ^ bad_frame_sync4_reg; -assign m_status_good_frame = good_frame_sync3_reg ^ good_frame_sync4_reg; - -// reset synchronization -always @(posedge s_clk or posedge async_rst) begin - if (async_rst) begin - s_rst_sync1_reg <= 1'b1; - s_rst_sync2_reg <= 1'b1; - s_rst_sync3_reg <= 1'b1; - end else begin - s_rst_sync1_reg <= 1'b0; - s_rst_sync2_reg <= s_rst_sync1_reg || m_rst_sync1_reg; - s_rst_sync3_reg <= s_rst_sync2_reg; - end -end - -always @(posedge m_clk or posedge async_rst) begin - if (async_rst) begin - m_rst_sync1_reg <= 1'b1; - m_rst_sync2_reg <= 1'b1; - m_rst_sync3_reg <= 1'b1; - end else begin - m_rst_sync1_reg <= 1'b0; - m_rst_sync2_reg <= s_rst_sync1_reg || m_rst_sync1_reg; - m_rst_sync3_reg <= m_rst_sync2_reg; - end -end - -// Write logic -always @* begin - write = 1'b0; - - drop_frame_next = 1'b0; - overflow_next = 1'b0; - bad_frame_next = 1'b0; - good_frame_next = 1'b0; - - wr_ptr_next = wr_ptr_reg; - wr_ptr_cur_next = wr_ptr_cur_reg; - wr_ptr_gray_next = wr_ptr_gray_reg; - wr_ptr_cur_gray_next = wr_ptr_cur_gray_reg; - - if (s_axis_tready && s_axis_tvalid) begin - // transfer in - if (!FRAME_FIFO) begin - // normal FIFO mode - write = 1'b1; - wr_ptr_next = wr_ptr_reg + 1; - wr_ptr_gray_next = wr_ptr_next ^ (wr_ptr_next >> 1); - end else if (full_cur || full_wr || drop_frame_reg) begin - // full, packet overflow, or currently dropping frame - // drop frame - drop_frame_next = 1'b1; - if (s_axis_tlast) begin - // end of frame, reset write pointer - wr_ptr_cur_next = wr_ptr_reg; - wr_ptr_cur_gray_next = wr_ptr_cur_next ^ (wr_ptr_cur_next >> 1); - drop_frame_next = 1'b0; - overflow_next = 1'b1; - end - end else begin - write = 1'b1; - wr_ptr_cur_next = wr_ptr_cur_reg + 1; - wr_ptr_cur_gray_next = wr_ptr_cur_next ^ (wr_ptr_cur_next >> 1); - if (s_axis_tlast) begin - // end of frame - if (DROP_BAD_FRAME && (USER_BAD_FRAME_MASK & s_axis_tuser == USER_BAD_FRAME_VALUE)) begin - // bad packet, reset write pointer - wr_ptr_cur_next = wr_ptr_reg; - wr_ptr_cur_gray_next = wr_ptr_cur_next ^ (wr_ptr_cur_next >> 1); - bad_frame_next = 1'b1; - end else begin - // good packet, update write pointer - wr_ptr_next = wr_ptr_cur_reg + 1; - wr_ptr_gray_next = wr_ptr_next ^ (wr_ptr_next >> 1); - good_frame_next = 1'b1; - end - end - end - end -end - -always @(posedge s_clk) begin - if (s_rst_sync3_reg) begin - wr_ptr_reg <= {ADDR_WIDTH+1{1'b0}}; - wr_ptr_cur_reg <= {ADDR_WIDTH+1{1'b0}}; - wr_ptr_gray_reg <= {ADDR_WIDTH+1{1'b0}}; - wr_ptr_cur_gray_reg <= {ADDR_WIDTH+1{1'b0}}; - - drop_frame_reg <= 1'b0; - overflow_reg <= 1'b0; - bad_frame_reg <= 1'b0; - good_frame_reg <= 1'b0; - end else begin - wr_ptr_reg <= wr_ptr_next; - wr_ptr_cur_reg <= wr_ptr_cur_next; - wr_ptr_gray_reg <= wr_ptr_gray_next; - wr_ptr_cur_gray_reg <= wr_ptr_cur_gray_next; - - drop_frame_reg <= drop_frame_next; - overflow_reg <= overflow_next; - bad_frame_reg <= bad_frame_next; - good_frame_reg <= good_frame_next; - end - - if (FRAME_FIFO) begin - wr_addr_reg <= wr_ptr_cur_next; - end else begin - wr_addr_reg <= wr_ptr_next; - end - - if (write) begin - mem[wr_addr_reg[ADDR_WIDTH-1:0]] <= s_axis; - end -end - -// pointer synchronization -always @(posedge s_clk) begin - if (s_rst_sync3_reg) begin - rd_ptr_gray_sync1_reg <= {ADDR_WIDTH+1{1'b0}}; - rd_ptr_gray_sync2_reg <= {ADDR_WIDTH+1{1'b0}}; - end else begin - rd_ptr_gray_sync1_reg <= rd_ptr_gray_reg; - rd_ptr_gray_sync2_reg <= rd_ptr_gray_sync1_reg; - end -end - -always @(posedge m_clk) begin - if (m_rst_sync3_reg) begin - wr_ptr_gray_sync1_reg <= {ADDR_WIDTH+1{1'b0}}; - wr_ptr_gray_sync2_reg <= {ADDR_WIDTH+1{1'b0}}; - end else begin - wr_ptr_gray_sync1_reg <= wr_ptr_gray_reg; - wr_ptr_gray_sync2_reg <= wr_ptr_gray_sync1_reg; - end -end - -// status synchronization -always @(posedge s_clk) begin - if (s_rst_sync3_reg) begin - overflow_sync1_reg <= 1'b0; - bad_frame_sync1_reg <= 1'b0; - good_frame_sync1_reg <= 1'b0; - end else begin - overflow_sync1_reg <= overflow_sync1_reg ^ overflow_reg; - bad_frame_sync1_reg <= bad_frame_sync1_reg ^ bad_frame_reg; - good_frame_sync1_reg <= good_frame_sync1_reg ^ good_frame_reg; - end -end - -always @(posedge m_clk) begin - if (m_rst_sync3_reg) begin - overflow_sync2_reg <= 1'b0; - overflow_sync3_reg <= 1'b0; - bad_frame_sync2_reg <= 1'b0; - bad_frame_sync3_reg <= 1'b0; - good_frame_sync2_reg <= 1'b0; - good_frame_sync3_reg <= 1'b0; - end else begin - overflow_sync2_reg <= overflow_sync1_reg; - overflow_sync3_reg <= overflow_sync2_reg; - overflow_sync4_reg <= overflow_sync3_reg; - bad_frame_sync2_reg <= bad_frame_sync1_reg; - bad_frame_sync3_reg <= bad_frame_sync2_reg; - bad_frame_sync4_reg <= bad_frame_sync3_reg; - good_frame_sync2_reg <= good_frame_sync1_reg; - good_frame_sync3_reg <= good_frame_sync2_reg; - good_frame_sync4_reg <= good_frame_sync3_reg; - end -end - -// Read logic -always @* begin - read = 1'b0; - - rd_ptr_next = rd_ptr_reg; - rd_ptr_gray_next = rd_ptr_gray_reg; - - mem_read_data_valid_next = mem_read_data_valid_reg; - - if (store_output || !mem_read_data_valid_reg) begin - // output data not valid OR currently being transferred - if (!empty) begin - // not empty, perform read - read = 1'b1; - mem_read_data_valid_next = 1'b1; - rd_ptr_next = rd_ptr_reg + 1; - rd_ptr_gray_next = rd_ptr_next ^ (rd_ptr_next >> 1); - end else begin - // empty, invalidate - mem_read_data_valid_next = 1'b0; - end - end -end - -always @(posedge m_clk) begin - if (m_rst_sync3_reg) begin - rd_ptr_reg <= {ADDR_WIDTH+1{1'b0}}; - rd_ptr_gray_reg <= {ADDR_WIDTH+1{1'b0}}; - mem_read_data_valid_reg <= 1'b0; - end else begin - rd_ptr_reg <= rd_ptr_next; - rd_ptr_gray_reg <= rd_ptr_gray_next; - mem_read_data_valid_reg <= mem_read_data_valid_next; - end - - rd_addr_reg <= rd_ptr_next; - - if (read) begin - mem_read_data_reg <= mem[rd_addr_reg[ADDR_WIDTH-1:0]]; - end -end - -// Output register -always @* begin - store_output = 1'b0; - - m_axis_tvalid_next = m_axis_tvalid_reg; - - if (m_axis_tready || !m_axis_tvalid) begin - store_output = 1'b1; - m_axis_tvalid_next = mem_read_data_valid_reg; - end -end - -always @(posedge m_clk) begin - if (m_rst_sync3_reg) begin - m_axis_tvalid_reg <= 1'b0; - end else begin - m_axis_tvalid_reg <= m_axis_tvalid_next; - end - - if (store_output) begin - m_axis_reg <= mem_read_data_reg; - end -end - -endmodule diff --git a/hw/vendor/ethernet/dualmem_widen.sv b/hw/vendor/ethernet/dualmem_widen.sv deleted file mode 100644 index 5fb19eab0..000000000 --- a/hw/vendor/ethernet/dualmem_widen.sv +++ /dev/null @@ -1,75 +0,0 @@ - -module dualmem_widen(clka, clkb, dina, dinb, addra, addrb, wea, web, douta, doutb, ena, enb); - - input wire clka, clkb; - input [15:0] dina; - input [63:0] dinb; - input [10:0] addra; - input [8:0] addrb; - input [1:0] wea; - input [1:0] web; - input [0:0] ena, enb; - output [15:0] douta; - output [63:0] doutb; - - genvar r; - wire [47:0] dout; - -/* -`ifndef verilator - `define RAMB16 -`endif -*/ - - `define RAMB16 - -`ifdef RAMB16 - - generate for (r = 0; r < 2; r=r+1) - RAMB16_S9_S36 - RAMB16_S9_S36_inst - ( - .CLKA ( clka ), // Port A Clock - .DOA ( douta[r*8 +: 8] ), // Port A 1-bit Data Output - .DOPA ( ), - .ADDRA ( addra ), // Port A 14-bit Address Input - .DIA ( dina[r*8 +: 8] ), // Port A 1-bit Data Input - .DIPA ( 1'b0 ), - .ENA ( ena ), // Port A RAM Enable Input - .SSRA ( 1'b0 ), // Port A Synchronous Set/Reset Input - .WEA ( wea[r] ), // Port A Write Enable Input - .CLKB ( clkb ), // Port B Clock - .DOB ( doutb[r*32 +: 32] ), // Port B 1-bit Data Output - .DOPB ( ), - .ADDRB ( addrb ), // Port B 14-bit Address Input - .DIB ( dinb[r*32 +: 32] ), // Port B 1-bit Data Input - .DIPB ( 4'b0 ), - .ENB ( enb ), // Port B RAM Enable Input - .SSRB ( 1'b0 ), // Port B Synchronous Set/Reset Input - .WEB ( web[r] ) // Port B Write Enable Input - ); - endgenerate - -`else // !`ifdef RAMB16 - -// This bit is a placeholder - -infer_dpram #(.RAM_SIZE(11), .BYTE_WIDTH(8)) ram1 // RAM_SIZE is in words -( -.ram_clk_a(clka), -.ram_en_a(|ena), -.ram_we_a({wea[1],wea[1],wea[1],wea[1],wea[0],wea[0],wea[0],wea[0]}), -.ram_addr_a(addra), -.ram_wrdata_a({dina,dina,dina,dina}), -.ram_rddata_a({dout,douta}), -.ram_clk_b(clkb), -.ram_en_b(|enb), -.ram_we_b({web[1],web[1],web[1],web[1],web[0],web[0],web[0],web[0]}), -.ram_addr_b({2'b0,addrb}), -.ram_wrdata_b(dinb), -.ram_rddata_b(doutb) - ); - -`endif - -endmodule // dualmem diff --git a/hw/vendor/ethernet/dualmem_widen8.sv b/hw/vendor/ethernet/dualmem_widen8.sv deleted file mode 100644 index 4a6d0667b..000000000 --- a/hw/vendor/ethernet/dualmem_widen8.sv +++ /dev/null @@ -1,100 +0,0 @@ - -module dualmem_widen8(clka, clkb, dina, dinb, addra, addrb, wea, web, douta, doutb, ena, enb); - - input wire clka, clkb; - input [15:0] dina; - input [63:0] dinb; - input [12:0] addra; - input [10:0] addrb; - input [1:0] wea; - input [1:0] web; - input [0:0] ena, enb; - output [15:0] douta; - output [63:0] doutb; - - genvar r; - wire [63:0] dout0; - wire [255:0] dout1; - wire [7:0] we0, we1, en0, en1; - wire [63:0] din0; - wire [255:0] din1; - - reg [12:0] addra_dly; - reg [10:0] addrb_dly; - -/* -`ifndef verilator - `define RAMB16 -`endif -*/ - - `define RAMB16 - -`ifdef RAMB16 - - assign douta = dout0 >> {addra_dly[12:11],4'b0000}; - assign doutb = dout1 >> {addrb_dly[10:9],6'b000000}; - assign we0 = wea << {addra[12:11],1'b0}; - assign we1 = web << {addrb[10:9],1'b0}; - assign en0 = {ena,ena} << {addra[12:11],1'b0}; - assign en1 = {enb,enb} << {addrb[10:9],1'b0}; - assign din0 = {dina,dina,dina,dina}; - assign din1 = {dinb,dinb,dinb,dinb}; - - always @(posedge clka) - begin - addra_dly <= addra; - end - - always @(posedge clkb) begin - addrb_dly <= addrb; - end - - generate for (r = 0; r < 8; r=r+1) - RAMB16_S9_S36 - RAMB16_S9_S36_inst - ( - .CLKA ( clka ), // Port A Clock - .DOA ( dout0[r*8 +: 8] ), // Port A 1-bit Data Output - .DOPA ( ), - .ADDRA ( addra[10:0] ), // Port A 14-bit Address Input - .DIA ( din0[r*8 +: 8] ), // Port A 1-bit Data Input - .DIPA ( 1'b0 ), - .ENA ( en0[r] ), // Port A RAM Enable Input - .SSRA ( 1'b0 ), // Port A Synchronous Set/Reset Input - .WEA ( we0[r] ), // Port A Write Enable Input - .CLKB ( clkb ), // Port B Clock - .DOB ( dout1[r*32 +: 32] ), // Port B 1-bit Data Output - .DOPB ( ), - .ADDRB ( addrb[8:0] ), // Port B 14-bit Address Input - .DIB ( din1[r*32 +: 32] ), // Port B 1-bit Data Input - .DIPB ( 4'b0 ), - .ENB ( en1[r] ), // Port B RAM Enable Input - .SSRB ( 1'b0 ), // Port B Synchronous Set/Reset Input - .WEB ( we1[r] ) // Port B Write Enable Input - ); - endgenerate - -`else // !`ifdef RAMB16 - -// This bit is a placeholder - -infer_dpram #(.RAM_SIZE(11), .BYTE_WIDTH(8)) ram1 // RAM_SIZE is in words -( -.ram_clk_a(clka), -.ram_en_a(|ena), -.ram_we_a({wea[1],wea[1],wea[1],wea[1],wea[0],wea[0],wea[0],wea[0]}), -.ram_addr_a(addra), -.ram_wrdata_a({dina,dina,dina,dina}), -.ram_rddata_a({dout,douta}), -.ram_clk_b(clkb), -.ram_en_b(|enb), -.ram_we_b({web[1],web[1],web[1],web[1],web[0],web[0],web[0],web[0]}), -.ram_addr_b({2'b0,addrb}), -.ram_wrdata_b(dinb), -.ram_rddata_b(doutb) - ); - -`endif - -endmodule // dualmem diff --git a/hw/vendor/ethernet/eth_mac_1g_rgmii_fifo.sv b/hw/vendor/ethernet/eth_mac_1g_rgmii_fifo.sv deleted file mode 100644 index fa28bb2c9..000000000 --- a/hw/vendor/ethernet/eth_mac_1g_rgmii_fifo.sv +++ /dev/null @@ -1,307 +0,0 @@ -/* - -Copyright (c) 2015-2018 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 - -/* - * 1G Ethernet MAC with RGMII interface and TX and RX FIFOs - */ -module eth_mac_1g_rgmii_fifo # -( - // target ("SIM", "GENERIC", "XILINX", "ALTERA") - parameter TARGET = "GENERIC", - // IODDR style ("IODDR", "IODDR2") - // Use IODDR for Virtex-4, Virtex-5, Virtex-6, 7 Series, Ultrascale - // Use IODDR2 for Spartan-6 - parameter IODDR_STYLE = "IODDR2", - // 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", - // Use 90 degree clock for RGMII transmit ("TRUE", "FALSE") - parameter USE_CLK90 = "TRUE", - 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 gtx_clk, - input wire gtx_clk90, - input wire gtx_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, - - /* - * RGMII interface - */ - input wire rgmii_rx_clk, - input wire [3:0] rgmii_rxd, - input wire rgmii_rx_ctl, - output wire rgmii_tx_clk, - output wire [3:0] rgmii_txd, - output wire rgmii_tx_ctl, - output wire mac_gmii_tx_en, - - /* - * Status - */ - 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, - output wire [1:0] speed, - output wire [31:0] rx_fcs_reg, - output wire [31:0] tx_fcs_reg, - - /* - * 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 rx_error_bad_frame_int; -wire rx_error_bad_fcs_int; - -reg [1:0] rx_sync_reg_1; -reg [1:0] rx_sync_reg_2; -reg [1:0] rx_sync_reg_3; -reg [1:0] rx_sync_reg_4; - -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 - - -wire [1:0] speed_int; - -reg [1:0] speed_sync_reg_1; -reg [1:0] speed_sync_reg_2; - -assign speed = speed_sync_reg_2; - -always @(posedge logic_clk) begin - speed_sync_reg_1 <= speed_int; - speed_sync_reg_2 <= speed_sync_reg_1; -end - -eth_mac_1g_rgmii #( - .TARGET(TARGET), - .IODDR_STYLE(IODDR_STYLE), - .CLOCK_INPUT_STYLE(CLOCK_INPUT_STYLE), - .USE_CLK90(USE_CLK90), - .ENABLE_PADDING(ENABLE_PADDING), - .MIN_FRAME_LENGTH(MIN_FRAME_LENGTH) -) -eth_mac_1g_rgmii_inst ( - .gtx_clk(gtx_clk), - .gtx_clk90(gtx_clk90), - .gtx_rst(gtx_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), - .rgmii_rx_clk(rgmii_rx_clk), - .rgmii_rxd(rgmii_rxd), - .rgmii_rx_ctl(rgmii_rx_ctl), - .rgmii_tx_clk(rgmii_tx_clk), - .rgmii_txd(rgmii_txd), - .rgmii_tx_ctl(rgmii_tx_ctl), - .mac_gmii_tx_en(mac_gmii_tx_en), - .rx_error_bad_frame(rx_error_bad_frame_int), - .rx_error_bad_fcs(rx_error_bad_fcs_int), - .rx_fcs_reg(rx_fcs_reg), - .tx_fcs_reg(tx_fcs_reg), - .speed(speed_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/hw/vendor/ethernet/ethernet.rdl b/hw/vendor/ethernet/ethernet.rdl new file mode 100644 index 000000000..398b6c405 --- /dev/null +++ b/hw/vendor/ethernet/ethernet.rdl @@ -0,0 +1,318 @@ +/* Copyright lowRISC contributors. + * Licensed under the Apache License, Version 2.0; see LICENSE for details. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Register Description Language (SystemRDL 2.0) file for the Ethernet peripheral. + * Derived from memory_map.txt. + * + * Address space overview (relative to peripheral base): + * 0x0800 – 0x087F CSR registers (128 B, 32-bit, access per register) + * 0x1000 – 0x17FF TX data buffer ( 2 KB, 64-bit words, write-only) + * 0x4000 – 0x5FFF RX data buffer ( 8 KB, 64-bit words, read-only) + * 0x6000 – 0x603F RX descriptor table ( 64 B, 8 × 64-bit entries, read-only) + * + * The host-facing bus is 64-bit data / 64-bit address. CSR registers are 32-bit; + * the upper 32 bits of any 64-bit write to the CSR region are ignored (BE[63:32] + * must be zero, otherwise the access is flagged as a bad request). + */ + +/* ========================================================================= + * Reusable field types + * ========================================================================= */ + +/* R/W configuration field: software reads and writes; hardware reads the value. */ +field cfg_f { + sw = rw; + hw = r; + reset = 1'b0; +}; + +/* Read-only status field: hardware writes the value; software reads it. */ +field sts_f { + sw = r; + hw = w; + reset = 1'b0; +}; + +/* Write-only command field: software writes; hardware reads. Reads as 0. */ +field wo_f { + sw = w; + hw = r; + reset = 1'b0; +}; + +/* Write-one-to-clear sticky field: hardware sets; software writes 1 to clear. */ +field wtc_f { + sw = rw; + hw = w; + onwrite = woclr; + reset = 1'b0; +}; + +/* ========================================================================= + * Top-level address map + * ========================================================================= */ +addrmap ethernet { + desc = "Ethernet peripheral. 64-bit data, 64-bit address bus interface."; + + /* ----------------------------------------------------------------------- + * CSR REGISTERS (base 0x0800, 32-bit, 4-byte aligned) + * ----------------------------------------------------------------------- */ + + /* INTR_STATE @ 0x0800 - current interrupt status (read-only). + * Level interrupts (bits 0–4) deassert when the underlying condition clears. + * Sticky interrupts (bits 5–6) are cleared by writing 1. */ + reg { + desc = "Current interrupt status. + irq_o = |(INTR_MASK & INTR_STATE). + Level interrupts clear automatically when the source condition deasserts."; + + sts_f rx_not_empty[0:0]; + rx_not_empty->desc = "RX data buffer not empty (level). Deasserts when the buffer is fully drained."; + + sts_f rx_table_almost_full[1:1]; + rx_table_almost_full->desc = "RX descriptor table almost full (level). High when <=2 descriptor slots remain."; + + sts_f rx_table_full[2:2]; + rx_table_full->desc = "RX descriptor table full (level). No more incoming packets can be committed."; + + sts_f rx_buf_almost_full[3:3]; + rx_buf_almost_full->desc = "RX data buffer almost full (level). Fires when <1522 bytes remain free."; + + wtc_f rx_packet_lost[4:4]; + rx_packet_lost->desc = "A packet was lost (sticky). Set when a packet is dropped due to RX descriptor table or RX data buffer being full. Write 1 to clear."; + + wtc_f tx_done[5:5]; + tx_done->desc = "TX done (sticky). Set on tx_done_pulse at the end of a transmission. Write 1 to clear."; + + wtc_f manual_irq[6:6]; + manual_irq->desc = "Manual IRQ (sticky). Set by writing 1 to INTR_TEST[0]. Write 1 to clear."; + } INTR_STATE @ 0x0800; + + /* INTR_MASK @ 0x0804 - same access pattern as cfg_f but all bits reset to 1. + * Anonymous field declarations are used throughout this register. */ + reg { + desc = "Per-source interrupt enable mask. 1 = enabled, 0 = masked. Reset value: 0x0 (all disabled)."; + + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for rx_not_empty."; } rx_not_empty[0:0]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for rx_table_almost_full."; } rx_table_almost_full[1:1]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for rx_table_full."; } rx_table_full[2:2]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for rx_buf_almost_full."; } rx_buf_almost_full[3:3]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for rx_packet_lost."; } rx_packet_lost[4:4]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for tx_done."; } tx_done[5:5]; + field { sw = rw; hw = r; reset = 1'b1; desc = "Mask bit for manual_irq."; } manual_irq[6:6]; + } INTR_MASK @ 0x0804; + + /* INTR_TEST @ 0x0808 - write-only */ + reg { + desc = "Interrupt test register. Write-only."; + + wo_f set_manual_irq[0:0]; + set_manual_irq->desc = "Write 1 to force-set the manual_irq sticky bit in INTR_STATE (for testing)."; + } INTR_TEST @ 0x0808; + + /* 0x080C - reserved. No register instance. */ + + /* CTRL @ 0x0810 */ + reg { + desc = "Control register. If written while an RX is in progress, the behaviour is not defined for that packet i.e. it may be either dropped or kept. Packets already in the buffer will not be disturbed, and subsequent packets will be received according to the new configuration."; + + cfg_f promiscuous_mode[0:0]; + promiscuous_mode->desc = "Promiscuous mode. + 0 = filter by local MAC address (default); + 1 = capture all received frames regardless of destination address."; + + cfg_f loopback[1:1]; + loopback->desc = "Internal loopback enable. + 0 = normal operation; + 1 = internally loop back transmitted frames to the RX path for testing + Loop includes framing logic but NOT the MAC and PHY. + Does not affect MDIO. + The incoming RX data will be ignored while in loopback mode. TX packets are still sent out."; + + } CTRL @ 0x0810; + + /* STATUS @ 0x0814 - read-only */ + reg { + desc = "RX and TX status flags (read-only)."; + + sts_f n_packets_in_rx_buf[11:8]; + n_packets_in_rx_buf->desc = "Number of packets currently stored in the RX data buffer. Incremented by hardware and decrement by writing to pop register."; + + sts_f tx_busy[7:7]; + tx_busy->desc = "TX busy. Asserted while a frame is being transmitted. + Do not write TX_CTRL while this bit is set."; + + // Bits 4, 5 and 6 left blank + // this is because bits 0-3 are identical between STATUS and INTR_STATE + // and bits 4/5/6 of INTR_STATE are used for something else + // so it could be confusing to use them here for a different purpose + + sts_f rx_buf_almost_full[3:3]; + rx_buf_almost_full->desc = "RX data buffer almost full (level). Fires when ≤24 words (~192 bytes) remain free."; + + sts_f rx_table_full[2:2]; + rx_table_full->desc = "RX descriptor table full (level). No more incoming packets can be committed."; + + sts_f rx_table_almost_full[1:1]; + rx_table_almost_full->desc = "RX descriptor table almost full (level). Fires when ≤2 descriptor slots remain."; + + sts_f rx_not_empty[0:0]; + rx_not_empty->desc = "RX data buffer not empty (level). Deasserts when the buffer is fully drained."; + } STATUS @ 0x0814; + + /* MACLO @ 0x0818 - lower 32 bits of local MAC address */ + reg { + desc = "Lower 32 bits of the local MAC address. Reset value: 0x00000000."; + + cfg_f mac_lo[31:0]; + mac_lo->desc = "MAC address bits [31:0]."; + } MACLO @ 0x0818; + + /* MACHI @ 0x081C - upper 16 bits of local MAC address; bits [31:16] reserved */ + reg { + desc = "Upper 16 bits of the local MAC address. Bits [31:16] are reserved. Reset value: 0x00000000."; + + cfg_f mac_hi[15:0]; + mac_hi->desc = "MAC address bits [47:32]."; + } MACHI @ 0x081C; + + /* TX_CTRL @ 0x0820 - writing this register also kicks the TX FSM */ + reg { + desc = "TX control register. Writing this register immediately starts a transmission - + the TX framing FSM begins reading the TX data buffer and sending tx_packet_len bytes. + Do not write while STATUS.tx_busy is set."; + + cfg_f tx_packet_len[10:0]; + tx_packet_len->desc = "Number of bytes to transmit (min 2, max 1518 for a maximum-size Ethernet frame + with VLAN tag). The TX framing FSM reads this many bytes from the TX data buffer."; + } TX_CTRL @ 0x0820; + + /* 0x0824 - reserved. No register instance. */ + + /* RX_POP @ 0x0828 - write-only */ + reg { + desc = "RX descriptor table pop register. Write-only. + The descriptor table acts as a FIFO; entry 0 is always the oldest unread packet."; + + wo_f pop[0:0]; + pop->desc = "Write 1 to pop (discard) the oldest entry from the RX descriptor table, + advancing the FIFO head pointer."; + } RX_POP @ 0x0828; + + /* MDIO_CTRL @ 0x082C - bit-bang MDIO/MDC interface. + * Three R/W config outputs (cfg_f) plus one read-only input (sts_f). */ + reg { + desc = "Bit-bang MDIO/MDC interface control register. + Software toggles mdio_clk and drives mdio_o/mdio_oen for MDIO transactions; + mdio_i returns the value sampled from the PHY."; + + cfg_f mdio_clk[0:0]; + mdio_clk->desc = "MDC clock output. Toggle to generate MDC clock cycles."; + cfg_f mdio_o[1:1]; + mdio_o->desc = "MDIO output data bit. Driven onto the MDIO pin when mdio_oen = 1."; + cfg_f mdio_oen[2:2]; + mdio_oen->desc = "MDIO output enable. 1 = drive MDIO pin from mdio_o; 0 = MDIO pin is an input."; + sts_f mdio_i[3:3]; + mdio_i->desc = "MDIO input data sampled from the PHY. Read-only."; + } MDIO_CTRL @ 0x082C; + + /* VENDOR_ID @ 0x0830 - vendor identification - read-only */ + reg { + desc = "Device identification register. Read-only."; + + sts_f vendor_id[31:0]; + vendor_id->desc = "Always reads 0x6C525343 (ASCII for 'lRSC')" + } VENDOR_ID @ 0x0830; + + /* DEVICE_ID @ 0x0834 - device identification - read-only */ + reg { + desc = "Device identification register. Read-only."; + + sts_f device_id[31:0]; + device_id->desc = "Always reads 0x45544858 (ASCII for 'ETHX')" + } DEVICE_ID @ 0x0834; + + /* MAGIC_NUM @ 0x0838 - hardcoded number - read-only */ + reg { + desc = "Hardcoded number, useful for checking builds. Read-only."; + + sts_f magic_num[31:0]; + magic_num->desc = "Hardcoded to a random number. Read only."; + + // Should replace with an actual version numbering scheme, possibly as below + // but it's worth researching conventions for encoding compatibility info + //sts_f devel[31:30]; + //devel->desc = "Development version. 1 = development version, 0 = production version. If set, all other bits are randomly generated for identifying builds, and not indicative of a version numbering scheme."; + ///* bits 30 and 29 are reserved for future use */ + //sts_f magic[28:15]; + //magic->desc = "Magic number randomly generated for each build."; + //sts_f major[14:10]; + //major->desc = "Major version number."; + //sts_f minor[9:5]; + //minor->desc = "Minor version number."; + //sts_f patch[4:0]; + //patch->desc = "Patch version number."; + } MAGIC_NUM @ 0x0838 + + /* ----------------------------------------------------------------------- + * TX DATA BUFFER (0x1000 – 0x17FF, 2 KB, 256 × 64-bit words, write-only) + * + * Written by software before asserting TX_CTRL to start transmission. + * Max Ethernet frame is 1522 bytes = 191 64-bit words. + * Byte address of word N: 0x1000 + N*8. + * Writes during an active transmission are not blocked by hardware; + * software must not write until the TX done interrupt fires. + * ----------------------------------------------------------------------- */ + mem { + desc = "TX data buffer. 256 * 64-bit words (2 KB). Write-only."; + memwidth = 64; + mementries = 256; + sw = w; + } external TX_DATA_BUF @ 0x1000; + + /* ----------------------------------------------------------------------- + * RX DATA BUFFER (0x4000 – 0x5FFF, 8 KB, 1024 × 64-bit words, read-only) + * + * Ring buffer managed entirely by hardware; filled automatically as frames + * arrive on the RGMII interface. Software reads packets using the head_ptr + * field from the RX descriptor table. + * Byte address of word N: 0x4000 + N*8. + * ----------------------------------------------------------------------- */ + mem { + desc = "RX data buffer. 1024 * 64-bit words (8 KB). Read-only."; + memwidth = 64; + mementries = 1024; + sw = r; + } external RX_DATA_BUF @ 0x4000; + + /* ----------------------------------------------------------------------- + * RX DESCRIPTOR TABLE (0x6000 – 0x603F, 64 B, 8 × 64-bit entries) + * + * Acts as a FIFO - entry 0 is always the oldest unread packet. + * After software reads a packet it pops the entry by writing 1 to RX_POP[0]. + * Byte address of entry N: 0x6000 + N*8. + * See rx_desc_entry_t for the field layout of each entry. + * ----------------------------------------------------------------------- */ + mem { + desc = "RX descriptor table. 8 entries, each 64 bits (64 B total). Read-only."; + memwidth = 64; + mementries = 8; + sw = r; + } external rx_desc_table @ 0x6000; + + /* ========================================================================= + * RX descriptor table entry type (64-bit) + * + * Entry layout: + * [63:32] head_ptr - word index into RX data buffer; byte addr = 0x4000 + head_ptr*8 + * [31:16] pkt_len - packet length in bytes (max 1522) + * [15: 0] metadata + * [15:2] (reserved) + * [1:0] capture_reason - 2'b00=MAC_MATCHES 2'b01=MAC_BROADCAST + 2'b10=MAC_MULTICAST 2'b11=NON_MAC_MATCH + * ========================================================================= */ + +}; diff --git a/hw/vendor/ethernet/framing_top.sv b/hw/vendor/ethernet/framing_top.sv deleted file mode 100644 index a17bceff6..000000000 --- a/hw/vendor/ethernet/framing_top.sv +++ /dev/null @@ -1,413 +0,0 @@ -// See LICENSE for license details. -`default_nettype none - -module framing_top - ( - input wire msoc_clk, - input wire [14:0] core_lsu_addr, - input wire [63:0] core_lsu_wdata, - input wire [7:0] core_lsu_be, - input wire ce_d, - input wire we_d, - input wire framing_sel, - output logic [63:0] framing_rdata, - - // Internal 125 MHz clock - input wire clk_int, - input wire rst_int, - input wire clk90_int, - input wire clk_200_int, - - /* - * Ethernet: 1000BASE-T RGMII - */ - input wire phy_rx_clk, - input wire [3:0] phy_rxd, - input wire phy_rx_ctl, - output wire phy_tx_clk, - output wire [3:0] phy_txd, - output wire phy_tx_ctl, - output wire phy_reset_n, - input wire phy_int_n, - input wire phy_pme_n, - - input wire phy_mdio_i, - output reg phy_mdio_o, - output reg phy_mdio_oe, - output wire phy_mdc, - - output reg eth_irq - ); - -// obsolete signals to be removedphy_ -// - - -logic [14:0] core_lsu_addr_dly; - -logic tx_enable_i, mac_gmii_tx_en; -logic [47:0] mac_address, rx_dest_mac; -logic [10:0] tx_frame_addr, rx_length_axis[0:7], tx_packet_length; -logic [12:0] axis_tx_frame_size; -logic ce_d_dly, avail; -logic [63:0] framing_rdata_pkt, framing_wdata_pkt; -logic [3:0] tx_enable_dly, firstbuf, nextbuf, lastbuf; -logic [2:0] last; - -reg byte_sync, sync, irq_en, tx_busy; - - wire [7:0] m_enb = (we_d ? core_lsu_be : 8'hFF); - logic phy_mdclk, cooked, tx_enable_old, loopback, promiscuous; - logic [3:0] spare; - logic [10:0] rx_addr_axis; - - /* - * AXI input - */ - reg tx_axis_tvalid; - reg tx_axis_tvalid_dly; - wire tx_axis_tvalid_gated; - reg tx_axis_tlast; - wire [7:0] tx_axis_tdata; - wire tx_axis_tready; - wire tx_axis_tuser = 1'b0; - - /* - * AXI output - */ - wire [7:0] rx_axis_tdata; - wire rx_axis_tvalid; - wire rx_axis_tlast; - wire rx_axis_tuser; - - /* - * AXIS Status - */ - wire [31:0] tx_fcs_reg_rev, rx_fcs_reg_rev; - - logic last_override; - - always_ff @(posedge clk_int) begin - if (rst_int) begin - last_override <= 1'b1; - end else if (rx_axis_tvalid) begin - last_override <= 1'b0; - end - end - - always @(posedge clk_int) - if (rst_int == 1'b1) - begin - byte_sync <= 1'b1; - end - else - begin - if (rx_axis_tvalid && (byte_sync == 0) && (nextbuf != (firstbuf+lastbuf)&15)) - begin - byte_sync <= 1'b1; - end - if ((rx_axis_tlast || (last_override && !rx_axis_tvalid)) && byte_sync) - begin - last <= 1'b1; - end - else if ((last > 0) && (last < 7)) - begin - byte_sync <= 1'b0; - last <= last + 3'b1; - end - else - begin - last <= 3'b0; - end - end - - wire [1:0] rx_wr = rx_axis_tvalid << rx_addr_axis[2]; - logic [15:0] douta; - assign tx_axis_tdata = douta >> {tx_frame_addr[2],3'b000}; - assign phy_mdc = phy_mdclk; - - logic rx_use_incr_buf_offset; - logic [2:0] rx_buf_offset; - - always_ff @(posedge clk_int) begin - if (rst_int) rx_use_incr_buf_offset <= 1'b1; - else if (rx_addr_axis < 7) rx_use_incr_buf_offset <= 1'b1; - else if (rx_addr_axis == 7) rx_use_incr_buf_offset <= !sync; - end - - assign rx_buf_offset = rx_use_incr_buf_offset ? (nextbuf[2:0] + 3'b1) : nextbuf[2:0]; - - dualmem_widen8 RAMB16_inst_rx ( - .clka(clk_int), // Port A Clock - .clkb(msoc_clk), // Port A Clock - .douta(), // Port A 8-bit Data Output - .addra({rx_buf_offset,rx_addr_axis[10:3],rx_addr_axis[1:0]}), // Port A 11-bit Address Input - .dina({rx_axis_tdata,rx_axis_tdata}), // Port A 8-bit Data Input - .ena(rx_axis_tvalid), // Port A RAM Enable Input - .wea(rx_wr), // Port A Write Enable Input - .doutb(framing_rdata_pkt), // Port B 32-bit Data Output - .addrb(core_lsu_addr[13:3]), // Port B 9-bit Address Input - .dinb(core_lsu_wdata), // Port B 32-bit Data Input - .enb(ce_d & framing_sel & core_lsu_addr[14]), - // Port B RAM Enable Input - .web(we_d ? {(|core_lsu_be[7:4]),(|core_lsu_be[3:0])} : 2'b0) // Port B Write Enable Input - ); - - dualmem_widen RAMB16_inst_tx ( - .clka(~clk_int), // Port A Clock - .clkb(msoc_clk), // Port A Clock - .douta(douta), // Port A 8-bit Data Output - .addra({1'b0,tx_frame_addr[10:3],tx_frame_addr[1:0]}), // Port A 11-bit Address Input - .dina(16'b0), // Port A 8-bit Data Input - .ena(tx_axis_tvalid), // Port A RAM Enable Input - .wea(2'b0), // Port A Write Enable Input - .doutb(framing_wdata_pkt), // Port B 32-bit Data Output - .addrb(core_lsu_addr[11:3]), // Port B 9-bit Address Input - .dinb(core_lsu_wdata), // Port B 32-bit Data Input - .enb(ce_d & framing_sel & (core_lsu_addr[14:12]==3'b001)), - // Port B RAM Enable Input - .web(we_d ? {(|core_lsu_be[7:4]),(|core_lsu_be[3:0])} : 2'b0) // Port B Write Enable Input - ); - -always @(posedge msoc_clk) - if (rst_int) - begin - core_lsu_addr_dly <= 0; - mac_address <= 48'H230100890702; - tx_packet_length <= 0; - tx_enable_dly <= 0; - cooked <= 1'b0; - loopback <= 1'b0; - spare <= 4'b0; - promiscuous <= 1'b0; - phy_mdio_oe <= 1'b0; - phy_mdio_o <= 1'b0; - phy_mdclk <= 1'b0; - sync <= 1'b0; - firstbuf <= 4'b0; - lastbuf <= 4'b0; - nextbuf <= 4'b0; - eth_irq <= 1'b0; - irq_en <= 1'b0; - ce_d_dly <= 1'b0; - tx_busy <= 1'b0; - avail = 1'b0; - end - else - begin - core_lsu_addr_dly <= core_lsu_addr; - ce_d_dly <= ce_d; - avail = (nextbuf == firstbuf + 1 && !rx_use_incr_buf_offset) ? !rx_axis_tvalid : (nextbuf != firstbuf); - eth_irq <= avail & irq_en; // make eth_irq go away immediately if irq_en is low - if (framing_sel&we_d&(&core_lsu_be[3:0])&(core_lsu_addr[14:11]==4'b0001)) - case(core_lsu_addr[6:3]) - 0: mac_address[31:0] <= core_lsu_wdata; - 1: {irq_en,promiscuous,spare,loopback,cooked,mac_address[47:32]} <= core_lsu_wdata; - 2: begin tx_enable_dly <= 10; tx_packet_length <= core_lsu_wdata; end /* tx payload size */ - 3: begin tx_enable_dly <= 0; tx_packet_length <= 0; end - 4: begin {phy_mdio_oe,phy_mdio_o,phy_mdclk} <= core_lsu_wdata; end - 5: begin lastbuf <= core_lsu_wdata[3:0]; end - 6: begin firstbuf <= core_lsu_wdata[3:0]; end - default:; - endcase - if (last > 0) - begin - // check broadcast/multicast address - sync <= (rx_dest_mac[47:24]==24'h01005E) | (&rx_dest_mac) | (mac_address == rx_dest_mac) | promiscuous; - end - else if (!last) - begin - if (sync) nextbuf <= nextbuf + 1'b1; - sync <= 1'b0; - end - if (tx_axis_tlast && tx_enable_i) - begin - tx_enable_dly <= 0; - end - else if (1'b1 == |tx_enable_dly) - begin - tx_busy <= 1'b1; - tx_enable_dly <= tx_enable_dly + !(&tx_enable_dly); - end - else if (~mac_gmii_tx_en) - tx_busy <= 1'b0; - end - -always @(posedge clk_int) - if (rst_int) - begin - tx_enable_i <= 1'b0; - end - else - begin - if (tx_axis_tready && tx_axis_tvalid && tx_axis_tlast) - begin - tx_enable_i <= 1'b0; - end - else if (1'b1 == &tx_enable_dly) - tx_enable_i <= 1'b1; - end - - always @* casez({ce_d_dly,core_lsu_addr_dly[14:3]}) - 13'b10001????0000 : framing_rdata = mac_address[31:0]; - 13'b10001????0001 : framing_rdata = {irq_en, promiscuous, spare, loopback, cooked, mac_address[47:32]}; - 13'b1000?????0010 : framing_rdata = {tx_busy, 4'b0, tx_frame_addr, 5'b0, tx_packet_length}; - 13'b10001????0011 : framing_rdata = tx_fcs_reg_rev; - 13'b10001????0100 : framing_rdata = {phy_mdio_i,phy_mdio_oe,phy_mdio_o,phy_mdclk}; - 13'b10001????0101 : framing_rdata = rx_fcs_reg_rev; - 13'b10001????0110 : framing_rdata = {eth_irq, avail, lastbuf, nextbuf, firstbuf}; - 13'b10001????1??? : framing_rdata = rx_length_axis[core_lsu_addr_dly[5:3]]; - 13'b10010???????? : framing_rdata = framing_wdata_pkt; - 13'b11??????????? : framing_rdata = framing_rdata_pkt; - default: framing_rdata = 'h0; - endcase - - parameter dly = 0; - - wire [31:0] tx_fcs_reg, rx_fcs_reg; - assign tx_fcs_reg_rev = {tx_fcs_reg[0],tx_fcs_reg[1],tx_fcs_reg[2],tx_fcs_reg[3], - tx_fcs_reg[4],tx_fcs_reg[5],tx_fcs_reg[6],tx_fcs_reg[7], - tx_fcs_reg[8],tx_fcs_reg[9],tx_fcs_reg[10],tx_fcs_reg[11], - tx_fcs_reg[12],tx_fcs_reg[13],tx_fcs_reg[14],tx_fcs_reg[15], - tx_fcs_reg[16],tx_fcs_reg[17],tx_fcs_reg[18],tx_fcs_reg[19], - tx_fcs_reg[20],tx_fcs_reg[21],tx_fcs_reg[22],tx_fcs_reg[23], - tx_fcs_reg[24],tx_fcs_reg[25],tx_fcs_reg[26],tx_fcs_reg[27], - tx_fcs_reg[28],tx_fcs_reg[29],tx_fcs_reg[30],tx_fcs_reg[31]}; - assign rx_fcs_reg_rev = {rx_fcs_reg[0],rx_fcs_reg[1],rx_fcs_reg[2],rx_fcs_reg[3], - rx_fcs_reg[4],rx_fcs_reg[5],rx_fcs_reg[6],rx_fcs_reg[7], - rx_fcs_reg[8],rx_fcs_reg[9],rx_fcs_reg[10],rx_fcs_reg[11], - rx_fcs_reg[12],rx_fcs_reg[13],rx_fcs_reg[14],rx_fcs_reg[15], - rx_fcs_reg[16],rx_fcs_reg[17],rx_fcs_reg[18],rx_fcs_reg[19], - rx_fcs_reg[20],rx_fcs_reg[21],rx_fcs_reg[22],rx_fcs_reg[23], - rx_fcs_reg[24],rx_fcs_reg[25],rx_fcs_reg[26],rx_fcs_reg[27], - rx_fcs_reg[28],rx_fcs_reg[29],rx_fcs_reg[30],rx_fcs_reg[31]}; - - always @(posedge clk_int) - if (rst_int) - begin - tx_axis_tvalid <= 'b0; - tx_axis_tvalid_dly <= 'b0; - tx_frame_addr <= 'b0; - tx_axis_tlast <= 'b0; - end - else - begin - tx_enable_old <= tx_enable_i; - if (tx_enable_i & (tx_enable_old == 0)) - begin - tx_frame_addr <= 'b0; - end - if (tx_axis_tready && tx_axis_tvalid) - begin - tx_frame_addr <= tx_frame_addr + 1; - tx_axis_tlast <= (tx_frame_addr == tx_packet_length-2) & tx_axis_tvalid_dly; - end - tx_axis_tvalid <= tx_axis_tvalid_dly; - if (tx_enable_old) - tx_axis_tvalid_dly <= 1'b1; - else if (~tx_axis_tlast) - tx_axis_tvalid_dly <= 1'b0; - end - assign tx_axis_tvalid_gated = tx_axis_tvalid & tx_enable_i; - always @(posedge clk_int) - if (rst_int) - begin - rx_addr_axis <= 'b0; - rx_dest_mac <= 'b0; - end - else - begin - if (rx_axis_tvalid) - begin - rx_addr_axis <= rx_addr_axis + 1; - if (rx_addr_axis < 6) - rx_dest_mac <= {rx_dest_mac[39:0],rx_axis_tdata}; - end - if (rx_axis_tvalid && rx_axis_tlast) - begin - rx_length_axis[nextbuf[2:0]] <= rx_addr_axis + 1; - rx_addr_axis <= 'b0; - end - end - -rgmii_soc rgmii_soc1 - ( - .rst_int(rst_int), - .clk_int(clk_int), - .clk90_int(clk90_int), - .clk_200_int(clk_200_int), - /* - * Ethernet: 1000BASE-T RGMII - */ - .phy_rx_clk(phy_rx_clk), - .phy_rxd(phy_rxd), - .phy_rx_ctl(phy_rx_ctl), - .phy_tx_clk(phy_tx_clk), - .phy_txd(phy_txd), - .phy_tx_ctl(phy_tx_ctl), - .phy_reset_n(phy_reset_n), - .phy_int_n(phy_int_n), - .phy_pme_n(phy_pme_n), - .mac_gmii_tx_en(mac_gmii_tx_en), - .tx_axis_tdata(tx_axis_tdata), - .tx_axis_tvalid(tx_axis_tvalid_gated), - .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), - .rx_fcs_reg(rx_fcs_reg), - .tx_fcs_reg(tx_fcs_reg) -); - -// `define XILINX_ILA_1 -// `define XILINX_ILA_2 -// `define XILINX_ILA_3 - -`ifdef XILINX_ILA_1 -xlnx_ila_1 eth_ila_clk_rx ( - .clk(clk_int), // input wire clk - .probe0(rx_axis_tdata), // input wire [7:0] probe4 - .probe1(rx_axis_tvalid), // input wire [0:0] probe5 - .probe2(rx_axis_tlast), // input wire [0:0] probe6 - .probe3(rx_axis_tuser), // input wire [0:0] probe7 - .probe4(byte_sync), - .probe5(last), - .probe6(rx_addr_axis), - .probe7(rx_dest_mac), - .probe8(rx_length_axis[nextbuf[2:0]]), - .probe9(rx_clk) -); -`endif - -`ifdef XILINX_ILA_2 -xlnx_ila_2 eth_ila_clk_int ( - .clk(clk_int), // input wire clk - .probe0(tx_axis_tdata), // input wire [7:0] probe0 - .probe1(tx_axis_tvalid), // input wire [0:0] probe1 - .probe2(tx_axis_tready), // input wire [0:0] probe2 - .probe3(tx_axis_tlast), // input wire [0:0] probe3 - .probe4(tx_enable_i), - .probe5(douta), - .probe6(tx_axis_tvalid_dly), - .probe7(tx_frame_addr), - .probe8(mac_gmii_tx_en), - .probe9(tx_enable_old) -); -`endif - -`ifdef XILINX_ILA_3 -xlnx_ila_3 eth_ila_clk_msoc ( - .clk(msoc_clk), // input wire clk - .probe0(sync), - .probe1(avail), - .probe2(nextbuf), - .probe3(tx_enable_dly), - .probe4(tx_busy) -); -`endif - -endmodule // framing_top -`default_nettype wire diff --git a/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.core b/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.core new file mode 100644 index 000000000..f1ae941b4 --- /dev/null +++ b/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.core @@ -0,0 +1,109 @@ +CAPI=2: + +# This is a FuseSoC .core file +# https://fusesoc.readthedocs.io/_/downloads/en/stable/pdf/ + +name: "lowrisc:ethernet:axi_top" +description: "Ethernet MAC and framing top with internal TX/RX buffers and an AXI4 interface" + +filesets: + sim_only: + depend: + - lowrisc:prim_generic:all + + synth_only: + files: + - synth/axi_ethernet.xdc: {file_type: xdc} + - synth/axi_ethernet.tcl: {file_type: tclSource} + - synth/ethernet_top_axi_synth.sv: {file_type: systemVerilogSource} # top-level for trial synthesis. Thin wrapper. Not necessary for using the design in your own SoC + depend: + - lowrisc:prim_xilinx:all + # including this will cause any virtual cores to point to their corresponding XILINX concrete cores (as opposed to generic primitives) + + rtl: + files: + - ethernet_top_axi.sv + file_type: systemVerilogSource + depend: + - lowrisc:ethernet:top + - pulp-platform.org::axi:0.39.9 + # this core is originally part of Pulp but was modified by lowRISC to use lowRISC primitives. Behaviour should be the same. The fusesoc.conf file attached to this repo points to the lowRISC version. + + cocotb: + files: + - verif/ethernet_top_axi_tb.sv: {file_type : systemVerilogSource} + - cov/ethernet_csr_cov.sv: {file_type : systemVerilogSource} # TODO separate file list for coverage things. Also move this file to be adjacent to its target + - cov/axis_pkt_ring_buffer_cov.sv: {file_type : systemVerilogSource} # TODO separate file list for coverage things. Also move this file to be adjacent to its target + - verif/coco_top.py: {file_type : user , copyto: .} + - verif/dut_utils.py: {file_type : user , copyto: .} + - verif/test_utils.py: {file_type : user , copyto: .} + - verif/models.py: {file_type : user , copyto: .} + + lint_waivers: + files: + - axi_ethernet.vlt: { file_type: vlt } + +targets: + default: &default_target + filesets: + - rtl + + sim: + <<: *default_target + flow: sim + flow_options: + tool: verilator + cocotb_module: coco_top + + verilator_options: # arguments to BUILD Verilator with + - "--timing" + - "-Wno-fatal" # don't exit on warnings TODO remove this once warnings are fixed + - "--trace" # enable tracing + - "--trace-fst" # trace to an FST file (instead of VCD) + - "--trace-structs" # trace structs (e.g. packed structs) properly + + # Toggle coverage (optional) + - tcov ? ("--coverage-toggle") # tracks toggle coverage + + # FSM coverage (optional) + - fcov ? ("--coverage-fsm") # tracks FSMs coverage + + # Properties and cover groups (optional) + - pcov ? ("--coverage-user") # tracks coverage of properties + - pcov ? ("-DCOVERAGE") # define `COVERAGE + + run_options: # arguments to RUN Verilator with + - "--trace" # enable tracing + - "--trace-fst" # trace to an FST file (instead of VCD) + - "--trace-structs" # trace structs (e.g. packed structs) properly + + timescale: 1ns/1ns + filesets_append: + - sim_only + - cocotb + toplevel: ethernet_top_axi_tb + + trial_synth: + <<: *default_target + default_tool: vivado + toplevel: ethernet_top_axi_synth + filesets_append: + - synth_only + tools: + vivado: + part: xc7k325tffg900-2 + + lint: + <<: *default_target + default_tool: verilator + filesets_append: + - sim_only + - lint_waivers + - cocotb # TODO we only need the top-level not the Python files... + toplevel: ethernet_top_axi_tb + tools: + verilator: + mode: lint-only + verilator_options: + - "-Wall" + - "--timing" diff --git a/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.vlt b/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.vlt new file mode 100644 index 000000000..f468437ea --- /dev/null +++ b/hw/vendor/ethernet/hw/axi_eth/axi_ethernet.vlt @@ -0,0 +1,16 @@ +`verilator_config + +// The AXI utilities from Pulp have a lot of lint issues +// We will waive these +// TODO fix them upstream +lint_off -rule DECLFILENAME -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule GENUNNAMED -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule UNUSEDSIGNAL -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule UNUSEDPARAM -file "*/pulp-platform*_axi*/src/*.sv" -match "*AxiIdWidth*" +lint_off -rule WIDTHTRUNC -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule WIDTHEXPAND -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule UNOPTFLAT -file "*/pulp-platform*_axi*/src/*.sv" +lint_off -rule UNUSEDPARAM -file "*/pulp-platform*_axi*/src/*.sv" + +lint_off -rule DECLFILENAME -file "*/lowrisc_common_cells*/rtl/*.sv" +lint_off -rule GENUNNAMED -file "*/lowrisc_common_cells*/rtl/*.sv" diff --git a/hw/vendor/ethernet/hw/axi_eth/cov/axis_pkt_ring_buffer_cov.sv b/hw/vendor/ethernet/hw/axi_eth/cov/axis_pkt_ring_buffer_cov.sv new file mode 100644 index 000000000..61a6d1f76 --- /dev/null +++ b/hw/vendor/ethernet/hw/axi_eth/cov/axis_pkt_ring_buffer_cov.sv @@ -0,0 +1,52 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: axis_pkt_ring_buffer_cov +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Coverage collection for the axis_pkt_ring_buffer + +NOTE: This module is experimental and not yet fully implemented. + +*/ + +module axis_pkt_ring_buffer_cov; +`ifdef COVERAGE + covergroup axis_pkt_ring_buffer_cg @(posedge axis_pkt_ring_buffer.clk_i); + // AXI Stream + cp_pkt_tvalid : coverpoint axis_pkt_ring_buffer.pkt_tvalid_i; + cp_pkt_tlast : coverpoint axis_pkt_ring_buffer.pkt_tlast_i; + cp_pkt_abandon : coverpoint axis_pkt_ring_buffer.pkt_abandon_i; + + // Packet handling + cp_commit_pkt : coverpoint axis_pkt_ring_buffer.commit_pkt; + cp_abandon_pkt : coverpoint axis_pkt_ring_buffer.abandon_pkt; + cp_buf_write : coverpoint axis_pkt_ring_buffer.buf_write; + cp_buf_almost_full : coverpoint axis_pkt_ring_buffer.buf_almost_full_o; + cp_buf_full : coverpoint axis_pkt_ring_buffer.buf_full; + cp_table_full : coverpoint axis_pkt_ring_buffer.table_full_o; + cp_table_almost_full : coverpoint axis_pkt_ring_buffer.table_almost_full_o; + + // Crosses + cp_pkt_tlastXcp_buf_full : cross cp_pkt_tlast, cp_buf_full; // cover last during full buffer, or full buffer without last + cp_pkt_tlastXcp_table_full : cross cp_pkt_tlast, cp_table_full; // cover last during full table, or full table without last + endgroup + axis_pkt_ring_buffer_cg axis_pkt_ring_buffer_cg_inst; + initial axis_pkt_ring_buffer_cg_inst = new(); + + property full_table_pop_during_packet; + @(posedge axis_pkt_ring_buffer.clk_i) disable iff (!axis_pkt_ring_buffer.rst_ni) + 1 + // TODO express this property + // - the table was full when a packet began + // - a pop occured while the packet was being received + // - the packet was committed (not abandoned) + endproperty cover property(full_table_pop_during_packet); + +`endif +endmodule diff --git a/hw/vendor/ethernet/hw/axi_eth/cov/ethernet_csr_cov.sv b/hw/vendor/ethernet/hw/axi_eth/cov/ethernet_csr_cov.sv new file mode 100644 index 000000000..60b9aec28 --- /dev/null +++ b/hw/vendor/ethernet/hw/axi_eth/cov/ethernet_csr_cov.sv @@ -0,0 +1,111 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_csr_cov +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Coverage collection for the CSR block. Intended to be bound to the CSR block +by a testbench. Collects coverage on all the configuration and status registers, +as well as: +- cross-coverage on pairs of modes +- cross-coverage on status and events + +This module makes use of upward hierarchical references to the CSR block, so it +must be bound to it. This is a very useful, albeit unintuitive SystemVerilog +feature: if THIS module makes reference to 'ethernet_csr', and there is nothing +in THIS scope called 'ethernet_csr', it will look upward in the hierarchy for +an instance or object with that name, or (as in this case) a module with that +name. + +*/ + +module ethernet_csr_cov; +`ifdef COVERAGE + covergroup ethernet_csr_cg @(posedge ethernet_csr.clk_i); + // TODO this covergroup is too large and should be split into multiple smaller ones + + //////////////// + // Interrupts // + //////////////// + cp_intr_tx_done : coverpoint ethernet_csr.interrupts[5]; // cover true or false + cp_intr_manual : coverpoint ethernet_csr.interrupts[6]; // cover true or false + cp_intr_pkt_lost : coverpoint ethernet_csr.interrupts[4]; // cover true or false + + ////////////////////// + // RX Configuration // + ////////////////////// + cp_promiscuity : coverpoint ethernet_csr.rx_config_o.promiscuous_mode; // cover true or false + cp_loopback : coverpoint ethernet_csr.mac_config_o.loopback; // cover true or false + cp_mac_addr : coverpoint ethernet_csr.rx_config_o.mac_addr { + bins all_set = {48'hFFFFFFFFFFFF}; + bins all_clear = {48'h000000000000}; + bins random = default; + }; // cover min, max, and random MAC addresses + cp_pop : coverpoint ethernet_csr.rx_pop_o; // cover true or false + + /////////////// + // RX Status // + /////////////// + cp_table_full : coverpoint ethernet_csr.rx_status_i.table_full; // cover true or false + cp_table_almost_full : coverpoint ethernet_csr.rx_status_i.table_almost_full; // cover true or false + cp_buf_almost_full : coverpoint ethernet_csr.rx_status_i.buf_almost_full; // cover true or false + cp_rx_buf_empty : coverpoint ethernet_csr.rx_status_i.empty; // cover true or false + cp_pkt_lost : coverpoint ethernet_csr.rx_status_i.pkt_lost_pulse; // cover true or false + cp_n_packets : coverpoint ethernet_csr.rx_status_i.n_packets_in_rx_buf { + bins zero = {4'd0}; + bins random = {[4'd1:4'd7]}; + bins max = {4'd8}; + }; // cover min, max, and random number of packets in RX buffer + + ////////////////////// + // TX Configuration // + ////////////////////// + cp_tx_packet_len : coverpoint ethernet_csr.tx_config_o.packet_len { + bins zero = {11'd0}; // TODO we have a bit of a problem here because I've covered all these different packet lengths, but the coverage still gets tracked even if there is no TX going on. We should only consider packet lengths WHEN a TX is actually kicked, maybe by using a sample() function? + bins illegal_low = {[11'd1:11'd55]}; + bins min = {11'd56}; + bins random_legal = {[11'd57:11'd1517]}; + bins max = {11'd1518}; + bins illegal_high = {[11'd1519:11'd2046]}; + bins sat = {11'd2047}; + }; // cover min, max, and random packet lengths + cp_kick_tx : coverpoint ethernet_csr.tx_kick_o; // cover true or false + + /////////////// + // TX Status // + /////////////// + cp_tx_busy : coverpoint ethernet_csr.tx_status_i.busy; // cover true or false + cp_tx_done : coverpoint ethernet_csr.tx_status_i.done_pulse; // cover true or false + + //////////////// + // RX Crosses // + //////////////// + + // Modes, pairwise crosses + cp_popXcp_n_packets : cross cp_pop, cp_n_packets; // cover all combinations of pop and number of packets in RX buffer + cp_promiscuityXloopback : cross cp_promiscuity, cp_loopback; // cover all combinations of promiscuous and loopback mode + cp_promiscuityXmac_addr : cross cp_promiscuity, cp_mac_addr; // cover all combinations of promiscuous mode and MAC address + cp_loopbackXmac_addr : cross cp_loopback, cp_mac_addr; // cover all combinations of loopback mode and MAC address + + // Status crossed with events + cp_table_fullXpkt_lost : cross cp_table_full, cp_pkt_lost; // cover all combinations of table full and packet lost + cp_table_almost_fullXpkt_lost : cross cp_table_almost_full, cp_pkt_lost; // cover all combinations of table almost full and packet lost + cp_buf_almost_fullXpkt_lost : cross cp_buf_almost_full, cp_pkt_lost; // cover all combinations of buffer almost full and packet lost + + //////////////// + // TX Crosses // + //////////////// + cp_tx_busyXkick_tx : cross cp_tx_busy, cp_kick_tx; // cover all combinations of TX busy and kick TX + cp_tx_doneXkick_tx : cross cp_tx_done, cp_kick_tx; // cover all combinations of TX done and kick TX (getting both simultaneously could be tricky!) + endgroup + // TODO cover reads/writes to every register + + ethernet_csr_cg ethernet_csr_cg_inst; + initial ethernet_csr_cg_inst = new(); +`endif +endmodule diff --git a/hw/vendor/ethernet/hw/axi_eth/ethernet_top_axi.sv b/hw/vendor/ethernet/hw/axi_eth/ethernet_top_axi.sv new file mode 100644 index 000000000..6af0a9ec4 --- /dev/null +++ b/hw/vendor/ethernet/hw/axi_eth/ethernet_top_axi.sv @@ -0,0 +1,140 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_top_axi +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Wraps ethernet_top and adapts the memory interface to an AXI interface. +The user should pass the AXI struct types in as parameters - they should the +AXI struct types defined by the Pulp AXI platform. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +This module is a testbench top level with a comprehensive CocoTB test suite +See coco_top.py and models.py for details of the testbench +The tests are intended to be invoked via FuseSoC, which will invoke CocoTB and +Verilator. Test results are written to "coco.log". + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +ethernet_top_axi #( + .TARGET(TARGET), + .axi_req_t(axi_req_t), + .axi_rsp_t(axi_rsp_t) +) dut ( + .clk_125M_i (), + .clk_125M_quad_i (), + .clk_200M_i (), + .rst_ni (), + .axi_req_i (), + .axi_rsp_o (), + .ethernet_irq_o (), + .phy_reset_no (), + .eth_rgmii_rx_i (), + .eth_rgmii_tx_o (), + .eth_rgmii_mdio_i (), + .eth_rgmii_mdio_o () +); + +*/ + +module ethernet_top_axi #( + parameter TARGET = "SIM", // "SIM", "GENERIC", "XILINX", or "ALTERA" + parameter type axi_req_t = logic, + parameter type axi_rsp_t = logic +) ( + // Clocking and reset + input logic clk_125M_i, // 125 MHz ethernet in-phase clock + input logic clk_125M_quad_i, // 125 MHz ethernet quadrature clock + input logic clk_200M_i, // 200 MHz IDELAYCTRL reference clock + input logic rst_ni, // Ethernet MAC reset, deassertion synchronous to clk_125M_i + + // AXI device interface + input axi_req_t axi_req_i, + output axi_rsp_t axi_rsp_o, + + // Interrupt out + output logic ethernet_irq_o, + + // RGMII signals to ethernet PHY + output logic phy_reset_no, // Active low reset to PHY (note: "no" is not short for "number"; it's 'n' for active-low and 'o' for output) + input ethernet_pkg::eth_rgmii_rx_t eth_rgmii_rx_i, + output ethernet_pkg::eth_rgmii_tx_t eth_rgmii_tx_o, + input ethernet_pkg::eth_rgmii_mdio_in_t eth_rgmii_mdio_i, + output ethernet_pkg::eth_rgmii_mdio_out_t eth_rgmii_mdio_o +); + // Memory interface to ethernet MAC + mem_if_utils_pkg::mem_req_t eth_mem_req; + mem_if_utils_pkg::mem_rsp_t eth_mem_rsp; + + logic eth_rvalid; + always_ff @ (posedge clk_125M_i or negedge rst_ni) begin + if (!rst_ni) eth_rvalid <= 1'b0; + else eth_rvalid <= eth_mem_rsp.gnt && eth_mem_req.req; + end + + // AXI to memory interface + axi_to_detailed_mem #( + .axi_req_t ( axi_req_t ), + .axi_resp_t ( axi_rsp_t ), + .AddrWidth ( 64 ), + .DataWidth ( 64 ), + .IdWidth ( 4 ), + .UserWidth ( 1 ), + .NumBanks ( 1 ) + ) i_axi_to_detailed_mem ( + .clk_i ( clk_125M_i ), + .rst_ni ( rst_ni ), + .busy_o (), + .axi_req_i ( axi_req_i ), + .axi_resp_o ( axi_rsp_o ), + .mem_req_o ( eth_mem_req.req ), + .mem_gnt_i ( eth_mem_rsp.gnt ), + .mem_addr_o ( eth_mem_req.addr ), + .mem_wdata_o ( eth_mem_req.data ), + .mem_strb_o ( eth_mem_req.be ), + .mem_atop_o (), // ignored + .mem_lock_o (), // ignored + .mem_we_o ( eth_mem_req.we ), + .mem_id_o (), // ignored + .mem_user_o (), // ignored + .mem_cache_o (), // ignored + .mem_prot_o (), // ignored + .mem_qos_o (), // ignored + .mem_region_o (), // ignored + .mem_cheri_tag_o (), // ignored + .mem_rvalid_i ( eth_rvalid ), + .mem_rdata_i ( eth_mem_rsp.data ), + .mem_err_i ( eth_mem_rsp.err ), + .mem_exokay_i ('0), // drive to zero because mem_lock_o is not used + .mem_cheri_tag_i ('0) // not using Cheri stuff + ); + + ////////////////////////////// + // Instantiate ethernet_top // + ////////////////////////////// + ethernet_top #( + .TARGET(TARGET) + ) ethernet_top_inst ( + // Clocking and reset + .clk_125M_i (clk_125M_i), // Main clock - used by memory interface and as 125 MHz ethernet in-phase clock + .rst_ni (rst_ni), // Main reset, deassertion synchronous to clk_125M_i + .clk_125M_quad_i (clk_125M_quad_i), // 125 MHz ethernet quadrature clock (used by MAC) + .clk_200M_i (clk_200M_i), // 200 MHz IDELAYCTRL reference clock + .mem_req_i (eth_mem_req), // Synchronous to clk_125M_i + .mem_rsp_o (eth_mem_rsp), // Synchronous to clk_125M_i + .phy_reset_no (phy_reset_no), + .eth_rgmii_rx_i (eth_rgmii_rx_i), + .eth_rgmii_tx_o (eth_rgmii_tx_o), + .eth_rgmii_mdio_i (eth_rgmii_mdio_i), + .eth_rgmii_mdio_o (eth_rgmii_mdio_o), + .irq_o (ethernet_irq_o) + ); +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/csr/ethernet_csr.sv b/hw/vendor/ethernet/hw/core_eth/csr/ethernet_csr.sv new file mode 100644 index 000000000..d37660cba --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/csr/ethernet_csr.sv @@ -0,0 +1,314 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_csr +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Control and status registers for the Ethernet IP. +See ethernet.rdl for a specification of registers. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +`include "prim_assert.sv" + +module ethernet_csr ( + input clk_i, + input rst_ni, + input mem_if_utils_pkg::mem_req_t mem_req_i, + output mem_if_utils_pkg::mem_rsp_t mem_rsp_o, + output ethernet_pkg::mac_config_t mac_config_o, + input ethernet_pkg::mac_status_t mac_status_i, + output ethernet_pkg::rx_config_t rx_config_o, + input ethernet_pkg::rx_status_t rx_status_i, + output logic rx_pop_o, + output ethernet_pkg::tx_config_t tx_config_o, + input ethernet_pkg::tx_status_t tx_status_i, + input ethernet_pkg::eth_rgmii_mdio_in_t eth_rgmii_mdio_i, + output ethernet_pkg::eth_rgmii_mdio_out_t eth_rgmii_mdio_o, + output logic tx_kick_o, + output logic irq_o +); + logic unused; + assign unused = ^mac_status_i; + + // TODO this module could do with refactoring, it's pretty ugly and doesn't support strobing or writing 0/2 CSRs in the same transaction + // also ideally should be autogenerated from the RDL + + // See ethernet.rdl for regmap. Addresses are: + typedef enum logic [ethernet_pkg::CSR_ADDR_W-1:0] { + REG_INTR_STATE_ADDR = 7'h00, + REG_INTR_MASK_ADDR = 7'h04, + REG_INTR_TEST_ADDR = 7'h08, + REG_CTRL_ADDR = 7'h10, + REG_STATUS_ADDR = 7'h14, + REG_MACLO_ADDR = 7'h18, + REG_MACHI_ADDR = 7'h1C, + REG_TX_CTRL_ADDR = 7'h20, + REG_RX_POP_ADDR = 7'h28, + REG_MDIO_CTRL_ADDR = 7'h2C, + REG_VENDOR_ID_ADDR = 7'h30, + REG_DEVICE_ID_ADDR = 7'h34, + REG_MAGIC_NUM_ADDR = 7'h38 + } reg_addr_e; + + ////////////////////////////////////// + // Memory-mapped register interface // + ////////////////////////////////////// + reg_addr_e reg_addr; + assign reg_addr = (reg_addr_e)'({mem_req_i.addr[ethernet_pkg::CSR_ADDR_W-1:3], (mem_req_i.be[0] ? 3'h0 : 3'h4)}); // if we are writing, ensure the address is aligned to the CSR actually being targetted by the byte-enable signal. It doesn't matter if we are reading + logic bad_request,bad_addr; + assign bad_addr = ~( + (reg_addr == REG_INTR_STATE_ADDR ) || + (reg_addr == REG_INTR_MASK_ADDR ) || + (reg_addr == REG_INTR_TEST_ADDR ) || + (reg_addr == REG_CTRL_ADDR ) || + (reg_addr == REG_STATUS_ADDR ) || + (reg_addr == REG_MACLO_ADDR ) || + (reg_addr == REG_MACHI_ADDR ) || + (reg_addr == REG_TX_CTRL_ADDR ) || + (reg_addr == REG_RX_POP_ADDR ) || + (reg_addr == REG_MDIO_CTRL_ADDR ) || + (reg_addr == REG_VENDOR_ID_ADDR ) || + (reg_addr == REG_DEVICE_ID_ADDR ) || + (reg_addr == REG_MAGIC_NUM_ADDR ) + ); + assign bad_request = ( + (bad_addr) || // access to undefined register + (mem_req_i.addr[1:0] != 2'h0) || // unaligned access + ((mem_req_i.be != 8'h0F) & (mem_req_i.be != 8'hF0) & (mem_req_i.we)) || // do not allow writing 0 or 2 CSRs simultaneously + (mem_req_i.we && (reg_addr inside {REG_STATUS_ADDR,REG_VENDOR_ID_ADDR,REG_DEVICE_ID_ADDR,REG_MAGIC_NUM_ADDR})) // do not allow writes to read-only registers + ); + assign mem_rsp_o.gnt = mem_req_i.req; + + logic write; + assign write = mem_req_i.req && mem_req_i.we && !bad_request; + + logic [6:0] intr_mask; + + logic tx_done_sticky; + logic manual_irq_sticky; + logic packet_lost_sticky; + + logic loopback_mode; + logic promiscuous_mode; + logic [47:0] mac_addr; + logic [10:0] tx_packet_len; + + logic mdio_clk; + logic mdio_o; + logic mdio_oen; + + //////////////// + // Interrupts // + //////////////// + logic manual_irq_pulse; + + logic [6:0] interrupts; + assign interrupts[6] = manual_irq_pulse || manual_irq_sticky; + assign interrupts[5] = tx_status_i.done_pulse || tx_done_sticky; + assign interrupts[4] = rx_status_i.pkt_lost_pulse || packet_lost_sticky; + assign interrupts[3] = rx_status_i.buf_almost_full; + assign interrupts[2] = rx_status_i.table_full; + assign interrupts[1] = rx_status_i.table_almost_full; + assign interrupts[0] = ~rx_status_i.empty; // RX buffer not empty + + // Three of the interrupts are event-triggered and sticky, with write-to-clear behaviour + // They are implemented here + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + tx_done_sticky <= 1'b0; + manual_irq_sticky <= 1'b0; + packet_lost_sticky <= 1'b0; + end else begin + // Clear sticky bits when the interrupt is acknowledged by software + if (write && (reg_addr == REG_INTR_STATE_ADDR)) begin + if (mem_req_i.data[4]) packet_lost_sticky <= 1'b0; + if (mem_req_i.data[5]) tx_done_sticky <= 1'b0; + if (mem_req_i.data[6]) manual_irq_sticky <= 1'b0; + end else begin + // Set sticky bits when the corresponding interrupt condition is met + if (interrupts[4]) packet_lost_sticky <= 1'b1; + if (interrupts[5]) tx_done_sticky <= 1'b1; + if (interrupts[6]) manual_irq_sticky <= 1'b1; + end + // note that you cannot clear RX status interrupts with a write - they must be serviced or masked + end + end + + ///////////////// + // Write logic // + ///////////////// + + // Register INTR_MASK + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) intr_mask <= '0; // disable all interrupts after reset + else if (write && (reg_addr == REG_INTR_MASK_ADDR)) intr_mask <= mem_req_i.data[32+6:32]; + end + + // Register INTR_TEST + assign manual_irq_pulse = (write && (reg_addr == REG_INTR_TEST_ADDR) && mem_req_i.data[0]); // pulse an interrupt whenever 1 is written to this register + + // Register CTRL + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + promiscuous_mode <= 1'b0; + loopback_mode <= 1'b0; + end + else if (write && (reg_addr == REG_CTRL_ADDR)) begin + promiscuous_mode <= mem_req_i.data[0]; + loopback_mode <= mem_req_i.data[1]; + end + end + + // Registers MACLO and MACHI + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) mac_addr[47:0] <= 48'h0; + else if (write && (reg_addr == REG_MACLO_ADDR)) mac_addr[31:0] <= mem_req_i.data[31:0]; // MACLO + else if (write && (reg_addr == REG_MACHI_ADDR)) mac_addr[47:32] <= mem_req_i.data[47:32]; // MACHI + end + + // Register TX_CTRL - note that side effect of kicking off a transmission + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) tx_packet_len <= 11'h0; + else if (write && (reg_addr == REG_TX_CTRL_ADDR)) tx_packet_len <= mem_req_i.data[10:0]; + end + assign tx_kick_o = (write && (reg_addr == REG_TX_CTRL_ADDR) && (|mem_req_i.data[10:0])); // kick off a transmission whenever TX_CTRL is written with nonzero packet length + // TODO reject writes to this register if another TX is already in progress + + // Register RX POP - write-only with side effect + assign rx_pop_o = (write && (reg_addr == REG_RX_POP_ADDR) && mem_req_i.data[0]); // pop a packet from the RX buffer whenever 1 is written to this register + + // Register MDIO_CTRL - mix of read-only and read-write + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + mdio_clk <= 1'b0; + mdio_o <= 1'b0; + mdio_oen <= 1'b0; + end + else if (write && (reg_addr == REG_MDIO_CTRL_ADDR)) begin + mdio_clk <= mem_req_i.data[32+0]; + mdio_o <= mem_req_i.data[32+1]; + mdio_oen <= mem_req_i.data[32+2]; + end + end + + ///////////////// + // Device info // + ///////////////// + // Make vendor ID, device ID, device version available as read-only field + logic [31:0] vendor_id, device_id, magic_num; + assign vendor_id = ethernet_pkg::VENDOR_ID; + assign device_id = ethernet_pkg::DEVICE_ID; + assign magic_num = ethernet_pkg::MAGIC_NUM; // random magic number to identify the build + + //////////////// + // Read logic // + //////////////// + + logic [31:0] status; + assign status = { + 20'b0, // upper bits reserved + rx_status_i.n_packets_in_rx_buf, // 4 bits status[11:8] + tx_status_i.busy, + 3'b0, // reserved bits + rx_status_i.buf_almost_full, + rx_status_i.table_full, + rx_status_i.table_almost_full, + ~rx_status_i.empty // bit 0 : RX buffer not empty + }; + + logic [63:0] read_data; + assign mem_rsp_o.data = read_data; + + // NOTE: because the data bus is 64 bits wide, we read two 32-bit registers at a time here + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) read_data <= '0; + else begin + unique case ({reg_addr[ethernet_pkg::CSR_ADDR_W-1:3],3'b0}) + (REG_INTR_STATE_ADDR) : read_data <= {25'h0, intr_mask, 25'h0, interrupts}; + // REG_INTR_MASK_ADDR absorbed into the above + (REG_INTR_TEST_ADDR) : read_data <= '0; + // reserved address here + (REG_CTRL_ADDR) : read_data <= {status, 30'h0, loopback_mode, promiscuous_mode}; + // REG_STATUS_ADDR absorbed into the above + (REG_MACLO_ADDR) : read_data <= {16'h0, mac_addr}; + // REG_MACHI_ADDR absorbed into the above + (REG_TX_CTRL_ADDR) : read_data <= {53'h0, tx_packet_len}; + // reserved address here + (REG_RX_POP_ADDR) : read_data <= {28'h0, eth_rgmii_mdio_i, mdio_oen, mdio_o, mdio_clk, 32'h0}; + // REG_MDIO_CTRL_ADDR absorbed into the above + (REG_VENDOR_ID_ADDR) : read_data <= {device_id, vendor_id}; + // REG_DEVICE_ID_ADDR absorbed into the above + (REG_MAGIC_NUM_ADDR) : read_data <= {32'h0, magic_num}; + // remaining addresses reserved + default: read_data <= '0; // undefined registers read as zero + endcase + end + end + + logic mem_rsp_o_err_q; + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) mem_rsp_o_err_q <= 1'b0; + else mem_rsp_o_err_q <= bad_request; // respond with an error if the request is invalid + end // TODO detect writes to read-only registers + assign mem_rsp_o.err = mem_rsp_o_err_q; + + /////////////////////// + // Assign to outputs // + /////////////////////// + assign rx_config_o.mac_addr = mac_addr; + assign rx_config_o.promiscuous_mode = promiscuous_mode; + + assign tx_config_o.packet_len = tx_packet_len; + assign irq_o = |(intr_mask & interrupts); // interrupt output is the masked combination of all interrupt sources + + assign mac_config_o.loopback = loopback_mode; + + assign eth_rgmii_mdio_o.o = mdio_o; + assign eth_rgmii_mdio_o.oen = mdio_oen; + assign eth_rgmii_mdio_o.c = mdio_clk; + + //////////////// + // Assertions // + //////////////// + // Assert that the memory interface behaves correctly + mem_if_assertions u_mem_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(mem_req_i), + .mem_rsp_o(mem_rsp_o) + ); + + // Assert that the outputs are known + `ASSERT_KNOWN(MacConfigOLoopback_A , mac_config_o.loopback); + `ASSERT_KNOWN(RxConfigOMacAddr_A , rx_config_o.mac_addr); + `ASSERT_KNOWN(RxConfigOPromiscuousMode_A , rx_config_o.promiscuous_mode); + `ASSERT_KNOWN(RxPopO_A , rx_pop_o); + `ASSERT_KNOWN(TxConfigOPacketLen_A , tx_config_o.packet_len); + `ASSERT_KNOWN(TxKickO_A , tx_kick_o); + `ASSERT_KNOWN(IrqO_A , irq_o); + + // Assert that tx_kick_o is a pulse + `ASSERT_PULSE(CSR_TxKickPulseO_A, tx_kick_o); + + // Assert the values under reset + `ASSERT(RstCfgLoopback_A, !rst_ni |-> mac_config_o.loopback == 1'b0, clk_i, 0); + `ASSERT(RstCfgPromiscuous_A, !rst_ni |-> rx_config_o.promiscuous_mode == 1'b0, clk_i, 0); + `ASSERT(RstCfgMacAddr_A, !rst_ni |-> rx_config_o.mac_addr == 48'h0, clk_i, 0); + `ASSERT(RstCfgTxPacketLen_A, !rst_ni |-> tx_config_o.packet_len == 11'h0, clk_i, 0); + `ASSERT(RstCfgTxKick_A, !rst_ni |-> tx_kick_o == 1'b0, clk_i, 0); + `ASSERT(RstCfgRxPop_A, !rst_ni |-> rx_pop_o == 1'b0, clk_i, 0); + `ASSERT(RstCfgIrq_A, !rst_ni |-> irq_o == 1'b0, clk_i, 0); + // ^ clk, ^ normally a reset to disable the assertion, but we want these to be true even under reset +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/ethernet.core b/hw/vendor/ethernet/hw/core_eth/ethernet.core new file mode 100644 index 000000000..31ea791c0 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/ethernet.core @@ -0,0 +1,87 @@ +CAPI=2: + +# This is a FuseSoC .core file +# https://fusesoc.readthedocs.io/_/downloads/en/stable/pdf/ + +name: "lowrisc:ethernet:top" +description: "Ethernet MAC and framing top with internal TX/RX buffers and a single simple memory interface" + +filesets: + sim_only: + depend: + - lowrisc:prim_generic:all + file_type: systemVerilogSource + + rtl: + files: + - top/ethernet_pkg.sv + - loopback/axis_pkt_isolate.sv + - loopback/axis_pkt_mux.sv + - loopback/ethernet_loopback.sv + - rx_framing/mac_address_filter.sv + - rx_framing/ethernet_rx_framing.sv + - tx_framing/ethernet_tx_framing.sv + - csr/ethernet_csr.sv + - top/mac_wrapper.sv + - top/ethernet_mem_map.sv + - top/ethernet_top.sv + file_type: systemVerilogSource + depend: + - lowrisc:ethernet:axis_pkt_ring_buffer + - lowrisc:ethernet:mac + - lowrisc:ethernet:mem_if_utils + - lowrisc:prim:assert:0.1 + - lowrisc:prim:util:0.1 + - lowrisc:prim:fifo + + tb: + files: + - verif/ethernet_top_tb.sv + file_type: systemVerilogSource + +parameters: + SYNTHESIS: + datatype: bool + paramtype: vlogdefine + +targets: + default: &default_target + filesets: + - rtl + + sim: + <<: *default_target + default_tool: xsim + filesets_append: + - sim_only + - tb + toplevel: ethernet_top_tb + tools: + xsim: + xelab_options: [ + --debug, typical, # necessary to be able to inspect waveforms + --timescale, 1ns/1ps # default timescale for any moduels that don't specify one + ] + + synth: + <<: *default_target + default_tool: vivado + toplevel: ethernet_top + filesets_append: + - synth_only + tools: + vivado: + part: xc7k325tffg900-2 + + lint: + <<: *default_target + default_tool: verilator + filesets_append: + - sim_only + toplevel: ethernet_top + tools: + verilator: + mode: lint-only + verilator_options: + - "-Wall" + - "--timing" diff --git a/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_isolate.sv b/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_isolate.sv new file mode 100644 index 000000000..4abe9cc4f --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_isolate.sv @@ -0,0 +1,68 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: axis_pkt_isolate +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Isolator for an AXI Stream with packets delimeted by tlast. + +When isolate_i is asserted, the isolator will first complete any in-progress +packet, then zero the output stream and assert isolated_o. +Similarly, when isolate_i is deasserted, the isolator will wait until any in- +progress packet is complete before allowing data to flow through and deasserting +isolated_o. +Therefore isolated_o is generally equal to isolated_i, but may lag changes +by an arbitrary amount of time to allow packets to complete. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +module axis_pkt_isolate ( + input logic clk_i, + input logic rst_ni, + input ethernet_pkg::axis_t axis_i, + output ethernet_pkg::axis_t axis_o, + input logic isolate_i, + output logic isolated_o +); + // Track if we are mid-packet to avoid cutting off a packet or un-isolating mid-packet + logic packet_in_progress; + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) packet_in_progress <= 1'b0; + else begin + if (axis_i.valid) begin + packet_in_progress <= !axis_i.last; + end + end + end + // packet_in_progress being asserted means some data has been captured for this packet + // i.e. it will not be asserted DURING the first beat but will rise once the first beat is captured + // it will fall just after the last beat is captured as indicated by tlast + // note that it will fall even if valid is asserted on the following cycle, so for back-to-back packets we will still see packet_in_progress go low for one cycle + // this makes sense: the packet is not considered to be "in progress" until we have captured some data for it + // this one cycle of low is important because it allows us to switch isolation state between packets + + // Track the isolation state. We only change the state when we are not mid-packet. + logic isolated_q; + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) isolated_q <= 1'b1; // default to isolated on reset, just in case + else if (!packet_in_progress) isolated_q <= isolate_i; + end + assign isolated_o = packet_in_progress ? isolated_q : isolate_i; + // If no packet in progress, passthrough isolate_i to isolated_o. If a packet is in progress, we need to retain our previous isolation state until the packet is complete. + // Be careful if you are messing with this logic! + // There is some subtle timing here e.g. on the first beat of a new packet + + // Implement the isolation logic + assign axis_o = isolated_o ? '0 : axis_i; +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_mux.sv b/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_mux.sv new file mode 100644 index 000000000..78730388c --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/loopback/axis_pkt_mux.sv @@ -0,0 +1,103 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: axis_pkt_mux +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Multiplexor for two AXI Stream (AXIS) streams with packets delimeted by tlast. + +This is not entirely trivial because we have to prevent "packet splices" +i.e. if the select lines toggles in the middle of a packet on one stream, we +should never see: + - a partial packet on the output stream + - a packet on the output stream that mixes data from the both input streams + +To avoid this, we have an 'isolator' on each input stream. This has an +isolate_i input and an isolated_o output. When isolate_i is asserted, the +isolator will complete any existing packets before asserting isolated_o. +Similarly when it is deasserted, the isolator will wait until any already- +ongoing packets are complete before allowing data to flow through. + +To ensure splicing is impossible, the isolated_o signals from both isolators +are fed back into one another's isolate_i inputs. This is not unlike a NAND +latch but with delays on the feedback paths. + +Does not: + - Support backpressure TODO + - Do anything fancy with IDs + - Pay any special attention to any AXIS signals except tvalid and tlast + +Note: this module has potential to be refactored for reusability given AXIS is +a standard interface. For now it is only used in Ethernet so that's not a +priority. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +`include "prim_assert.sv" + +module axis_pkt_mux ( + input clk_i, + input rst_ni, + input logic sel_i, // Select signal for one of the two input streams + output logic passthrough_o, // when asserted, the output stream is passing through the selected input stream. Should be normally high but may be briefly deasserted when sel_i changes to allow the isolators to complete any in-progress packets + input ethernet_pkg::axis_t axis_i [2], // Input streams to be MUXed + output ethernet_pkg::axis_t axis_o // Multiplexed output stream +); + ethernet_pkg::axis_t axis_isolated [2]; + + // Flop the output stream + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) axis_o <= '0; + else axis_o <= axis_isolated[0] | axis_isolated[1]; // The isolation logic zeros any isolated stream, so we can just OR them together + end + + // This is really cool! + // It behaves similarly to an NAND latch with the symmetrical feedback paths + // When loopback_en_i switches state, it will FIRST isolate the currently selected path, + // then once the isolated_o signal for that path is asserted, that will allow the other path to be un-isolated + logic isolated [2]; + logic isolated_q [2]; + // registered version of isolated to avoid combinational loops. Shouldn't be necessary really but some EDA tools will probably be upset by it. + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + isolated_q[0] <= 1'b1; + isolated_q[1] <= 1'b1; + end else begin + isolated_q[0] <= isolated[0]; + isolated_q[1] <= isolated[1]; + end + end + + axis_pkt_isolate isolate0 ( + .clk_i, + .rst_ni, + .axis_i(axis_i[0]), + .axis_o(axis_isolated[0]), + .isolate_i(sel_i | !isolated_q[1]), + .isolated_o(isolated[0]) + ); + + axis_pkt_isolate isolate1 ( + .clk_i, + .rst_ni, + .axis_i(axis_i[1]), + .axis_o(axis_isolated[1]), + .isolate_i(!sel_i | !isolated_q[0]), + .isolated_o(isolated[1]) + ); + + assign passthrough_o = !isolated[0] || !isolated[1]; // when either path is un-isolated, we are passing through the selected input stream + + `ASSERT(NeverMixPaths_A, isolated[0] || isolated[1]) // we should NEVER have both paths un-isolated at the same time, otherwise we could see a packet splice +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/loopback/ethernet_loopback.sv b/hw/vendor/ethernet/hw/core_eth/loopback/ethernet_loopback.sv new file mode 100644 index 000000000..5298dae78 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/loopback/ethernet_loopback.sv @@ -0,0 +1,52 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_loopback +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Thin wrapper around axis_pkt_mux +Multiplexes the RX path between the actual RX (from the MAC) and the TX (for +loopback). + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +module ethernet_loopback ( + input clk_i, + input rst_ni, + input loopback_en, + input axis_tx_ready_i, + input ethernet_pkg::axis_t axis_rx_real_i, + input ethernet_pkg::axis_t axis_tx_i, + output ethernet_pkg::axis_t axis_o // Multiplexed output stream +); + ethernet_pkg::axis_t tx_axis_modified; + // The axis_pkt_mux does not support backpressure generally + // but in the Ethernet case we know the only backpressure is on the TX path, + // so we can just gate tvalid with tready on the TX path for this + // This is completely fine for Ethernet but is not generally true for AXIS, so we don't want to change the axis_pkt_mux module itself + assign tx_axis_modified.valid = axis_tx_i.valid & axis_tx_ready_i; + assign tx_axis_modified.data = axis_tx_i.data; + assign tx_axis_modified.last = axis_tx_i.last; + assign tx_axis_modified.user = axis_tx_i.user; + + axis_pkt_mux loopback_mux ( + .clk_i, + .rst_ni, + .sel_i (loopback_en), // select between TX and RX for loopback + .passthrough_o (), // deasserts briefly when mid-switch. Not used here + .axis_i ({axis_rx_real_i,tx_axis_modified}), // RX path is input 0, TX path is input 1 + .axis_o (axis_o) + ); + +endmodule diff --git a/hw/vendor/ethernet/LICENSE b/hw/vendor/ethernet/hw/core_eth/mac/LICENSE similarity index 100% rename from hw/vendor/ethernet/LICENSE rename to hw/vendor/ethernet/hw/core_eth/mac/LICENSE diff --git a/hw/vendor/ethernet/axis_gmii_rx.sv b/hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_rx.sv similarity index 99% rename from hw/vendor/ethernet/axis_gmii_rx.sv rename to hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_rx.sv index 247f6c9e3..98addfb3e 100644 --- a/hw/vendor/ethernet/axis_gmii_rx.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_rx.sv @@ -26,7 +26,7 @@ Author: Alex Forencich Date: Thu Nov 8 13:15:47 2018 -0800 Modified by Jonathan Kimmitt to extract CRC bytes - + lfsr submodule renamed rgmii_lfsr to avoid name clash with main project */ @@ -72,7 +72,6 @@ module axis_gmii_rx ); localparam [7:0] - ETH_PRE = 8'h55, ETH_SFD = 8'hD5; localparam [2:0] @@ -209,6 +208,7 @@ always @* begin error_bad_fcs_next = 1'b1; end crc_cnt_next = 2'b0; + m_axis_tlast_next = 1'b1; state_next = STATE_CRC; end else begin state_next = STATE_PAYLOAD; @@ -219,13 +219,12 @@ always @* begin update_crc = 1'b1; m_axis_tdata_next = gmii_rxd_d4; - m_axis_tvalid_next = 1'b1; + m_axis_tvalid_next = 1'b0; crc_cnt_next = crc_cnt + 1; if (&crc_cnt) begin // end of packet + CRC bytes fcs_next = crc_next; - m_axis_tlast_next = 1'b1; state_next = STATE_IDLE; end else begin fcs_next = 32'b0; diff --git a/hw/vendor/ethernet/axis_gmii_tx.sv b/hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_tx.sv similarity index 82% rename from hw/vendor/ethernet/axis_gmii_tx.sv rename to hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_tx.sv index b93b574bb..2427a3b90 100644 --- a/hw/vendor/ethernet/axis_gmii_tx.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/axis_gmii_tx.sv @@ -26,7 +26,7 @@ Author: Alex Forencich Date: Thu Nov 8 13:15:47 2018 -0800 Modified by Jonathan Kimmitt to extract CRC bytes - + lfsr submodule renamed rgmii_lfsr to avoid name clash with main project */ @@ -83,11 +83,12 @@ localparam [2:0] STATE_IDLE = 3'd0, STATE_PREAMBLE = 3'd1, STATE_PAYLOAD = 3'd2, - STATE_LAST = 3'd3, - STATE_PAD = 3'd4, - STATE_FCS = 3'd5, - STATE_WAIT_END = 3'd6, - STATE_IFG = 3'd7; + STATE_LAST = 3'd3, // last byte of payload + STATE_PAD = 3'd4, // padding bytes + STATE_FCS = 3'd5, // frame-check sequence + STATE_WAIT_END = 3'd6, // state for waiting for end of a frame that went bad during transmission + STATE_IFG = 3'd7; // inter-frame gap state +// This FSM is pretty messy and could do with refactoring but atm it works so I'm not going to touch it reg [2:0] state_reg, state_next; @@ -107,7 +108,7 @@ reg gmii_tx_en_reg, gmii_tx_en_next; reg gmii_tx_er_reg, gmii_tx_er_next; reg s_axis_tready_reg, s_axis_tready_next; -reg [31:0] crc_state, fcs_next; +reg [31:0] crc_state, fcs_next; wire [31:0] crc_next; @@ -230,7 +231,8 @@ always @* begin s_axis_tready_next = !s_axis_tready_reg; if (s_axis_tuser) begin gmii_tx_er_next = 1'b1; - frame_ptr_next = 1'b0; + frame_ptr_next = 16'b0; + ifg_next = 8'b0; state_next = STATE_IFG; end else begin state_next = STATE_LAST; @@ -290,10 +292,10 @@ always @* begin frame_ptr_next = frame_ptr_reg + 16'd1; case (frame_ptr_reg) - 2'd0: gmii_txd_next = ~crc_state[7:0]; - 2'd1: gmii_txd_next = ~crc_state[15:8]; - 2'd2: gmii_txd_next = ~crc_state[23:16]; - 2'd3: gmii_txd_next = ~crc_state[31:24]; + 16'd0: gmii_txd_next = ~crc_state[7:0]; + 16'd1: gmii_txd_next = ~crc_state[15:8]; + 16'd2: gmii_txd_next = ~crc_state[23:16]; + 16'd3: gmii_txd_next = ~crc_state[31:24]; default:; endcase gmii_tx_en_next = 1'b1; @@ -303,6 +305,7 @@ always @* begin end else begin frame_ptr_next = 16'd0; fcs_next = crc_state; + ifg_next = 8'b0; state_next = STATE_IFG; end end @@ -319,7 +322,7 @@ always @* begin if (s_axis_tlast) begin s_axis_tready_next = 1'b0; ifg_next = 8'b0; - state_next = STATE_IFG; + state_next = STATE_IFG; end else begin state_next = STATE_WAIT_END; end @@ -330,6 +333,20 @@ always @* begin STATE_IFG: begin // send IFG + // Somewhere around reading this piece of code, I found myself + // myself wondering "should the tx_en be asserted during the IPG?" + // It's referred to as IFG here but IPG is the proper term (inter-packet gap, not inter-frame gap) + // The language in most sources is unclear on this point, for example: + // "After a packet has been sent, transmitters are required to transmit a minimum of 96 bits (12 octets) of idle line state before transmitting the next packet" + // To me, 'required to transmit' would suggest that we SHOULD be asserting tx_en + // the RMGII standard doesn't say anything about it since it's only concerned with the data + // that is transferred and not the nature of the data. + // I had to do quite a lot of digging before getting to page 6402 of IEEE 802.3-2022 (that's Annex 36A.4) to find the + // answer attached to an example. The answer is no. TX_EN should NOT be asserted during the IPG.axis_gmii_tx + // the original creator of this MAC had it right, fair play Alex + // Let this comment spare anyone else the nightmare of reading IEEE standards + // - @thomas6785 + reset_crc = 1'b1; mii_odd_next = 1'b1; @@ -364,6 +381,7 @@ always @(posedge clk) begin crc_state <= 32'hFFFFFFFF; fcs_reg <= 32'hFFFFFFFF; + ifg_reg <= 8'b0; // possible bug: if you send a packet and immediately reset then send another, the IFG can be skipped end else begin state_reg <= state_next; diff --git a/hw/vendor/ethernet/eth_mac_1g.sv b/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g.sv similarity index 100% rename from hw/vendor/ethernet/eth_mac_1g.sv rename to hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g.sv diff --git a/hw/vendor/ethernet/eth_mac_1g_rgmii.sv b/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii.sv similarity index 99% rename from hw/vendor/ethernet/eth_mac_1g_rgmii.sv rename to hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii.sv index 096e9cede..16d804308 100644 --- a/hw/vendor/ethernet/eth_mac_1g_rgmii.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii.sv @@ -170,7 +170,7 @@ always @(posedge gtx_clk) begin speed_reg <= 2'b10; end else begin rx_speed_count_1 <= rx_speed_count_1 + 1; - + if (rx_prescale_sync_2 ^ rx_prescale_sync_3) begin rx_speed_count_2 <= rx_speed_count_2 + 1; end @@ -186,7 +186,7 @@ always @(posedge gtx_clk) begin // prescaled count overflow - 100M or 1000M rx_speed_count_1 <= 0; rx_speed_count_2 <= 0; - if (rx_speed_count_1[6:5]) begin + if (|rx_speed_count_1[6:5]) begin // large reference count - 100M speed_reg <= 2'b01; end else begin diff --git a/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii_wrapper.sv b/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii_wrapper.sv new file mode 100644 index 000000000..316bbd094 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/mac/eth_mac_1g_rgmii_wrapper.sv @@ -0,0 +1,239 @@ +/* + +Copyright (c) 2015-2018 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 + +/* + * 1G Ethernet MAC with RGMII interface and RX FIFO + */ +module eth_mac_1g_rgmii_wrapper # +( + // target (e.g. Xilinx FPGA, generic model, etc.) + parameter TARGET = "SIM", // "SIM", "GENERIC", "XILINX", or "ALTERA" + // IODDR style ("IODDR", "IODDR2") + // Use IODDR for Virtex-4, Virtex-5, Virtex-6, 7 Series, Ultrascale + // Use IODDR2 for Spartan-6 + parameter IODDR_STYLE = "IODDR2", + // 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", + // Use 90 degree clock for RGMII transmit ("TRUE", "FALSE") + parameter USE_CLK90 = "TRUE", + parameter ENABLE_PADDING = 1, + parameter MIN_FRAME_LENGTH = 64 +) +( + input wire gtx_clk, + input wire gtx_clk90, + input wire gtx_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, + output wire rx_axis_tlast, + output wire rx_axis_tuser, + + /* + * RGMII interface + */ + input wire rgmii_rx_clk, + input wire [3:0] rgmii_rxd, + input wire rgmii_rx_ctl, + output wire rgmii_tx_clk, + output wire [3:0] rgmii_txd, + output wire rgmii_tx_ctl, + output wire mac_gmii_tx_en, + + /* + * Status + */ + output wire rx_error_bad_frame, + output wire rx_error_bad_fcs, + output wire [1:0] speed, + output wire [31:0] rx_fcs_reg, + output wire [31:0] tx_fcs_reg, + + /* + * Configuration + */ + input wire [7:0] ifg_delay +); + +wire rx_clk; +wire rx_rst; + +// RX AXI Stream signals between FIFO and MAC +wire rx_fifo_axis_tvalid; +wire [7:0] rx_fifo_axis_tdata; +wire rx_fifo_axis_tlast; +wire rx_fifo_axis_tuser; + +// synchronize MAC status signals into logic clock domain +wire rx_error_bad_frame_int; +wire rx_error_bad_fcs_int; + +reg [1:0] rx_sync_reg_1; +reg [1:0] rx_sync_reg_2; +reg [1:0] rx_sync_reg_3; +reg [1:0] rx_sync_reg_4; + +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_fcs_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 + + +wire [1:0] speed_int; + +reg [1:0] speed_sync_reg_1; +reg [1:0] speed_sync_reg_2; + +assign speed = speed_sync_reg_2; + +always @(posedge logic_clk) begin + speed_sync_reg_1 <= speed_int; + speed_sync_reg_2 <= speed_sync_reg_1; +end + +eth_mac_1g_rgmii #( + .TARGET (TARGET), + .IODDR_STYLE (IODDR_STYLE), + .CLOCK_INPUT_STYLE (CLOCK_INPUT_STYLE), + .USE_CLK90 (USE_CLK90), + .ENABLE_PADDING (ENABLE_PADDING), + .MIN_FRAME_LENGTH (MIN_FRAME_LENGTH) +) +eth_mac_1g_rgmii_inst ( + .gtx_clk (gtx_clk ), + .gtx_clk90 (gtx_clk90 ), + .gtx_rst (gtx_rst ), + + // TX AXI Stream + .tx_clk ( ), // TX clk is identical to logic rst for now, so we don't need CDC + // TODO this is true in our current setup but not true in general as this module has separate inputs for logic_clk and gtx_clk + .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 AXI Stream + .rx_clk (rx_clk ), + .rx_rst (rx_rst ), + .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 ), + + // RGMII RX + .rgmii_rx_clk (rgmii_rx_clk ), + .rgmii_rxd (rgmii_rxd ), + .rgmii_rx_ctl (rgmii_rx_ctl ), + + // RGMII TX + .rgmii_tx_clk (rgmii_tx_clk ), + .rgmii_txd (rgmii_txd ), + .rgmii_tx_ctl (rgmii_tx_ctl ), + + // Status + .mac_gmii_tx_en(mac_gmii_tx_en), + .rx_error_bad_frame(rx_error_bad_frame_int), + .rx_error_bad_fcs(rx_error_bad_fcs_int), + .rx_fcs_reg(rx_fcs_reg), + .tx_fcs_reg(tx_fcs_reg), + .speed(speed_int), + + // Config + .ifg_delay(ifg_delay) +); + +// I have chosen a depth of 8 for RX FIFO +// The RGMII clocks are strictly less than or equal to the AXI clock (125 MHz) +// and backpressure on the AXI side is completely fine, so buffering isn't needed +// we only really need the FIFOs to provide CDC +// assuming the CDC propagates in 2-3 cycles of the destination clock, a depth of 4 would be enough +// but if the reset synchronisers happens to reach the source domain one cycle sooner we could have a little bit of overflow +// so a depth of 8 is a safe choice +// FIFO width is the width of the AXI Stream (8 data bits + 1 user bit + 1 valid bit + 1 last bit = 11 bits) so this is pretty cheap +localparam RX_FIFO_DEPTH = 8; + +// FIFO for the RX path to cross from MAC clock domain to logic clock domain +prim_fifo_async #( + .Width(8+1+1), // data + last + user + .Depth(RX_FIFO_DEPTH), + .OutputZeroIfEmpty(1'b1) // drive zeros when the FIFO is empty to avoid any metastable data being sent to the logic domain +) rx_fifo ( + .clk_wr_i (rx_clk ), + .rst_wr_ni (!rx_rst ), + .clk_rd_i (logic_clk ), + .rst_rd_ni (!logic_rst ), + .wvalid_i (rx_fifo_axis_tvalid ), + .wready_o ( ), // no backpressure should ever be put on the RX stream + .wdata_i ({rx_fifo_axis_tdata, + rx_fifo_axis_tlast, + rx_fifo_axis_tuser}), + .rvalid_o (rx_axis_tvalid ), + .rready_i (1'b1 ), // no backpressure should ever be put on the RX stream + .rdata_o ({rx_axis_tdata, + rx_axis_tlast, + rx_axis_tuser }), + .wdepth_o ( ), // not used + .rdepth_o ( ) // not used +); + +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/mac/ethernet_mac.core b/hw/vendor/ethernet/hw/core_eth/mac/ethernet_mac.core new file mode 100644 index 000000000..e335e2f0c --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/mac/ethernet_mac.core @@ -0,0 +1,36 @@ +CAPI=2: +# Copyright lowRISC contributors (COSMIC project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# This is a FuseSoC .core file +# https://fusesoc.readthedocs.io/_/downloads/en/stable/pdf/ + +name: "lowrisc:ethernet:mac" +description: "1GbE ethernet MAC" + +filesets: + files_rtl: + depend: + - lowrisc:prim_generic:flop_2sync + - lowrisc:prim:fifo + files: + - axis_gmii_rx.sv + - axis_gmii_tx.sv + - eth_mac_1g.sv + - eth_mac_1g_rgmii.sv + - eth_mac_1g_rgmii_wrapper.sv + - iddr.sv + - oddr.sv + - rgmii_core.sv + - rgmii_lfsr.sv + - rgmii_phy_if.sv + - rgmii_soc.sv + - ssio_ddr_in.sv + - ethernet_rgmii_tx_assertions.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl diff --git a/hw/vendor/ethernet/hw/core_eth/mac/ethernet_rgmii_tx_assertions.sv b/hw/vendor/ethernet/hw/core_eth/mac/ethernet_rgmii_tx_assertions.sv new file mode 100644 index 000000000..c063d8499 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/mac/ethernet_rgmii_tx_assertions.sv @@ -0,0 +1,82 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_rgmii_tx_assertions +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Non-comprehensive assertions for an RGMII TX interface. +Enforces the inter-packet gap of 12 or more cycles and packet length of 64-1522 +bytes as required by the RGMII spec. + +The packet length requirement could be accomplished with a simple +$rose(phy_tx_ctl) |-> ##[64:1522] $fell(phy_tx_ctl) assertion, +but Verilator doesn't support those so I've just manually put in a counter +This counter should be stripped away during synthesis since it drives nothing +except the assertions. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +// Assertions only - no logic +ethernet_rgmii_tx_assertions u_ethernet_rgmii_tx_assertions ( + .phy_tx_clk (), + .phy_tx_ctl (), + .phy_reset_n () +); + +*/ + +module ethernet_rgmii_tx_assertions ( + input logic phy_tx_clk, + input logic phy_tx_ctl, + input logic phy_reset_n +); + logic ctl_last; + logic [11:0] cycle_counter; + + always_ff @ (posedge phy_tx_clk) begin + if (!phy_reset_n) ctl_last <= 0; + else ctl_last <= phy_tx_ctl; + end + + always_ff @ (posedge phy_tx_clk) begin + if (!phy_reset_n) begin + cycle_counter <= 0; + end else begin + if (phy_tx_ctl == ctl_last) begin // no change this cycle + cycle_counter <= (cycle_counter + (&cycle_counter ? 0 : 1)); // increment cycle counter (saturating at 4095) + end else begin + cycle_counter <= 1; // reset cycle counter on change, counting the first beat which was this rising edge + end + end + end + + always @ (posedge phy_tx_clk) begin + if (phy_tx_ctl != ctl_last) begin + if (ctl_last) begin // we were transmitting, so cycle_counter reflects the packet length in bytes + assert (cycle_counter >= 71 && cycle_counter <= 1530) else $error("RGMII TX packet length out of bounds: %0d bytes", cycle_counter); + // minimum packet is 60 payload + 4 CRC + 7 byte preamble (sometimes the SFD is counted as part of the preamble, sometimes not, so we will just say 7 bytes of preamble) + // maximum is 1518 payload + 4 CRC + 8 byte preamble (including the SFD) + // in theory the preamble can be as long as you want want I've arbitrarily capped it at 1530 here + end else begin // we were not transmitting, so cycle_counter reflects the inter-packet gap in bytes + assert (cycle_counter >= 12) else $error("RGMII TX inter-packet gap too short: %0d bytes", cycle_counter); + // Ethernet specification states that 12 bytes is the minimum inter-frame gap + end + end + end + // TODO add cover points for: + // - cycle_counter terminating at 64 + // - cycle counter terminating at 1522 + // - inter-packet gap terminating at 12 + // - IPG growing counter saturating +endmodule diff --git a/hw/vendor/ethernet/iddr.sv b/hw/vendor/ethernet/hw/core_eth/mac/iddr.sv similarity index 89% rename from hw/vendor/ethernet/iddr.sv rename to hw/vendor/ethernet/hw/core_eth/mac/iddr.sv index 6e77d95f6..c268f7d79 100644 --- a/hw/vendor/ethernet/iddr.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/iddr.sv @@ -65,9 +65,9 @@ genvar n; generate -if (TARGET == "XILINX") begin - for (n = 0; n < WIDTH; n = n + 1) begin : iddr - if (IODDR_STYLE == "IODDR") begin +if (TARGET == "XILINX") begin : gen_xilinx_iddr + for (n = 0; n < WIDTH; n = n + 1) begin : gen_xilinx_iddr_width + if (IODDR_STYLE == "IODDR") begin : gen_ioddr IDDR #( .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), .SRTYPE("ASYNC") @@ -81,7 +81,7 @@ if (TARGET == "XILINX") begin .R(1'b0), .S(1'b0) ); - end else if (IODDR_STYLE == "IODDR2") begin + end else if (IODDR_STYLE == "IODDR2") begin : gen_ioddr2 IDDR2 #( .DDR_ALIGNMENT("C0") ) @@ -97,7 +97,7 @@ if (TARGET == "XILINX") begin ); end end -end else if (TARGET == "ALTERA") begin +end else if (TARGET == "ALTERA") begin : gen_altera_iddr wire [WIDTH-1:0] q1_int; reg [WIDTH-1:0] q1_delay; @@ -120,12 +120,12 @@ end else if (TARGET == "ALTERA") begin end assign q1 = q1_delay; -end else begin - reg [WIDTH-1:0] d_reg_1 = {WIDTH{1'b0}}; - reg [WIDTH-1:0] d_reg_2 = {WIDTH{1'b0}}; +end else begin : gen_generic_iddr + reg [WIDTH-1:0] d_reg_1; + reg [WIDTH-1:0] d_reg_2; - reg [WIDTH-1:0] q_reg_1 = {WIDTH{1'b0}}; - reg [WIDTH-1:0] q_reg_2 = {WIDTH{1'b0}}; + reg [WIDTH-1:0] q_reg_1; + reg [WIDTH-1:0] q_reg_2; always @(posedge clk) begin d_reg_1 <= d; diff --git a/hw/vendor/ethernet/oddr.sv b/hw/vendor/ethernet/hw/core_eth/mac/oddr.sv similarity index 86% rename from hw/vendor/ethernet/oddr.sv rename to hw/vendor/ethernet/hw/core_eth/mac/oddr.sv index 31450aea3..2159a102f 100644 --- a/hw/vendor/ethernet/oddr.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/oddr.sv @@ -65,9 +65,9 @@ genvar n; generate -if (TARGET == "XILINX") begin - for (n = 0; n < WIDTH; n = n + 1) begin : oddr - if (IODDR_STYLE == "IODDR") begin +if (TARGET == "XILINX") begin : gen_xilinx_target + for (n = 0; n < WIDTH; n = n + 1) begin : gen_oddr + if (IODDR_STYLE == "IODDR") begin : gen_ioddr ODDR #( .DDR_CLK_EDGE("SAME_EDGE"), .SRTYPE("ASYNC") @@ -81,7 +81,7 @@ if (TARGET == "XILINX") begin .R(1'b0), .S(1'b0) ); - end else if (IODDR_STYLE == "IODDR2") begin + end else if (IODDR_STYLE == "IODDR2") begin : gen_ioddr2 ODDR2 #( .DDR_ALIGNMENT("C0"), .SRTYPE("ASYNC") @@ -98,7 +98,7 @@ if (TARGET == "XILINX") begin ); end end -end else if (TARGET == "ALTERA") begin +end else if (TARGET == "ALTERA") begin : gen_altera_target altddio_out #( .WIDTH(WIDTH), .POWER_UP_HIGH("OFF"), @@ -113,23 +113,17 @@ end else if (TARGET == "ALTERA") begin .aclr(1'b0), .dataout(q) ); -end else begin - reg [WIDTH-1:0] d_reg_1 = {WIDTH{1'b0}}; - reg [WIDTH-1:0] d_reg_2 = {WIDTH{1'b0}}; - - reg [WIDTH-1:0] q_reg = {WIDTH{1'b0}}; - - always @(posedge clk) begin - d_reg_1 <= d1; - d_reg_2 <= d2; - end +end else begin : gen_generic_target + reg [WIDTH-1:0] d2_q; + reg [WIDTH-1:0] q_reg; always @(posedge clk) begin - q_reg <= d1; + d2_q <= d2; end - always @(negedge clk) begin - q_reg <= d_reg_2; + always @(clk) begin + if (clk) q_reg <= d1; + else if (!clk) q_reg <= d2_q; end assign q = q_reg; diff --git a/hw/vendor/ethernet/rgmii_core.sv b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_core.sv similarity index 62% rename from hw/vendor/ethernet/rgmii_core.sv rename to hw/vendor/ethernet/hw/core_eth/mac/rgmii_core.sv index ec4acd5b5..ae9ebcdc4 100644 --- a/hw/vendor/ethernet/rgmii_core.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_core.sv @@ -29,7 +29,8 @@ THE SOFTWARE. */ module rgmii_core # ( - parameter TARGET = "XILINX" + // target (e.g. Xilinx FPGA, generic model, etc.) + parameter TARGET = "SIM" // "SIM", "GENERIC", "XILINX", or "ALTERA" ) ( /* @@ -50,24 +51,22 @@ module rgmii_core # output wire [3:0] phy_txd, output wire phy_tx_ctl, output wire phy_reset_n, - input wire phy_int_n, - input wire phy_pme_n, output wire mac_gmii_tx_en, /* * AXI input */ - + input wire tx_axis_tvalid, input wire tx_axis_tlast, input wire [7:0] tx_axis_tdata, output wire tx_axis_tready, input wire tx_axis_tuser, - + /* * AXI output */ - + output wire [7:0] rx_axis_tdata, output wire rx_axis_tvalid, output wire rx_axis_tlast, @@ -79,63 +78,49 @@ module rgmii_core # output wire [31:0] rx_fcs_reg, output wire [31:0] tx_fcs_reg - ); assign phy_reset_n = !rst; -eth_mac_1g_rgmii_fifo #( +eth_mac_1g_rgmii_wrapper #( .TARGET(TARGET), .IODDR_STYLE("IODDR"), .CLOCK_INPUT_STYLE("BUFR"), - .USE_CLK90("TRUE"), - .ENABLE_PADDING(1), - .MIN_FRAME_LENGTH(64), - .TX_FIFO_ADDR_WIDTH(12), - .TX_FRAME_FIFO(1), - .RX_FIFO_ADDR_WIDTH(12), - .RX_FRAME_FIFO(1) + .USE_CLK90("TRUE") ) eth_mac_inst ( - .gtx_clk(clk), - .gtx_clk90(clk90), - .gtx_rst(rst), - .logic_clk(clk), - .logic_rst(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(1'b1), - .rx_axis_tlast(rx_axis_tlast), - .rx_axis_tuser(rx_axis_tuser), - - .rgmii_rx_clk(phy_rx_clk), - .rgmii_rxd(phy_rxd), - .rgmii_rx_ctl(phy_rx_ctl), - .rgmii_tx_clk(phy_tx_clk), - .rgmii_txd(phy_txd), - .rgmii_tx_ctl(phy_tx_ctl), - .mac_gmii_tx_en(mac_gmii_tx_en), - - .tx_fifo_overflow(), - .tx_fifo_bad_frame(), - .tx_fifo_good_frame(), - .rx_error_bad_frame(), - .rx_error_bad_fcs(), - .rx_fcs_reg(rx_fcs_reg), - .tx_fcs_reg(tx_fcs_reg), - .rx_fifo_overflow(), - .rx_fifo_bad_frame(), - .rx_fifo_good_frame(), - .speed(), - - .ifg_delay(12) + .gtx_clk (clk), + .gtx_clk90 (clk90), + .gtx_rst (rst), + .logic_clk (clk), + .logic_rst (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), + + .rgmii_rx_clk (phy_rx_clk), + .rgmii_rxd (phy_rxd), + .rgmii_rx_ctl (phy_rx_ctl), + .rgmii_tx_clk (phy_tx_clk), + .rgmii_txd (phy_txd), + .rgmii_tx_ctl (phy_tx_ctl), + .mac_gmii_tx_en (mac_gmii_tx_en), + + .rx_error_bad_frame (), + .rx_error_bad_fcs (), + .rx_fcs_reg (rx_fcs_reg), // frame check sequence value + .tx_fcs_reg (tx_fcs_reg), // frame check sequence value + .speed (), + + .ifg_delay (12) // inter-frame gap is required to be 12 bytes or more per Ethernet standards ); endmodule diff --git a/hw/vendor/ethernet/rgmii_lfsr.sv b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_lfsr.sv similarity index 96% rename from hw/vendor/ethernet/rgmii_lfsr.sv rename to hw/vendor/ethernet/hw/core_eth/mac/rgmii_lfsr.sv index 5782a973e..7a6c79fc7 100644 --- a/hw/vendor/ethernet/rgmii_lfsr.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_lfsr.sv @@ -58,7 +58,7 @@ next state computation, shifting DATA_WIDTH bits per pass through the module. I is XORed with LFSR feedback path, tie data_in to zero if this is not required. Works in two parts: statically computes a set of bit masks, then uses these bit masks to -select bits for XORing to compute the next state. +select bits for XORing to compute the next state. Ports: @@ -159,7 +159,7 @@ DATA_WIDTH Specify width of input and output data bus. The module will perform one shift per input data bit, so if the input data bus is not required tie data_in to zero and set DATA_WIDTH -to the required number of shifts per clock cycle. +to the required number of shifts per clock cycle. STYLE @@ -204,7 +204,7 @@ reg [DATA_WIDTH-1:0] output_mask_data[DATA_WIDTH-1:0]; reg [LFSR_WIDTH-1:0] state_val; reg [DATA_WIDTH-1:0] data_val; -integer i, j, k; +integer i, j; initial begin // init bit masks @@ -233,7 +233,7 @@ initial begin // add XOR inputs from correct indicies for (j = 1; j < LFSR_WIDTH; j = j + 1) begin - if (LFSR_POLY & (1 << j)) begin + if (|(LFSR_POLY & (1 << j))) begin state_val = lfsr_mask_state[j-1] ^ state_val; data_val = lfsr_mask_data[j-1] ^ data_val; end @@ -288,7 +288,7 @@ initial begin // add XOR inputs at correct indicies for (j = 1; j < LFSR_WIDTH; j = j + 1) begin - if (LFSR_POLY & (1 << j)) begin + if (|(LFSR_POLY & (1 << j))) begin lfsr_mask_state[j] = lfsr_mask_state[j] ^ state_val; lfsr_mask_data[j] = lfsr_mask_data[j] ^ data_val; end @@ -352,23 +352,19 @@ initial begin // end end -// synthesis translate_off -`define SIMULATION -// synthesis translate_on - -`ifdef SIMULATION +`ifndef SYNTHESIS // "AUTO" style is "REDUCTION" for faster simulation -parameter STYLE_INT = (STYLE == "AUTO") ? "REDUCTION" : STYLE; +localparam STYLE_INT = (STYLE == "AUTO") ? "REDUCTION" : STYLE; `else // "AUTO" style is "LOOP" for better synthesis result -parameter STYLE_INT = (STYLE == "AUTO") ? "LOOP" : STYLE; +localparam STYLE_INT = (STYLE == "AUTO") ? "LOOP" : STYLE; `endif genvar n; generate -if (STYLE_INT == "REDUCTION") begin +if (STYLE_INT == "REDUCTION") begin : gen_reduction_style // use Verilog reduction operator // fast in iverilog @@ -376,14 +372,14 @@ if (STYLE_INT == "REDUCTION") begin // slightly smaller than generated code with Quartus // --> better for simulation - for (n = 0; n < LFSR_WIDTH; n = n + 1) begin : loop1 + for (n = 0; n < LFSR_WIDTH; n = n + 1) begin : gen_loop1 assign state_out[n] = ^{(state_in & lfsr_mask_state[n]), (data_in & lfsr_mask_data[n])}; end - for (n = 0; n < DATA_WIDTH; n = n + 1) begin : loop2 + for (n = 0; n < DATA_WIDTH; n = n + 1) begin : gen_loop2 assign data_out[n] = ^{(state_in & output_mask_state[n]), (data_in & output_mask_data[n])}; end -end else if (STYLE_INT == "LOOP") begin +end else if (STYLE_INT == "LOOP") begin : gen_loop_style // use nested loops // very slow in iverilog @@ -426,7 +422,7 @@ end else if (STYLE_INT == "LOOP") begin end end -end else begin +end else begin : gen_unknown_style initial begin $error("Error: unknown style setting!"); diff --git a/hw/vendor/ethernet/rgmii_phy_if.sv b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_phy_if.sv similarity index 93% rename from hw/vendor/ethernet/rgmii_phy_if.sv rename to hw/vendor/ethernet/hw/core_eth/mac/rgmii_phy_if.sv index 0b73f510b..201ec20e4 100644 --- a/hw/vendor/ethernet/rgmii_phy_if.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_phy_if.sv @@ -29,8 +29,8 @@ THE SOFTWARE. */ module rgmii_phy_if # ( - // target ("SIM", "GENERIC", "XILINX", "ALTERA") - parameter TARGET = "GENERIC", + // target (e.g. Xilinx FPGA, generic model, etc.) + parameter TARGET = "SIM", // "SIM", "GENERIC", "XILINX", or "ALTERA" // IODDR style ("IODDR", "IODDR2") // Use IODDR for Virtex-4, Virtex-5, Virtex-6, 7 Series, Ultrascale // Use IODDR2 for Spartan-6 @@ -106,16 +106,14 @@ assign mac_gmii_rx_er = rgmii_rx_ctl_1 ^ rgmii_rx_ctl_2; reg rgmii_tx_clk_1; reg rgmii_tx_clk_2; -reg rgmii_tx_clk_rise; reg rgmii_tx_clk_fall; -reg [5:0] count_reg, count_next; +reg [5:0] count_reg; always @(posedge clk) begin if (rst) begin rgmii_tx_clk_1 <= 1'b1; rgmii_tx_clk_2 <= 1'b0; - rgmii_tx_clk_rise <= 1'b1; rgmii_tx_clk_fall <= 1'b1; count_reg <= 0; end else begin @@ -124,12 +122,10 @@ always @(posedge clk) begin if (speed == 2'b00) begin // 10M count_reg <= count_reg + 1; - rgmii_tx_clk_rise <= 1'b0; rgmii_tx_clk_fall <= 1'b0; if (count_reg == 24) begin rgmii_tx_clk_1 <= 1'b1; rgmii_tx_clk_2 <= 1'b1; - rgmii_tx_clk_rise <= 1'b1; end else if (count_reg >= 49) begin rgmii_tx_clk_1 <= 1'b0; rgmii_tx_clk_2 <= 1'b0; @@ -139,12 +135,10 @@ always @(posedge clk) begin end else if (speed == 2'b01) begin // 100M count_reg <= count_reg + 1; - rgmii_tx_clk_rise <= 1'b0; rgmii_tx_clk_fall <= 1'b0; if (count_reg == 2) begin rgmii_tx_clk_1 <= 1'b1; rgmii_tx_clk_2 <= 1'b1; - rgmii_tx_clk_rise <= 1'b1; end else if (count_reg >= 4) begin rgmii_tx_clk_2 <= 1'b0; rgmii_tx_clk_fall <= 1'b1; @@ -154,7 +148,6 @@ always @(posedge clk) begin // 1000M rgmii_tx_clk_1 <= 1'b1; rgmii_tx_clk_2 <= 1'b0; - rgmii_tx_clk_rise <= 1'b1; rgmii_tx_clk_fall <= 1'b1; end end @@ -202,10 +195,6 @@ always @* begin end end -wire phy_rgmii_tx_clk_new; -wire [3:0] phy_rgmii_txd_new; -wire phy_rgmii_tx_ctl_new; - oddr #( .TARGET(TARGET), .IODDR_STYLE(IODDR_STYLE), diff --git a/hw/vendor/ethernet/hw/core_eth/mac/rgmii_soc.sv b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_soc.sv new file mode 100644 index 000000000..e6a81ef10 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/mac/rgmii_soc.sv @@ -0,0 +1,279 @@ +/* + +Copyright (c) 2014-2018 Alex Forencich + 2026 Modified by Thomas O'Dea for lowRISC C.I.C. + +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. + +Wrapper for rgmii_core + +This: + - Adds IO delay to physical i/o for timing closure + + |--------------------------------------This module-------------------------------------------| + | | + | | + |---------------| | |--------------| |-------------------| | |-------------------| + | TX Buffer(s) | ---AXI Stream---> | TX MAC | ---RGMII (8-bits wide)-------------> | TX PHY IF (ODDR) | ---RGMII (4-bits DDR)--> | TX PHY (off-chip) | =========| + |---------------| | |--------------| |-------------------| | |-------------------| | + | | | + | | Ethernet + | | Cable + | | | + |---------------| | |--------------| |-------------------| | |-------------------| | + | RX Buffer(s) | <--AXI Stream---- | RX MAC | <--RGMII (8-bits wide)-------------- | RX PHY IF (IDDR) | <--RGMII (4-bits DDR)--0 | RX PHY (off-chip) | =========| + |---------------| | |--------------| |-------------------| | |-------------------| + | | + |--------------------------------------------------------------------------------------------| +*/ + +`include "prim_assert.sv" + +module rgmii_soc # +( + // target (e.g. Xilinx FPGA, generic model, etc.) + parameter TARGET = "SIM" // "SIM", "GENERIC", "XILINX", or "ALTERA" +) ( + // Internal 125 MHz clock + input clk_int, + input rst_int, + input clk90_int, + input clk_200_int, + + /* + * Ethernet: 1000BASE-T RGMII + */ + input wire phy_rx_clk, + input wire [3:0] phy_rxd, + input wire phy_rx_ctl, + output wire phy_tx_clk, + output wire [3:0] phy_txd, + output wire phy_tx_ctl, + output wire phy_reset_n, + output wire mac_gmii_tx_en, + + /* + * AXI input + */ + input wire tx_axis_tvalid, + input wire tx_axis_tlast, + input wire [7:0] tx_axis_tdata, + output wire tx_axis_tready, + 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 rx_axis_tuser +); + +// IODELAY elements for RGMII interface to PHY +wire [3:0] phy_rxd_delay; +wire phy_rx_ctl_delay; + +generate if (TARGET == "XILINX") begin : gen_xilinx_target + IDELAYCTRL + idelayctrl_inst + ( + .REFCLK(clk_200_int), + .RST(rst_int), + .RDY() + ); + + IDELAYE2 #( + .IDELAY_TYPE("FIXED") + ) + phy_rxd_idelay_0 + ( + .IDATAIN(phy_rxd[0]), + .DATAOUT(phy_rxd_delay[0]), + .DATAIN(1'b0), + .C(1'b0), + .CE(1'b0), + .INC(1'b0), + .CINVCTRL(1'b0), + .CNTVALUEIN(5'd0), + .CNTVALUEOUT(), + .LD(1'b0), + .LDPIPEEN(1'b0), + .REGRST(1'b0) + ); + + IDELAYE2 #( + .IDELAY_TYPE("FIXED") + ) + phy_rxd_idelay_1 + ( + .IDATAIN(phy_rxd[1]), + .DATAOUT(phy_rxd_delay[1]), + .DATAIN(1'b0), + .C(1'b0), + .CE(1'b0), + .INC(1'b0), + .CINVCTRL(1'b0), + .CNTVALUEIN(5'd0), + .CNTVALUEOUT(), + .LD(1'b0), + .LDPIPEEN(1'b0), + .REGRST(1'b0) + ); + + IDELAYE2 #( + .IDELAY_TYPE("FIXED") + ) + phy_rxd_idelay_2 + ( + .IDATAIN(phy_rxd[2]), + .DATAOUT(phy_rxd_delay[2]), + .DATAIN(1'b0), + .C(1'b0), + .CE(1'b0), + .INC(1'b0), + .CINVCTRL(1'b0), + .CNTVALUEIN(5'd0), + .CNTVALUEOUT(), + .LD(1'b0), + .LDPIPEEN(1'b0), + .REGRST(1'b0) + ); + + IDELAYE2 #( + .IDELAY_TYPE("FIXED") + ) + phy_rxd_idelay_3 + ( + .IDATAIN(phy_rxd[3]), + .DATAOUT(phy_rxd_delay[3]), + .DATAIN(1'b0), + .C(1'b0), + .CE(1'b0), + .INC(1'b0), + .CINVCTRL(1'b0), + .CNTVALUEIN(5'd0), + .CNTVALUEOUT(), + .LD(1'b0), + .LDPIPEEN(1'b0), + .REGRST(1'b0) + ); + + IDELAYE2 #( + .IDELAY_VALUE(0), + .IDELAY_TYPE("FIXED") + ) + phy_rx_ctl_idelay + ( + .IDATAIN(phy_rx_ctl), + .DATAOUT(phy_rx_ctl_delay), + .DATAIN(1'b0), + .C(1'b0), + .CE(1'b0), + .INC(1'b0), + .CINVCTRL(1'b0), + .CNTVALUEIN(5'd0), + .CNTVALUEOUT(), + .LD(1'b0), + .LDPIPEEN(1'b0), + .REGRST(1'b0) + ); +end else begin : gen_generic_target + logic unused; + assign unused = clk_200_int; + + // For simulation we can ignore the input delay and just pass signals through + assign phy_rx_ctl_delay = phy_rx_ctl; + assign phy_rxd_delay = phy_rxd; +end +endgenerate + +// Instantiate core +rgmii_core #( + .TARGET(TARGET) +) core_inst ( + /* + * Clock: 125MHz + * Synchronous reset + */ + .clk(clk_int), + .clk90(clk90_int), + .rst(rst_int), + /* + * Ethernet: 1000BASE-T RGMII + */ + .phy_rx_clk (phy_rx_clk), + .phy_rxd (phy_rxd_delay), + .phy_rx_ctl (phy_rx_ctl_delay), + .phy_tx_clk (phy_tx_clk), + .phy_txd (phy_txd), + .phy_tx_ctl (phy_tx_ctl), + .phy_reset_n (phy_reset_n), + .mac_gmii_tx_en (mac_gmii_tx_en), + + .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), + .rx_fcs_reg (), + .tx_fcs_reg () +); + + //////////////// + // Assertions // + //////////////// + + // Assert RGMII-ish outputs are known + `ASSERT_KNOWN(PhyTxClkKnown_A, phy_tx_clk, clk_int, rst_int) + `ASSERT_KNOWN(PhyTxDKnown_A, phy_txd, clk_int, rst_int) + `ASSERT_KNOWN(PhyTxCtlKnown_A, phy_tx_ctl, clk_int, rst_int) + `ASSERT_KNOWN(PhyRstKnown_A, phy_reset_n, clk_int, rst_int) + `ASSERT_KNOWN(TxEnKnown_A, mac_gmii_tx_en, clk_int, rst_int) + + // Assert TX AXI Stream is known + `ASSERT_KNOWN(TxAxisTreadyKnown_A, tx_axis_tready, clk_int, rst_int) + + // Assert RX AXI Stream is known + `ASSERT_KNOWN(RxAxisTValidKnown_A, rx_axis_tvalid, clk_int, rst_int) + `ASSERT_KNOWN_IF(RxAxisTDataKnown_A, rx_axis_tdata, rx_axis_tvalid, clk_int, rst_int) + `ASSERT_KNOWN_IF(RxAxisTLastKnown_A, rx_axis_tlast, rx_axis_tvalid, clk_int, rst_int) + `ASSERT_KNOWN_IF(RxAxisTUserKnown_A, rx_axis_tuser, rx_axis_tvalid, clk_int, rst_int) + + // Assert some reset conditions + // all other outputs are irrelevant on reset since their validity is subject to one of these signals + `ASSERT(TxEnOnReset_A, rst_int |-> !mac_gmii_tx_en, clk_int, 0) + `ASSERT(TxCtlOnReset_A, rst_int |=> !phy_tx_ctl, clk_int, 0) // `|=>` is used here because the ODDR introduces a one cycle latency which has no reset. It shouldn't matter since phy_reset_n will disable the whole PHY anyway + `ASSERT(PHYResetOnReset_A, rst_int |-> !phy_reset_n, clk_int, 0) + `ASSERT(RxAxisInvalidOnReset_A, rst_int |-> !rx_axis_tvalid, clk_int, 0) + // ^ clock ^ normally we would put a reset here to disable the assertion, but we want these ones to hold on reset + + // Assert packets are appropriately sized and spaced + //`ifndef SYNTHESIS + ethernet_rgmii_tx_assertions u_ethernet_rgmii_tx_assertions ( + .phy_tx_clk, + .phy_tx_ctl, + .phy_reset_n + ); + //`endif +endmodule diff --git a/hw/vendor/ethernet/ssio_ddr_in.sv b/hw/vendor/ethernet/hw/core_eth/mac/ssio_ddr_in.sv similarity index 88% rename from hw/vendor/ethernet/ssio_ddr_in.sv rename to hw/vendor/ethernet/hw/core_eth/mac/ssio_ddr_in.sv index dc1cca5c6..82ee82262 100644 --- a/hw/vendor/ethernet/ssio_ddr_in.sv +++ b/hw/vendor/ethernet/hw/core_eth/mac/ssio_ddr_in.sv @@ -29,8 +29,8 @@ THE SOFTWARE. */ module ssio_ddr_in # ( - // target ("SIM", "GENERIC", "XILINX", "ALTERA") - parameter TARGET = "GENERIC", + // target (e.g. Xilinx FPGA, generic model, etc.) + parameter TARGET = "SIM", // "SIM", "GENERIC", "XILINX", or "ALTERA" // IODDR style ("IODDR", "IODDR2") // Use IODDR for Virtex-4, Virtex-5, Virtex-6, 7 Series, Ultrascale // Use IODDR2 for Spartan-6 @@ -59,11 +59,11 @@ wire clk_io; generate -if (TARGET == "XILINX") begin +if (TARGET == "XILINX") begin : gen_xilinx_ssio_ddr_in // use Xilinx clocking primitives - if (CLOCK_INPUT_STYLE == "BUFG") begin + if (CLOCK_INPUT_STYLE == "BUFG") begin : gen_bufg_noio // buffer RX clock BUFG @@ -76,7 +76,7 @@ if (TARGET == "XILINX") begin assign clk_io = clk_int; assign output_clk = clk_int; - end else if (CLOCK_INPUT_STYLE == "BUFR") begin + end else if (CLOCK_INPUT_STYLE == "BUFR") begin : gen_bufio_bufr assign clk_int = input_clk; @@ -97,8 +97,8 @@ if (TARGET == "XILINX") begin .CE(1'b1), .CLR(1'b0) ); - - end else if (CLOCK_INPUT_STYLE == "BUFIO") begin + + end else if (CLOCK_INPUT_STYLE == "BUFIO") begin : gen_bufio_bufg assign clk_int = input_clk; @@ -116,7 +116,7 @@ if (TARGET == "XILINX") begin .O(output_clk) ); - end else if (CLOCK_INPUT_STYLE == "BUFIO2") begin + end else if (CLOCK_INPUT_STYLE == "BUFIO2") begin : gen_bufio2_bufg // pass through RX clock to input buffers BUFIO2 #( @@ -141,7 +141,7 @@ if (TARGET == "XILINX") begin end -end else begin +end else begin : gen_generic_ssio_ddr_in // pass through RX clock to input buffers assign clk_io = input_clk; diff --git a/hw/vendor/ethernet/hw/core_eth/rx_framing/ethernet_rx_framing.sv b/hw/vendor/ethernet/hw/core_eth/rx_framing/ethernet_rx_framing.sv new file mode 100644 index 000000000..dc909be31 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/rx_framing/ethernet_rx_framing.sv @@ -0,0 +1,203 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_rx_framing +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Filters incoming packets based on MAC address and stores the data in a ring +buffer. A descriptor table provides metadata for each packet, including the +packet length and a pointer to its location in the ring buffer. Packets can be +popped by asserting a pulse on pop_pkt_i, which will free the oldest packet in +the buffer. + +Interfaces: + - AXI-Stream input (rx_axis_i) - no backpressure. User-defined signal asserts to indicate that the whole packet is bad + - Read-only memory interface for the data buffer (data_buf_mem_req_i, data_buf_mem_rsp_o) + - Read-only memory interface for the descriptor table (desc_table_mem_req_i, desc_table_rsp_o) + - Statuses (rx_status_o) + - Configuration (rx_config_i) + - Pulse to pop the oldest packet (pop_pkt_i) + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +`include "prim_assert.sv" + +module ethernet_rx_framing ( + input logic clk_i, + input logic rst_ni, + + // AXI-Stream input + input ethernet_pkg::axis_t rx_axis_i, + // backpressure is not allowed on the input stream + + // Memory for the data buffer + input mem_if_utils_pkg::mem_req_t data_buf_mem_req_i, + output mem_if_utils_pkg::mem_rsp_t data_buf_mem_rsp_o, + + // Memory interface for the packet descriptors + input mem_if_utils_pkg::mem_req_t desc_table_mem_req_i, + output mem_if_utils_pkg::mem_rsp_t desc_table_mem_rsp_o, + + // Statuses + output ethernet_pkg::rx_status_t rx_status_o, + + // Configuration + input ethernet_pkg::rx_config_t rx_config_i, + input logic pop_pkt_i // pulse to pop oldest packet +); + /////////////////////////// + // Filter write requests // + /////////////////////////// + // Filter write requests to the data buffer and descriptor table (they are read-only from the software side) + mem_if_utils_pkg::mem_req_t data_buf_mem_req_ro,desc_table_mem_req_ro; + mem_if_utils_pkg::mem_rsp_t data_buf_mem_rsp_ro,desc_table_mem_rsp_ro; + // mem_to_ro_mem takes a simple memory interface and blocks any write requests, responding to them immediately with an error response + // read requests are passed straight through + // it's mostly just combo logic + mem_to_ro_mem data_buf_mem_to_ro ( + .clk_i, .rst_ni, + .mem_req_i(data_buf_mem_req_i), + .mem_rsp_o(data_buf_mem_rsp_o), + .mem_req_o(data_buf_mem_req_ro), + .mem_rsp_i(data_buf_mem_rsp_ro) + ); + mem_to_ro_mem desc_table_mem_to_ro ( + .clk_i, .rst_ni, + .mem_req_i(desc_table_mem_req_i), + .mem_rsp_o(desc_table_mem_rsp_o), + .mem_req_o(desc_table_mem_req_ro), + .mem_rsp_i(desc_table_mem_rsp_ro) + ); + logic unused; + assign unused = ^{data_buf_mem_req_ro.data,data_buf_mem_req_ro.be,data_buf_mem_req_ro.we,desc_table_mem_req_ro.data,desc_table_mem_req_ro.be,desc_table_mem_req_ro.we}; + + ///////////////////// + // Packet metadata // + ///////////////////// + logic abandon_packet; // pulse to abandon this packet - won't necessarily be held for the whole packet, but if it is high at any point that is sufficient for the axis_pkt_ring_buffer to discard it + ethernet_pkg::pkt_metadata_t current_pkt_metadata; + ethernet_pkg::capture_reason_e capture_reason; // The reason for capturing this packet + + assign current_pkt_metadata.capture_reason = capture_reason; + // this gets sampled by the ring buffer at the end of the packet + + ////////////////////////// + // Feed the ring buffer // + ////////////////////////// + ethernet_pkg::pkt_metadata_t read_if_pkt_metadata; + logic [ethernet_pkg::RX_DATA_BUF_ADDR_W-1:0] read_if_pkt_buf_addr; + ethernet_pkg::pkt_len_t read_if_pkt_len; + axis_pkt_ring_buffer #( + .PACKET_INDEX_W (ethernet_pkg::RX_DESC_TABLE_ADDR_MSBS), // allow 8 packets in the buffer + .BUF_ADDR_W (ethernet_pkg::RX_DATA_BUF_ADDR_W), // allow 2^BUF_ADDR_W bytes in the buffer + .MAX_PKT_LEN (ethernet_pkg::MAX_ETH_PKT_LEN), + .BufAlmostFullThreshold (1524), // maximum ethernet packet is 1522 bytes = 190 words*64 bits/word + .TableAlmostFullThreshold (2), // raise the alarm when only two slots are left (out of 8) + .metadata_t (ethernet_pkg::pkt_metadata_t) + ) ring_buffer_inst ( + .clk_i, + .rst_ni, + + // Packet input stream + .pkt_tdata_i (rx_axis_i.data ), + .pkt_tvalid_i (rx_axis_i.valid ), + .pkt_tlast_i (rx_axis_i.last ), + + .pkt_metadata_i (current_pkt_metadata ), + .pkt_abandon_i (abandon_packet ), + + // Descriptor table access + .table_req_i (desc_table_mem_req_ro.req ), + .table_index_i (desc_table_mem_req_ro.addr[ethernet_pkg::RX_DESC_TABLE_ADDR_W-1:ethernet_pkg::RX_DESC_TABLE_ADDR_LSBS] ), + .table_gnt_o (desc_table_mem_rsp_ro.gnt ), + .table_err_o (desc_table_mem_rsp_ro.err ), + .table_metadata_o (read_if_pkt_metadata ), + .table_ptr_o (read_if_pkt_buf_addr ), + .table_pkt_len_o (read_if_pkt_len ), + + .table_pop_i (pop_pkt_i ), + + // Data buffer access + .buf_req_i (data_buf_mem_req_ro.req ), + .buf_addr_i (data_buf_mem_req_ro.addr[ethernet_pkg::RX_DATA_BUF_ADDR_W-1:0] ), // cut off MSBs + .buf_err_o (data_buf_mem_rsp_ro.err ), + .buf_data_o (data_buf_mem_rsp_ro.data ), + .buf_gnt_o (data_buf_mem_rsp_ro.gnt ), + + // Statuses + .empty_o (rx_status_o.empty ), + .packet_lost_o (rx_status_o.pkt_lost_pulse ), + .buf_almost_full_o (rx_status_o.buf_almost_full ), + .table_full_o (rx_status_o.table_full ), + .table_almost_full_o (rx_status_o.table_almost_full ), + .n_pkts_buffered_o (rx_status_o.n_packets_in_rx_buf ) + ); + + assign desc_table_mem_rsp_ro.data[15:0] = (16)'(read_if_pkt_metadata); // cast will just left-pad zeros + assign desc_table_mem_rsp_ro.data[31:16] = (16)'(read_if_pkt_len); // cast will just left-pad zeros + assign desc_table_mem_rsp_ro.data[63:32] = (32)'(read_if_pkt_buf_addr); // cast will just left-pad zeros + + /////////////////// + // MAC filtering // + /////////////////// + // Uses tlast to know when a new packet is starting, then checks the first 6 bytes (which will be the destination MAC address, per the Ethernet spec) + // to see if we should keep this packet + // abandon_packet will go high on the same cycle as tvalid if the MAC address is no good + // We also store capture_reason for later so it can be saved in the descriptor table + + mac_address_filter mac_address_filter_inst ( + .clk_i, + .rst_ni, + .rx_axis_i, + .mac_addr_i(rx_config_i.mac_addr), + .capture_reason_o(capture_reason) + ); + + // if the MAC doesn't match and we're not in promiscuous mode, we should drop the packet. Also drop if the CRC is bad + assign abandon_packet = ((capture_reason == ethernet_pkg::NON_MAC_MATCH) && ~rx_config_i.promiscuous_mode) || (rx_axis_i.user && rx_axis_i.valid); + + //////////////// + // Assertions // + //////////////// + // Assert the memory interfaces behave correctly + mem_if_assertions data_buf_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(data_buf_mem_req_i), + .mem_rsp_o(data_buf_mem_rsp_o) + ); + mem_if_assertions desc_table_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(desc_table_mem_req_i), + .mem_rsp_o(desc_table_mem_rsp_o) + ); + + // Assert outputs are known + `ASSERT_KNOWN(EmptyFlagKnown_A, rx_status_o.empty); + `ASSERT_KNOWN(BufFullFlagKnown_A, rx_status_o.pkt_lost_pulse); + `ASSERT_KNOWN(BufAlmostFullFlagKnown_A, rx_status_o.buf_almost_full); + `ASSERT_KNOWN(TableFullFlagKnown_A, rx_status_o.table_full); + `ASSERT_KNOWN(TableAlmostFullFlagKnown_A, rx_status_o.table_almost_full); + + // Assert behaviour under reset + `ASSERT(RxDataBufGntLowAtReset_A, !rst_ni |-> !data_buf_mem_rsp_o.gnt , clk_i, 0); + `ASSERT(RxStatusEmptyAtReset_A, !rst_ni |-> rx_status_o.empty , clk_i, 0); + `ASSERT(RxStatusPktLostAtReset_A, !rst_ni |-> !rx_status_o.pkt_lost_pulse , clk_i, 0); + `ASSERT(RxStatusBufAlmostfullAtReset_A, !rst_ni |-> !rx_status_o.buf_almost_full , clk_i, 0); + `ASSERT(RxStatusTableFullAtReset_A, !rst_ni |-> !rx_status_o.table_full , clk_i, 0); + `ASSERT(RxStatusTableAlmostFullAtReset_A, !rst_ni |-> !rx_status_o.table_almost_full , clk_i, 0); + // ^ clk, ^ normally a reset to disable the assertion, but we want these to be true even under reset + +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/rx_framing/mac_address_filter.sv b/hw/vendor/ethernet/hw/core_eth/rx_framing/mac_address_filter.sv new file mode 100644 index 000000000..c5e48a57f --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/rx_framing/mac_address_filter.sv @@ -0,0 +1,105 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mac_address_filter +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Monitors Ethernet packets of AXI Stream (packets delimited using TLAST) + +When a new packet starts, capture_reason will be invalid for the first 6 bytes +while it reads in the destination MAC address. Then it will give one of: + MAC_MATCHES + MAC_MULTICAST + MAC_BROADCAST + NON_MAC_MATCH +indicating the type of MAC address seen. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +mac_address_filter mac_address_filter_inst ( + .clk_i (), // clock + .rst_ni (), // reset, active low + .rx_axis_i (), // AXI Stream input (data, valid, and last) + .mac_addr_i (), // should be reasonably static, configuration signal + .capture_reason_o () // valid after the first 6 bytes of a packet have been received +); + +*/ + +module mac_address_filter ( + input clk_i, + input rst_ni, + + input logic [47:0] mac_addr_i, // MAC address to filter for + input ethernet_pkg::axis_t rx_axis_i, + output ethernet_pkg::capture_reason_e capture_reason_o +); + localparam logic [47:24] multicast_address = 24'h01005E; // multicast addresses begin like this + + logic [2:0] n_mac_bytes_checked; // count of how many bytes of the MAC address have been checked + + logic could_be_broadcast_next, could_be_broadcast_q; + logic could_be_mac_match_next, could_be_mac_match_q; + logic could_be_multicast_next, could_be_multicast_q; + + assign could_be_broadcast_next = could_be_broadcast_q && (rx_axis_i.data == 8'hFF); + assign could_be_mac_match_next = could_be_mac_match_q && (rx_axis_i.data == mac_addr_i[47 - n_mac_bytes_checked*8 -: 8]); + assign could_be_multicast_next = could_be_multicast_q && (rx_axis_i.data == multicast_address[47 - n_mac_bytes_checked*8 -: 8]); + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + n_mac_bytes_checked <= 3'b0; + end else if (rx_axis_i.valid && rx_axis_i.last) begin + n_mac_bytes_checked <= 3'b0; // reset the count at the start of a new packet + end else if (rx_axis_i.valid && n_mac_bytes_checked < 6) begin + n_mac_bytes_checked <= n_mac_bytes_checked + 1; // increment the count while we are receiving the first 6 bytes + end + end + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) could_be_broadcast_q <= 1'b1; + else if (rx_axis_i.valid && rx_axis_i.last) begin + could_be_broadcast_q <= 1'b1; // reset for a new packet + end else if (rx_axis_i.valid && n_mac_bytes_checked < 6) begin + could_be_broadcast_q <= could_be_broadcast_next; + end + end + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) could_be_mac_match_q <= 1'b1; + else if (rx_axis_i.valid && rx_axis_i.last) begin + could_be_mac_match_q <= 1'b1; // reset for a new packet + end else if (rx_axis_i.valid && n_mac_bytes_checked < 6) begin + could_be_mac_match_q <= could_be_mac_match_next; + end + end + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) could_be_multicast_q <= 1'b1; + else if (rx_axis_i.valid && rx_axis_i.last) begin + could_be_multicast_q <= 1'b1; // reset for a new packet + end else if (rx_axis_i.valid && n_mac_bytes_checked < 3) begin + could_be_multicast_q <= could_be_multicast_next; + end + end + + assign capture_reason_o = could_be_mac_match_q ? ethernet_pkg::MAC_MATCHES : + could_be_multicast_q ? ethernet_pkg::MAC_MULTICAST : + could_be_broadcast_q ? ethernet_pkg::MAC_BROADCAST : + ethernet_pkg::NON_MAC_MATCH; + + logic unused; + assign unused = rx_axis_i.user; // suppress unused signal warning +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/top/ethernet_mem_map.sv b/hw/vendor/ethernet/hw/core_eth/top/ethernet_mem_map.sv new file mode 100644 index 000000000..5ae4ec75b --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/top/ethernet_mem_map.sv @@ -0,0 +1,182 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_mem_map +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Split an incoming memory interface into four separate memory interfaces for: +- TX data buffer +- RX data buffer +- RX descriptor table +- CSRs + + Access Base Width Depth (words) Depth (bytes) Note +CSRs r/w 0x800 32 bits 0x10 0x80 +TX buffer wo 0x1000 64 bits 0x100 0x800 Maximum packet size is 0x5F2 bytes = 1522 bytes = 191 words = 0xbf +RX buffer ro 0x4000 64 bits 0x400 0x2000 Space for 5.4 max-sized packets or 32 typically-sized packets +RX table ro 0x6000 64 bits 0x8 0x40 8 entries for 8 packets, each entry is 64 bits (metadata for a packet) + +This module currently wraps bus.sv which is taken from Ibex and has a couple of +known issues, but none of them are problematic for this implementation. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +`include "prim_assert.sv" + +module ethernet_mem_map ( + input logic clk_i, + input logic rst_ni, + + // Host interface + input mem_if_utils_pkg::mem_req_t main_mem_req_i, + output mem_if_utils_pkg::mem_rsp_t main_mem_rsp_o, + + // Memory for RX buffer + output mem_if_utils_pkg::mem_req_t rx_data_mem_req_o, + input mem_if_utils_pkg::mem_rsp_t rx_data_mem_rsp_i, + + // Memory for TX buffer + output mem_if_utils_pkg::mem_req_t tx_data_mem_req_o, + input mem_if_utils_pkg::mem_rsp_t tx_data_mem_rsp_i, + + // Memory for RX descriptor table + output mem_if_utils_pkg::mem_req_t rx_meta_mem_req_o, + input mem_if_utils_pkg::mem_rsp_t rx_meta_mem_rsp_i, + + // Memory interface for registers + output mem_if_utils_pkg::mem_req_t reg_mem_req_o, + input mem_if_utils_pkg::mem_rsp_t reg_mem_rsp_i +); + localparam int DATA_W = mem_if_utils_pkg::DATA_W; + localparam int ADDR_W = mem_if_utils_pkg::ADDR_W; + localparam int DATA_W_BYTES = mem_if_utils_pkg::DATA_W_BYTES; + + logic reqs [4]; + logic rvalids [4]; + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) rvalids <= '{default: '0}; + else rvalids <= reqs; // rvalid is required to lag reqs & gnts by one cycle. This bus requires gnt to always be given immediately. I don't even bother driving rvakud in my downstream designs, so let's drive it here + end + + logic [ADDR_W-1:0] addrs [4]; + logic wes [4]; + logic errs [4]; + logic gnts [4]; + logic [DATA_W_BYTES-1:0] bes [4]; + logic [DATA_W-1:0] wdatas [4]; + logic [DATA_W-1:0] rdatas [4]; + + // Read data lines + assign rdatas = { reg_mem_rsp_i.data , tx_data_mem_rsp_i.data , rx_data_mem_rsp_i.data , rx_meta_mem_rsp_i.data }; + + // Error lines (one bit each) + assign errs = { reg_mem_rsp_i.err , tx_data_mem_rsp_i.err , rx_data_mem_rsp_i.err , rx_meta_mem_rsp_i.err }; + + // Grant lines (one bit each) + assign gnts = { reg_mem_rsp_i.gnt , tx_data_mem_rsp_i.gnt , rx_data_mem_rsp_i.gnt , rx_meta_mem_rsp_i.gnt }; + + // Request lines + assign reg_mem_req_o.req = reqs[0]; + assign tx_data_mem_req_o.req = reqs[1]; + assign rx_data_mem_req_o.req = reqs[2]; + assign rx_meta_mem_req_o.req = reqs[3]; + + // Address lines + assign reg_mem_req_o.addr = addrs[0]; + assign tx_data_mem_req_o.addr = addrs[1]; + assign rx_data_mem_req_o.addr = addrs[2]; + assign rx_meta_mem_req_o.addr = addrs[3]; + + // Write-enable lines (one bit) + assign reg_mem_req_o.we = wes[0]; + assign tx_data_mem_req_o.we = wes[1]; + assign rx_data_mem_req_o.we = wes[2]; + assign rx_meta_mem_req_o.we = wes[3]; + + // Byte-enable lines (one bit per byte) + assign reg_mem_req_o.be = bes[0]; + assign tx_data_mem_req_o.be = bes[1]; + assign rx_data_mem_req_o.be = bes[2]; + assign rx_meta_mem_req_o.be = bes[3]; + + // Write data lines + assign reg_mem_req_o.data = wdatas[0]; + assign tx_data_mem_req_o.data = wdatas[1]; + assign rx_data_mem_req_o.data = wdatas[2]; + assign rx_meta_mem_req_o.data = wdatas[3]; + + // these have to be an unpacked types or SystemVerilog will be upset + logic main_gnt [1]; + logic [DATA_W-1:0] main_rdata [1]; + logic main_err [1]; + assign main_mem_rsp_o.gnt = main_gnt[0]; + assign main_mem_rsp_o.data = main_rdata[0]; + assign main_mem_rsp_o.err = main_err[0]; + + + // Instantiate standard memory mapper bus (from Ibex) + bus #( + .NrDevices (4), + .NrHosts (1), + .DataWidth (DATA_W), + .AddressWidth (ADDR_W) + ) bus_inst ( + .clk_i (clk_i), + .rst_ni (rst_ni), + + .host_req_i ({main_mem_req_i.req}), + .host_addr_i ({main_mem_req_i.addr & 64'h0000_0000_0000_FFFF}), // mask off upper bits since they aren't used for this + .host_we_i ({main_mem_req_i.we}), + .host_be_i ({main_mem_req_i.be}), + .host_wdata_i ({main_mem_req_i.data}), + .host_rvalid_o (), // not used by my interface, it assumes we are valid the cycle after req&gnt + .host_gnt_o (main_gnt), + .host_rdata_o (main_rdata), + .host_err_o (main_err), + + .device_req_o (reqs), + .device_addr_o (addrs), + .device_we_o (wes), + .device_be_o (bes), + .device_gnt_i (gnts), + .device_wdata_o (wdatas), + .device_rdata_i (rdatas), + .device_err_i (errs), + + // CSR Address space \/ TX buffer address space \/ RX buffer address space \/ RX metadata address space \/ + .cfg_device_addr_base ({ 64'h0000_0000_0000_0800 , 64'h0000_0000_0000_1000 , 64'h0000_0000_0000_4000 , 64'h0000_0000_0000_6000 }), + .cfg_device_addr_mask ({~(64'h0000_0000_0000_007F) , ~(64'h0000_0000_0000_07FF) , ~(64'h0000_0000_0000_1FFF) , ~(64'h0000_0000_0000_003F) }), + .device_rvalid_i (rvalids) + ); + + //////////////// + // Assertions // + //////////////// + // The bus instantiated above doesn't support wait states, so the downstream devices must always grant immediately. Asserting this just to be sure + `ASSERT(GntAlwaysGiven_CSR_A, reg_mem_req_o.req -> reg_mem_rsp_i.gnt ); + `ASSERT(GntAlwaysGiven_TX_data_A, tx_data_mem_req_o.req -> tx_data_mem_rsp_i.gnt ); + `ASSERT(GntAlwaysGiven_RX_data_A, rx_data_mem_req_o.req -> rx_data_mem_rsp_i.gnt ); + `ASSERT(GntAlwaysGiven_RX_meta_A, rx_meta_mem_req_o.req -> rx_meta_mem_rsp_i.gnt ); + + // Assert the host interface behaves correctly + mem_if_assertions u_mem_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(main_mem_req_i), + .mem_rsp_o(main_mem_rsp_o) + ); + + // the other interfaces have similar assertions in their respective downstream modules, so I won't bother asserting them here +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/top/ethernet_pkg.sv b/hw/vendor/ethernet/hw/core_eth/top/ethernet_pkg.sv new file mode 100644 index 000000000..08efc60e6 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/top/ethernet_pkg.sv @@ -0,0 +1,161 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_pkg +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Package of types and constants for the Ethernet IP. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +N/A + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +N/A + +*/ + +package ethernet_pkg; + // Constants + localparam int MAX_ETH_PKT_LEN = 1522; // maximum Ethernet packet length in bytes + typedef logic [$clog2(MAX_ETH_PKT_LEN)-1:0] pkt_len_t; + + localparam logic [31:0] VENDOR_ID = 32'h6C525343; // "lRSC" in ASCII + localparam logic [31:0] DEVICE_ID = 32'h45544858; // "ETHX" in ASCII (X for AXI, there as a placeholder to allow alternate top-levels in future) + localparam logic [31:0] MAGIC_NUM = 32'h4720abe4; // random magic number to identify the build + + //////////////////////////// + // RX packet table params // + //////////////////////////// + localparam RX_DESC_TABLE_WIDTH = 64; + localparam RX_DESC_TABLE_DEPTH = 8; // number of packets to buffer + + localparam RX_DESC_TABLE_BYTES = RX_DESC_TABLE_DEPTH*RX_DESC_TABLE_WIDTH/8; + localparam RX_DESC_TABLE_ADDR_MSBS = $clog2(RX_DESC_TABLE_DEPTH); + localparam RX_DESC_TABLE_ADDR_W = $clog2(RX_DESC_TABLE_BYTES); // byte address width + localparam RX_DESC_TABLE_ADDR_LSBS = $clog2(RX_DESC_TABLE_WIDTH/8); // number of LSbs to ignore for word addressing + + /////////////////////////// + // RX data buffer params // + /////////////////////////// + localparam RX_DATA_BUF_WIDTH = 64; + localparam RX_DATA_BUF_DEPTH = 1024; + + localparam RX_DATA_BUF_BYTES = RX_DATA_BUF_DEPTH*RX_DATA_BUF_WIDTH/8; + localparam RX_DATA_BUF_ADDR_MSBS = $clog2(RX_DATA_BUF_DEPTH); + localparam RX_DATA_BUF_ADDR_W = $clog2(RX_DATA_BUF_BYTES); // byte address width + localparam RX_DATA_BUF_ADDR_LSBS = $clog2(RX_DATA_BUF_WIDTH/8); // number of LSbs to ignore for word addressing + + /////////////////////////// + // TX data buffer params // + /////////////////////////// + localparam TX_DATA_BUF_WIDTH = 64; + localparam TX_DATA_BUF_DEPTH = 256; // 256 words of 64 bits = 2048 bytes, enough for one max-sized packet + + localparam TX_DATA_BUF_BYTES = TX_DATA_BUF_DEPTH*TX_DATA_BUF_WIDTH/8; + localparam TX_DATA_BUF_ADDR_MSBS = $clog2(TX_DATA_BUF_DEPTH); + localparam TX_DATA_BUF_ADDR_W = $clog2(TX_DATA_BUF_BYTES); // byte address width + localparam TX_DATA_BUF_ADDR_LSBS = $clog2(TX_DATA_BUF_WIDTH/8); // number of LSbs to ignore for word addressing + + /////////////////////////////// + // CSRs address space params // + /////////////////////////////// + localparam CSR_WIDTH = 32; + localparam CSR_DEPTH = 32; // number of 32-bit registers. 32 is probably plenty + + localparam CSR_BYTES = CSR_DEPTH*CSR_WIDTH/8; + localparam CSR_ADDR_MSBS = $clog2(CSR_DEPTH); + localparam CSR_ADDR_W = $clog2(CSR_BYTES); // byte address width + localparam CSR_ADDR_LSBS = $clog2(CSR_WIDTH/8); // number of LSbs to ignore for word addressing + + // Enum: reason for capturing a packet + typedef enum logic [1:0] { + MAC_MATCHES = 0, + MAC_BROADCAST = 1, + MAC_MULTICAST = 2, + NON_MAC_MATCH = 3 + } capture_reason_e; + + // Metadata for a captured packet + typedef struct packed { + capture_reason_e capture_reason; // reason for capturing this packet + // Option to include more metadata: + // timestamp, sequence number, other stuff from the Ethernet frame (e.g. VLAN tag, source MAC address) + } pkt_metadata_t; + + // AXI Stream interface (one byte per beat) + typedef struct packed { + logic [7:0] data; + logic last; + logic valid; + logic user; + } axis_t; + + /////////////////////////// + // Config/status structs // + /////////////////////////// + + // RX status signals + typedef struct packed { + logic [$clog2(RX_DESC_TABLE_DEPTH+1)-1:0] n_packets_in_rx_buf; + logic empty; + logic table_almost_full; + logic table_full; + logic buf_almost_full; + logic pkt_lost_pulse; // pulses as an event whenever a packet is lost due to full buffer or full table + } rx_status_t; + + // RX config signals + typedef struct packed { + logic [47:0] mac_addr; // MAC address to match for capturing packets + logic promiscuous_mode; // whether to capture all packets regardless of MAC address + } rx_config_t; + + // TX status signals + typedef struct packed { + logic busy; + logic done_pulse; + } tx_status_t; + + // TX config signals + typedef struct packed { + logic [10:0] packet_len; + } tx_config_t; + + typedef struct packed { + logic loopback; + } mac_config_t; + + typedef struct packed { + logic tx_busy; + } mac_status_t; + + // RGMII interface for connecting Ethernet MAC to PHY + typedef struct packed { + logic clk; + logic ctl; + logic [3:0] d; + } eth_rgmii_rx_t; + + typedef struct packed { + logic clk; + logic en; + logic [3:0] d; + } eth_rgmii_tx_t; + + typedef logic eth_rgmii_mdio_in_t; + + typedef struct packed { + logic o; + logic oen; + logic c; + } eth_rgmii_mdio_out_t; +endpackage diff --git a/hw/vendor/ethernet/hw/core_eth/top/ethernet_top.sv b/hw/vendor/ethernet/hw/core_eth/top/ethernet_top.sv new file mode 100644 index 000000000..c42083e65 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/top/ethernet_top.sv @@ -0,0 +1,205 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_top +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Top-level Ethernet framer module. Instantiates the MAC, RX framing, TX framing, +and CSR blocks, as well as handling the memory interface for each one. + +Exposes an RGMII interface for connecting to the PHY and a memory interface for +accessing the TX data buffer, RX data buffer, RX metadata, and CSRs. + +Also exposes an MDIO interface, but with dedicated 'in' and 'out' lines instead +of a tristate line. An 'output enable' line is provided to connect to a +tristate buffer, which is not included here as it is technology-dependent. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +Smoke tests are available for this module named ethernet_top_tb.sv. +It is also exercises in the ethernet_top_axi top, which has a (more +comprehensive) testbench of its own. +See README.md for more details. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +ethernet_top #( + .TARGET(TARGET) +) ethernet_top_inst ( + // Clocking and reset + .clk_125M_i (), // Main clock - used by memory interface and as 125 MHz ethernet in-phase clock + .rst_ni (), // Main reset, deassertion synchronous to clk_125M_i + .clk_125M_quad_i (), // 125 MHz ethernet quadrature clock (used by MAC) + .clk_200M_i (), // 200 MHz IDELAYCTRL reference clock + .mem_req_i (), // Synchronous to clk_125M_i + .mem_rsp_o (), // Synchronous to clk_125M_i + .phy_reset_no (), + .eth_rgmii_rx_i (), + .eth_rgmii_tx_o (), + .eth_rgmii_mdio_i (), + .eth_rgmii_mdio_o (), + .irq_o () +); + +*/ + +module ethernet_top #( + parameter TARGET = "SIM" // "SIM", "GENERIC", "XILINX", or "ALTERA" +) ( + // Clocking and reset + input logic clk_125M_i, // Main clock - used by memory interface and as 125 MHz ethernet in-phase clock + input logic rst_ni, // Main reset - asynchronous assertion, deassertion synchronous to clk_125m_i + + input logic clk_125M_quad_i, // 125 MHz ethernet quadrature clock (used by MAC) + input logic clk_200M_i, // 200 MHz IDELAYCTRL reference clock + + // Main memory interface + input mem_if_utils_pkg::mem_req_t mem_req_i, // Synchronous to clk_125M_i + output mem_if_utils_pkg::mem_rsp_t mem_rsp_o, // Synchronous to clk_125M_i + + // RGMII signals (connects Ethernet PHY to MAC) + output logic phy_reset_no, // Active low reset to PHY (note: "no" is not short for "number"; it's 'n' for active-low and 'o' for output) + input ethernet_pkg::eth_rgmii_rx_t eth_rgmii_rx_i, + output ethernet_pkg::eth_rgmii_tx_t eth_rgmii_tx_o, + input ethernet_pkg::eth_rgmii_mdio_in_t eth_rgmii_mdio_i, + output ethernet_pkg::eth_rgmii_mdio_out_t eth_rgmii_mdio_o, + + // IRQ lines + output logic irq_o // Synchronous to clk_125M_i +); + ////////////////// + // Declarations // + ////////////////// + + // Connect the MAC to the RX and TX framing modules + // AXI Stream interface + ethernet_pkg::axis_t rx_axis_real,rx_axis_muxed; + ethernet_pkg::axis_t tx_axis; + logic tx_axis_tready; // backpressure for TX + // there is no backpressure allowed for RX + + // Connect RX framing to CSR block + ethernet_pkg::rx_config_t rx_config; + ethernet_pkg::rx_status_t rx_status; + logic rx_pop; // signal from CSR block to RX framing block to pop a packet + + // Connect TX framing to CSR block + ethernet_pkg::tx_config_t tx_config; + ethernet_pkg::tx_status_t tx_status; + logic tx_kick; // signal from CSR block to TX framing block to kick off a transmission + + // Connect MAC to CSR block + ethernet_pkg::mac_config_t mac_config; + ethernet_pkg::mac_status_t mac_status; + + // Connect RX framing, TX framing, and CSR block to Memory Map + mem_if_utils_pkg::mem_req_t rx_data_mem_req; // Memory interface for RX data buffer + mem_if_utils_pkg::mem_rsp_t rx_data_mem_rsp; + mem_if_utils_pkg::mem_req_t rx_meta_mem_req; // Memory interface for RX metadata (description table) + mem_if_utils_pkg::mem_rsp_t rx_meta_mem_rsp; + mem_if_utils_pkg::mem_req_t tx_data_mem_req; // Memory interface for TX data buffer + mem_if_utils_pkg::mem_rsp_t tx_data_mem_rsp; + mem_if_utils_pkg::mem_req_t reg_mem_req; // Memory interface for registers (configuration and status) + mem_if_utils_pkg::mem_rsp_t reg_mem_rsp; + + // Instantiate MAC + mac_wrapper #( + .TARGET(TARGET) + ) mac_wrapper_inst ( + // Clocks and resets + .clk_125M_i (clk_125M_i), + .rst_ni (rst_ni), + .clk_125M_quad_i (clk_125M_quad_i), + .clk_200M_i (clk_200M_i), + .phy_reset_no (phy_reset_no), + // Config/status + .mac_status_o (mac_status), + .mac_config_i (mac_config), + // RGMII interfaces + .rgmii_rx_i (eth_rgmii_rx_i), + .rgmii_tx_o (eth_rgmii_tx_o), + // AXI Stream interfaces + .tx_axis_i (tx_axis), + .tx_axis_tready_o (tx_axis_tready), + .rx_axis_o (rx_axis_real) + ); + + // Instantiate CSR block + ethernet_csr csr_inst ( + .clk_i (clk_125M_i ), + .rst_ni (rst_ni ), + .mem_req_i (reg_mem_req ), + .mem_rsp_o (reg_mem_rsp ), + .mac_config_o (mac_config ), + .mac_status_i (mac_status ), + .rx_config_o (rx_config ), + .rx_status_i (rx_status ), + .rx_pop_o (rx_pop ), + .tx_config_o (tx_config ), + .tx_status_i (tx_status ), + .tx_kick_o (tx_kick ), + .eth_rgmii_mdio_i (eth_rgmii_mdio_i ), + .eth_rgmii_mdio_o (eth_rgmii_mdio_o ), + .irq_o (irq_o ) + ); + + ethernet_mem_map mem_map_inst ( + .clk_i (clk_125M_i ), + .rst_ni (rst_ni ), + .main_mem_req_i (mem_req_i ), + .main_mem_rsp_o (mem_rsp_o ), + .rx_data_mem_req_o (rx_data_mem_req ), + .rx_data_mem_rsp_i (rx_data_mem_rsp ), + .rx_meta_mem_req_o (rx_meta_mem_req ), + .rx_meta_mem_rsp_i (rx_meta_mem_rsp ), + .tx_data_mem_req_o (tx_data_mem_req ), + .tx_data_mem_rsp_i (tx_data_mem_rsp ), + .reg_mem_req_o (reg_mem_req ), + .reg_mem_rsp_i (reg_mem_rsp ) + ); + + // Instantiate TX framing + ethernet_tx_framing tx_framing_inst( + .clk_i (clk_125M_i ), + .rst_ni (rst_ni ), + .tx_axis_o (tx_axis ), + .tx_axis_tready_i (tx_axis_tready ), // backpressure from MAC (legal for TX, not for RX) + .data_buf_mem_req_i (tx_data_mem_req), // connects to the memory map + .data_buf_mem_rsp_o (tx_data_mem_rsp), // connects to the memory map + .tx_status_o (tx_status ), // connects to CSR block + .tx_config_i (tx_config ), // connects to CSR block + .kick_tx_i (tx_kick ) // connects to CSR block + ); + + // Instantiate RX framing + ethernet_rx_framing rx_framing_inst ( + .clk_i (clk_125M_i ), + .rst_ni (rst_ni ), + .rx_axis_i (rx_axis_muxed ), // connects to MAC output + .data_buf_mem_req_i (rx_data_mem_req), // connects to the memory map + .data_buf_mem_rsp_o (rx_data_mem_rsp), // connects to the memory map + .desc_table_mem_req_i (rx_meta_mem_req), // connects to the memory map + .desc_table_mem_rsp_o (rx_meta_mem_rsp), // connects to the memory map + .rx_status_o (rx_status ), // connects to CSR block + .rx_config_i (rx_config ), // connects to CSR block + .pop_pkt_i (rx_pop ) // connects to CSR block + ); + + // Instantiate the loopback MUX + ethernet_loopback loopback_mux ( + .clk_i (clk_125M_i ), + .rst_ni (rst_ni ), + .loopback_en (mac_config.loopback), // select between TX and RX for loopback + .axis_tx_ready_i(tx_axis_tready ), // ready line to the TX AXI Stream - here it is used as an input so the MUX knows when a beat has been accepted. This only works because there is no backpressure on the RX side of this + .axis_tx_i (tx_axis ), // AXI Stream from TX framing + .axis_rx_real_i (rx_axis_real ), // AXI Stream from MAC RX output + .axis_o (rx_axis_muxed ) // Multiplexed AXI stream + ); +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/top/mac_wrapper.sv b/hw/vendor/ethernet/hw/core_eth/top/mac_wrapper.sv new file mode 100644 index 000000000..64de8862b --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/top/mac_wrapper.sv @@ -0,0 +1,87 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mac_wrapper +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Thin wrapper around rgmii_soc which creates an RGMII MAC +Exposes two AXI Streams: + - TX: AXI Stream input to the MAC + - RX: AXI Stream output from the MAC (no backpressure) +'tlast' should be taken as indicating the end of an Ethernet frame. +'tuser' should be interpreted as indicating an error in the frame. +The FCS will not be included in the AXI Stream, but the MAC will check it. + +The module handles mapping signal names to the Alex Forencich RGMII MAC core + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +module mac_wrapper #( + parameter TARGET = "XILINX" +) ( + // Clocks and resets + input clk_125M_i, // main 125 MHz clock + input rst_ni, // main active-low reset + input clk_125M_quad_i,// quadrature 125 MHz clock + input clk_200M_i, // 200 MHz clock + + // Config/status + output wire phy_reset_no, // Active-low reset output to be routed to the PHY + output ethernet_pkg::mac_status_t mac_status_o, // MAC Status struct + input ethernet_pkg::mac_config_t mac_config_i, // MAC Config struct + + // Ethernet: 1000BASE-T RGMII signals + input ethernet_pkg::eth_rgmii_rx_t rgmii_rx_i, + output ethernet_pkg::eth_rgmii_tx_t rgmii_tx_o, + + // AXI Stream input for TX + input ethernet_pkg::axis_t tx_axis_i, + output logic tx_axis_tready_o, + + // AXI Stream output for RX (no backpressure) + output ethernet_pkg::axis_t rx_axis_o +); + logic unused; + assign unused = ^mac_config_i; // currently unused + + ///////////////////////// + // Instantiate the MAC // + ///////////////////////// + rgmii_soc #( + .TARGET(TARGET) + ) rgmii_soc_inst ( + .rst_int (~rst_ni ), // rgmii_soc uses active HIGH reset (boy did I waste a lot of time discovering that) + .clk_int (clk_125M_i ), + .clk90_int (clk_125M_quad_i ), + .clk_200_int (clk_200M_i ), + + .phy_rx_clk (rgmii_rx_i.clk ), + .phy_rxd (rgmii_rx_i.d ), + .phy_rx_ctl (rgmii_rx_i.ctl ), + .phy_tx_clk (rgmii_tx_o.clk ), + .phy_txd (rgmii_tx_o.d ), + .phy_tx_ctl (rgmii_tx_o.en ), + .phy_reset_n (phy_reset_no ), + .mac_gmii_tx_en (mac_status_o.tx_busy ), + .tx_axis_tdata (tx_axis_i.data ), + .tx_axis_tvalid (tx_axis_i.valid ), + .tx_axis_tlast (tx_axis_i.last ), + .tx_axis_tready (tx_axis_tready_o ), + .tx_axis_tuser (tx_axis_i.user ), // Can be used to indicate an error in the frame + .rx_axis_tdata (rx_axis_o.data ), + .rx_axis_tvalid (rx_axis_o.valid ), + .rx_axis_tlast (rx_axis_o.last ), + .rx_axis_tuser (rx_axis_o.user ) // Indicates a CRC error in the received frame + ); +endmodule diff --git a/hw/vendor/ethernet/hw/core_eth/tx_framing/ethernet_tx_framing.sv b/hw/vendor/ethernet/hw/core_eth/tx_framing/ethernet_tx_framing.sv new file mode 100644 index 000000000..4e19501d7 --- /dev/null +++ b/hw/vendor/ethernet/hw/core_eth/tx_framing/ethernet_tx_framing.sv @@ -0,0 +1,165 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ethernet_tx_framing +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +TX framing control for Ethernet. Includes a write-only buffer for data to +to be transmitted, and an FSM for streaming that data out over an AXI Stream. + +The FSM is triggered by a pulse on 'kick_tx_i' and runs to the length of +tx_config_i.packet_len. + +Statuses: +While transmitting, the 'busy' signal will be asserted. +On the last cycle of transmission, 'done_pulse' will be high for one cycle. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +ethernet_top and ethernet_top_axi testbenches. + +*/ + +`include "prim_assert.sv" + +/* +TX framing module +*/ + +module ethernet_tx_framing ( + input clk_i, + input rst_ni, + output ethernet_pkg::axis_t tx_axis_o, // AXI Stream output (data, valid, and last) + input tx_axis_tready_i, // backpressure from Ethernet MAC (legal for TX, not for RX) + input mem_if_utils_pkg::mem_req_t data_buf_mem_req_i, // memory interface requests from software + output mem_if_utils_pkg::mem_rsp_t data_buf_mem_rsp_o, // memory interface response to software + output ethernet_pkg::tx_status_t tx_status_o, // status signals + input ethernet_pkg::tx_config_t tx_config_i, // config signals + input kick_tx_i // PULSE to start a transaction. Must not arrive while tx_status_o.busy is high +); + ////////////////// + // Declarations // + ////////////////// + logic [ethernet_pkg::TX_DATA_BUF_ADDR_W-1:0] tx_buffer_addr,tx_buffer_addr_next; // address pointer for reading from the buffer + logic tx_busy; + + /////////////////////////////////////////////// + // Make the host memory interface write-only // + /////////////////////////////////////////////// + mem_if_utils_pkg::mem_req_t tx_buffer_wr_req; + mem_if_utils_pkg::mem_rsp_t tx_buffer_wr_rsp; + + mem_to_wo_mem mem_filter_inst ( + .clk_i(clk_i), + .rst_ni, + .mem_req_i(data_buf_mem_req_i), + .mem_rsp_o(data_buf_mem_rsp_o), + .mem_req_o(tx_buffer_wr_req), + .mem_rsp_i(tx_buffer_wr_rsp) + ); + assign tx_buffer_wr_rsp.gnt = 1'b1; // grant all writes immediately + // TODO block writes if a TX is in progress and the address hasn't been tx'd yet (but allow addresses that are already TX'd to allow efficient streaming of packets) + // this would be very useful for preparing the next packet while the previous one is completing + assign tx_buffer_wr_rsp.err = 1'b0; // TODO also detect writes our of bounds + assign tx_buffer_wr_rsp.data = 64'hBADDF00DBADDF00D; // this data should never get used as this is a write-only memory + + ///////////////////////////////// + // Instantiate the data buffer // + ///////////////////////////////// + // This instantiates a Xilinx Primitive Macro to generate RAM + ram_downsizer_w64_r8 #( // TODO override memory size to be 1522 bytes instead of 2048 + .WR_ADDR_W (ethernet_pkg::TX_DATA_BUF_ADDR_W-ethernet_pkg::TX_DATA_BUF_ADDR_LSBS) // subtract to convert from byte addresses to 64-bit word addresses + ) tx_buffer ( + .clk_i, + .rst_ni, + // Writes from the host + .wr_en_i (tx_buffer_wr_req.req && tx_buffer_wr_req.we && &(tx_buffer_wr_req.be)), + .wr_addr_i (tx_buffer_wr_req.addr[ethernet_pkg::TX_DATA_BUF_ADDR_W-1:ethernet_pkg::TX_DATA_BUF_ADDR_LSBS]), // convert from byte address to word address + .wr_data_i (tx_buffer_wr_req.data), + + // Reads for the TX FSM + .rd_en_i (tx_busy | kick_tx_i), // keep reading data as long as we are busy transmitting, and on the first cycle + .rd_addr_i (tx_buffer_addr_next), // because of one cycle latency we pass in the NEXT address we will be transmitting + .rd_data_o (tx_axis_o.data) + ); + + ////////////////////// + // TX FSM and logic // + ////////////////////// + assign tx_axis_o.user = '0; // user signal used to indicate errors, not used for TX for now + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + tx_busy <= 1'b0; + end else begin + if (kick_tx_i && !tx_busy) begin + tx_busy <= 1'b1; // start transmitting when kicked + end else if (tx_axis_o.valid && tx_axis_tready_i && tx_axis_o.last) begin + tx_busy <= 1'b0; // stop transmitting after the last beat of the packet is accepted by the downstream + end else begin + tx_busy <= tx_busy; // hold value steady when no relevant event occurs + end + end + end + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) tx_buffer_addr <= '0; + else tx_buffer_addr <= tx_buffer_addr_next; + end + + always_comb begin + tx_buffer_addr_next = tx_buffer_addr; // default to hold value steady + if (tx_axis_o.valid && tx_axis_tready_i) begin + if (tx_axis_o.last) tx_buffer_addr_next = '0; // reset address pointer after the last beat of the packet is accepted by the downstream + else tx_buffer_addr_next = tx_buffer_addr + 1; // increment address pointer for the next beat + end + end + assign tx_axis_o.last = tx_buffer_addr == tx_config_i.packet_len - 1; // assert last when the current beat is the last byte of the packet + + assign tx_axis_o.valid = tx_busy; + + assign tx_status_o.busy = tx_busy; + assign tx_status_o.done_pulse = tx_axis_o.valid && tx_axis_tready_i && tx_axis_o.last; // pulse when the last beat of the packet is accepted by the downstream + + //////////////// + // Assertions // + //////////////// + // Assert the memory interface behaves correctly + mem_if_assertions u_mem_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(data_buf_mem_req_i), + .mem_rsp_o(data_buf_mem_rsp_o) + ); + + // Assert other outputs are defined + `ASSERT_KNOWN(TxAxisValidKnown_A, tx_axis_o.valid); + `ASSERT_KNOWN_IF(TxAxisDataKnown_A, tx_axis_o.data, tx_axis_o.valid); + `ASSERT_KNOWN_IF(TxAxisLastKnown_A, tx_axis_o.last, tx_axis_o.valid); + `ASSERT_KNOWN(TxStatusBusyKnown_A, tx_status_o.busy); + `ASSERT_KNOWN(TxStatusDonePulseKnown_A, tx_status_o.done_pulse); + + // Assert this isn't used incorrectly + `ASSERT(NoTxKickWhileBusy_A, ~(kick_tx_i && tx_status_o.busy)); // kick should not arrive while busy is high + + // Some behavioural assertions to check for integration mistakes or flag problematic behaviour + `ASSERT_PULSE(TxKickIsPulse_A, kick_tx_i); // kick should be a pulse + `ASSERT_PULSE(TxDoneIsPulse_A, tx_status_o.done_pulse); // done should be a pulse + `ASSERT(TxMemIFGntGivenInstantly_A, data_buf_mem_req_i.req -> data_buf_mem_rsp_o.gnt); // the memory interface should grant immediately since there are no wait states + + // Assert the AXI Stream is standard compliant + `ASSERT(TxAxisValidStaysUntilHandshake_A, tx_axis_o.valid && !tx_axis_tready_i |=> tx_axis_o.valid); // valid should stay high until the downstream is ready + `ASSERT(TxAxisInvariantUntilHandshake_A, tx_axis_o.valid && !tx_axis_tready_i |=> $stable(tx_axis_o.data) && $stable(tx_axis_o.last)); // data and last should stay the same until the downstream is ready + + // Assert behaviour under reset + `ASSERT(TxAxisValidLowAtReset_A, !rst_ni |-> !tx_axis_o.valid, clk_i, 0); + `ASSERT(TxBusyLowAtReset_A, !rst_ni |-> !tx_status_o.busy, clk_i, 0); + `ASSERT(TxDoneLowAtReset_A, !rst_ni |-> !tx_status_o.done_pulse, clk_i, 0); + // ^ clk, ^ normally a reset to disable the assertion, but we want these to be true even under reset +endmodule diff --git a/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.core b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.core new file mode 100644 index 000000000..fd453146d --- /dev/null +++ b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.core @@ -0,0 +1,21 @@ +CAPI=2: + +name: "lowrisc:ethernet:axis_pkt_ring_buffer" +description: "Translates AXI Stream into addressable buffer with a separate descriptor table for metadata + pointers" + +filesets: + rtl: + files: + - axis_pkt_ring_buffer_wr_ptr_logic.sv + - axis_pkt_ring_buffer.sv + file_type: systemVerilogSource + depend: + - lowrisc:ethernet:random_access_fifo + - lowrisc:prim:assert:0.1 + - lowrisc:prim:util:0.1 + - lowrisc:ethernet:mem_if_utils + +targets: + default: &default_target + filesets: + - rtl diff --git a/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.sv b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.sv new file mode 100644 index 000000000..a6b21e0d7 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer.sv @@ -0,0 +1,396 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: axis_pkt_ring_buffer +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Receives an AXI stream of data and stores it in a ring buffer, with a separate +table storing pointers and metadata about each packet. Packets are delimeted by +the 'tlast' signal. + +Instantiates its own internal RAM for the buffer, and a random-access FIFO for +the descriptor table. + +Once pkt_tvalid_i is asserted, we will begin capturing data in the ring buffer. +Whenever pkt_tlast_i is asserted, a new entry is added to the descriptor table +UNLESS pkt_abandon_i was asserted at any time during the packet, in which case +the packet is abandoned and will not be committed. Any data that was written +during that packet's streaming will be overwritten by the next packet. + +When a packet is captured, the descriptor table stores: + - a pointer to its start in the ring buffer + - its length + - the contents of pkt_metadata_i at the time of the packet's completion + +There are two memory interfaces: + - for reading the data buffer + - for reading the descriptor table +Memory interfaces behave according to mem_if_utils_pkg. + +The descriptor table can be read in any order, but entries can only be popped +(freed) from the head of the queue ("random-access FIFO"). Any entry in the +data buffer can be read at any time, but the data is only valid subject to the +descriptor table's pointers and lengths. + +DrawIO instance template: +%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22group%22%20vertex%3D%221%22%20connectable%3D%220%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22240%22%20y%3D%22240%22%20width%3D%22600%22%20height%3D%22440%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%223%22%20value%3D%22AXIS%20PKT%20RING%20BUFFER%22%20style%3D%22rounded%3D0%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BfontFamily%3DCourier%20New%3BverticalAlign%3Dtop%3BfontStyle%3D1%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20width%3D%22600%22%20height%3D%22440%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%224%22%20value%3D%22pkt_tdata_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%220.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%225%22%20value%3D%22pkt_tvalid_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%2230.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%226%22%20value%3D%22pkt_tlast_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%2261%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%227%22%20value%3D%22%26lt%3Bdiv%26gt%3B%26lt%3Bspan%20style%3D%26quot%3Bbackground-color%3A%20transparent%3B%20color%3A%20light-dark(rgb(0%2C%200%2C%200)%2C%20rgb(255%2C%20255%2C%20255))%3B%26quot%3B%26gt%3Bpkt_metadata_i%26lt%3B%2Fspan%26gt%3B%26lt%3B%2Fdiv%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%2291%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%228%22%20value%3D%22pkt_abandon_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22121.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%229%22%20value%3D%22table_req_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%2230.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2210%22%20value%3D%22table_addr_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%2260.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2211%22%20value%3D%22table_gnt_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%2290.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2212%22%20value%3D%22table_err_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22120.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2213%22%20value%3D%22table_ptr_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22150.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2214%22%20value%3D%22pkt_err_table_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22170%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2215%22%20value%3D%22pkt_err_buf_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22200%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2216%22%20value%3D%22table_pop_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%220.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2217%22%20value%3D%22table_metadata_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22180.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2218%22%20value%3D%22buf_req_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22260%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2219%22%20value%3D%22buf_addr_i%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22290%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2220%22%20value%3D%22buf_gnt_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22320%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2221%22%20value%3D%22buf_data_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22540%22%20y%3D%22350%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2222%22%20value%3D%22buf_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22320%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2223%22%20value%3D%22buf_almost_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22350%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2224%22%20value%3D%22table_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22380%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2225%22%20value%3D%22table_almost_full_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22410%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2226%22%20value%3D%22empty_o%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20y%3D%22290%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2227%22%20value%3D%22%22%20style%3D%22shape%3DcurlyBracket%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Brounded%3D1%3BlabelPosition%3Dleft%3BverticalLabelPosition%3Dmiddle%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22450%22%20y%3D%2210%22%20width%3D%2220%22%20height%3D%22190%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2228%22%20value%3D%22%22%20style%3D%22shape%3DcurlyBracket%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Brounded%3D1%3BlabelPosition%3Dleft%3BverticalLabelPosition%3Dmiddle%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22450%22%20y%3D%22270%22%20width%3D%2220%22%20height%3D%22100%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2229%22%20value%3D%22%22%20style%3D%22shape%3DcurlyBracket%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Brounded%3D1%3BflipH%3D1%3BlabelPosition%3Dright%3BverticalLabelPosition%3Dmiddle%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22165%22%20y%3D%2210%22%20width%3D%2220%22%20height%3D%22210%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2230%22%20value%3D%22%22%20style%3D%22shape%3DcurlyBracket%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3Brounded%3D1%3BflipH%3D1%3BlabelPosition%3Dright%3BverticalLabelPosition%3Dmiddle%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22165%22%20y%3D%22290%22%20width%3D%2220%22%20height%3D%22140%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2231%22%20value%3D%22%22%20style%3D%22shape%3Dcylinder3%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BboundedLbl%3D1%3BbackgroundOutline%3D1%3Bsize%3D15%3Brounded%3D0%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22260%22%20y%3D%22190%22%20width%3D%2260%22%20height%3D%2280%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2232%22%20value%3D%22%22%20style%3D%22shape%3Dcylinder3%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BboundedLbl%3D1%3BbackgroundOutline%3D1%3Bsize%3D15%3Brounded%3D0%3Balign%3Dright%3BverticalAlign%3Dmiddle%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3BfillColor%3Dnone%3BgradientColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22330%22%20y%3D%22190%22%20width%3D%2260%22%20height%3D%2280%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2233%22%20value%3D%22Incoming%20packet%20interface%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22185%22%20y%3D%22100%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2234%22%20value%3D%22Table%20Memory%20Interface%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22390%22%20y%3D%2291.5%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2235%22%20value%3D%22Buffer%20Memory%20Interface%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22390%22%20y%3D%22305%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3CmxCell%20id%3D%2236%22%20value%3D%22Status%20Signals%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dcenter%3BverticalAlign%3Dmiddle%3BwhiteSpace%3Dwrap%3Brounded%3D0%3BfontFamily%3DCourier%20New%3BfontSize%3D12%3BfontColor%3Ddefault%3B%22%20vertex%3D%221%22%20parent%3D%222%22%3E%3CmxGeometry%20x%3D%22190%22%20y%3D%22345%22%20width%3D%2260%22%20height%3D%2230%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +A dedicated testbench exists for this module which comapres its behaviour with +a Python model. See README.md for more details. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +// Ring buffer + descriptor table for capturing an AXI-Stream of packets +// Has its own internal memories which are accessible via two memory interfaces +axis_pkt_ring_buffer #( + .PACKET_INDEX_W (), // allow 2^PACKET_INDEX_W packets in the buffer + .BUF_ADDR_W (), // allow 2^BUF_ADDR_W bytes in the buffer + .MAX_PKT_LEN (), // maximum size of one packet + .BufAlmostFullThreshold (), // recommended value is the maximum size of one packet, as above + .TableAlmostFullThreshold (), // number of free entries in the descriptor table before we assert table_almost_full_o + .metadata_t () // type for the metadata associated with each packet +) ring_buffer_inst ( + .clk_i, + .rst_ni, + + // Packet input stream + .pkt_tdata_i (), + .pkt_tvalid_i (), + .pkt_tlast_i (), + + .pkt_metadata_i (), + .pkt_abandon_i (), + + // Descriptor table access + .table_req_i (), + .table_index_i (), + .table_gnt_o (), + .table_err_o (), + .table_metadata_o (), + .table_ptr_o (), + .table_pkt_len_o (), + + .table_pop_i (), + + // Data buffer access + .buf_req_i (), + .buf_addr_i (), + .buf_err_o (), + .buf_data_o (), + .buf_gnt_o (), + + // Statuses + .empty_o (), + .packet_lost_o (), + .buf_almost_full_o (), + .table_full_o (), + .table_almost_full_o (), + .n_pkts_buffered_o () +); +*/ + +`include "prim_assert.sv" + +module axis_pkt_ring_buffer #( + parameter PACKET_INDEX_W = 3, // constraints maximum number of buffered packets to 2^n + parameter BUF_ADDR_W = 13, // width of the byte addresses for the buffer (constrains the maximum size of the buffer to 2^n bytes) + parameter MAX_PKT_LEN = 1522, // maximum length of a packet in bytes + + parameter BufAlmostFullThreshold = 1524, // how many free entries in the buffer before we assert buf_almost_full_o? + parameter TableAlmostFullThreshold = 2, // how many free entries in the descriptor table before we assert table_almost_full_o? + parameter type metadata_t = logic, + + localparam OUT_DATA_W = 64, // width of the data bus for reading out the buffer (must be a multiple of IN_DATA_W) + localparam PACKET_LEN_W = $clog2(MAX_PKT_LEN), // width of the packet length field in the descriptor table (constrains the maximum packet length to 2^n bytes) + localparam IN_DATA_W = 8, // only support one byte as input for now + localparam BUF_ADDR_LSBS = $clog2(OUT_DATA_W/IN_DATA_W), // the number of least significant bits in the buffer address which are not used in the read address because we are reading out more bits at a time than we are writing in (i.e. we are upsizing) + localparam type pkt_data_t = logic [IN_DATA_W-1:0], + localparam type bus_data_t = logic [OUT_DATA_W-1:0], + localparam type buffer_addr_t = logic [BUF_ADDR_W-1:0], + localparam type table_addr_t = logic [PACKET_INDEX_W-1:0], + localparam type pkt_len_t = logic [PACKET_LEN_W-1:0] +) ( + input logic clk_i, + input logic rst_ni, + + // Interface for capturing incoming packets and metadata + input pkt_data_t pkt_tdata_i, // Incoming data (AXI-Stream) + input logic pkt_tlast_i, // Indicates the end of a packet. Metadata will be captured and a new entry added to the descriptor table, UNLESS pkt_abandon_i was asserted any time during this packet (but not on the same cycle as tlast). + input logic pkt_tvalid_i, // indicate whether any of the above signals are valid + + input metadata_t pkt_metadata_i, // Captured when a packet is committed + input logic pkt_abandon_i, // When asserted, the current packet is abandoned and will not be commited. Valid when pkt_tvalid_i is asserted. + + // Interface for accessing the descriptor table (metadata + pointers to the buffer) + input logic table_pop_i, // When asserted, entry 0 is popped (regardless of the value of table_req_i!) + + input logic table_req_i, + input table_addr_t table_index_i, // addresses are relative -- use 0 for the oldest packet, 1 for second oldest, etc.. Normal behaviour is to always read 0, but you can peek ahead if you like + output logic table_gnt_o, + output logic table_err_o, // valid AFTER table_gnt_o is asserted. Indicates attempt to read bad entry in the descriptor table + output metadata_t table_metadata_o, // metadata read from the table (valid AFTER table_gnt_o) + output buffer_addr_t table_ptr_o, // pointer to the packet being read fro mthe table (valid table_gnt_o) + output pkt_len_t table_pkt_len_o, // length of the packet in bytes (valid after table_gnt_o) + + // Interface for accessing the buffer itself + input logic buf_req_i, + input buffer_addr_t buf_addr_i, + output logic buf_gnt_o, + output bus_data_t buf_data_o, // valid on the cycle FOLLOWING buf_gnt_o && buf_req_i + output logic buf_err_o, // valid on the cycle FOLLOWING buf_gnt_o && buf_req_i + + // Status/warning for the FIFOs + output logic empty_o, + output logic table_almost_full_o, + output logic table_full_o, + output logic buf_almost_full_o, + output logic packet_lost_o, // pulse for one cycle when a packet is lost (either due to full buffer or full table) + output logic [PACKET_INDEX_W+1-1:0] n_pkts_buffered_o // number of packets currently buffered in the descriptor table +); + ////////////////// + // Declarations // + ////////////////// + + // Declare pointers + buffer_addr_t buf_write_ptr; // points to the next location to write incoming data to + buffer_addr_t current_packet_head_ptr; // points to the start of the current packet being captured (updated at the start of each new packet) + + // Signals used in logic for abandoning packets + logic abandon_this_packet,abandon_sticky; // flag which is set when pkt_abandon_i is asserted and ignores the current packet + + logic buf_full; // buffer full + logic [BUF_ADDR_W-1:0] buffer_ptrs_margin; // the number of words IN BETWEEN (exclusive) the read and write pointers on the buffer + + logic buf_write; // write signal to the ring buffer + logic commit_pkt; // asserted at the end of a packet to commit it to the descriptor table + logic abandon_pkt; // asserted at the end of a packet to abandon it and not commit it to the descriptor table + + // Create the descriptor table and type + typedef struct packed { + logic [PACKET_LEN_W-1:0] pkt_len; // length of the packet in BYTES (not words!) + metadata_t metadata; + buffer_addr_t head_ptr; + } pkt_desc_t; + + // Packet descriptions for the current packet, oldest packet, and randomly-read packet descriptor + pkt_desc_t current_pkt_desc, oldest_packet_desc, oldest_packet_desc_raw, random_access_packet_desc; + + ////////////////////////// + // Packet abandon logic // + ////////////////////////// + + // The logic for abandoning packets is trickier than you might expect and took me a while to get right + // so please read carefully if you are debugging or modifying this code + // There are basically three reasons to abandon a packet: + // 1. The buffer was full at any point during the packet (while tvalid was asserted) + // 2. The external abandon signal was asserted at any point during the packet (while tvalid was asserted) + // 3. The descriptor table was full AT THE COMPLETION of this packet (it's okay if it was full at the start) + // and you need to make sure you handle the edge cases correctly + + // abandon_this_packet indicates that any of the three conditions for abandoning the packet are met + // abandon_this_packet is only valid with tvalid and tlast + assign abandon_this_packet = ( + abandon_sticky || // sticky behaviour - if already true, stay true until the packet ends + pkt_abandon_i || // external instruction to abandon the packet (bypassing the flop in case its asserted on the very last cycle) + buf_full || // buffer full when the packet ends (as above) + table_full_o // description table full when the packet ends (it's okay if it's full when the packet starts, there could be a pop in the middle of the packet) + ); + + assign packet_lost_o = ((buf_full && pkt_tvalid_i) || (table_full_o && pkt_tlast_i && pkt_tvalid_i)) && ~abandon_sticky; + // if the buffer was full at any point, if the table is full at the end of the packet, that packet will be lost + // This will PULSE FOR ONE CYCLE when the packet is lost i.e. losing a packet is an event + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) abandon_sticky <= 1'b0; + else if (pkt_tlast_i && pkt_tvalid_i) abandon_sticky <= 1'b0; // reset after a packet ends + else abandon_sticky <= (pkt_abandon_i && pkt_tvalid_i) || packet_lost_o || abandon_sticky; // set and stay set if pkt_abandon_i is asserted and valid + end + + ///////////////////////// + // Write pointer logic // + ///////////////////////// + + // Logic to check if the buffer is full + assign buf_full = !empty_o && (oldest_packet_desc.head_ptr == buf_write_ptr); + // if this goes high we should expect it to stay high for a while, because the write pointer will stop advancing once the buffer is full + + // Calculate remaining space in the buffer, excluding packet currently being written + // we exclude the current packet to avoid th buf_almost_full flag being asserted and then deasserted partway through if the packet gets dropped + assign buffer_ptrs_margin = (empty_o ? current_packet_head_ptr : oldest_packet_desc.head_ptr) - current_packet_head_ptr; // the number of words IN BETWEEN (exclusive) the read and write pointers on the buffer + assign buf_almost_full_o = !empty_o && (buffer_ptrs_margin <= BUF_ADDR_W'(BufAlmostFullThreshold)); + + assign buf_write = pkt_tvalid_i && ~buf_full && ~abandon_this_packet; + // we should write incoming data to the buffer whenever it is valid, UNLESS we have decided to abandon this packet, or the buffer is full + + assign commit_pkt = pkt_tvalid_i && pkt_tlast_i && ~abandon_this_packet; + assign abandon_pkt = pkt_tvalid_i && pkt_tlast_i && abandon_this_packet; + + // This simple module will advance the write pointer whenever valid_i is asserted + // and also track the head of the current packet. If 'abandon' is asserted, the write + // pointer moves back to the head of the packet. If 'commit' is asserted, the head + // of the current packet is updated to where the write pointer is. See inside + // for more details. + axis_pkt_ring_buffer_wr_ptr_logic #( + .PTR_W(BUF_ADDR_W) + ) buf_wr_ptr_logic ( + .clk_i, + .rst_ni, + .valid_i (buf_write || abandon_pkt), // only increment the pointer when we are actually WRITING to the buffer (or if we are abandoning the packet, we need valid_i asserted to reset the pointer) + .commit_i (commit_pkt), + .abandon_i (abandon_pkt), + .wr_ptr_o (buf_write_ptr), + .head_ptr_o (current_packet_head_ptr) + ); + + ////////////////////////////////// + // Declare the descriptor table // + ////////////////////////////////// + // Create a table of packet descriptors (metadata + pointer) with random read access and popping mechanism + // The contents of the descriptor table FIFO are not reset by anything + // This is a major hazard for verification + // To ensure no bugs, we should never use the data output of the FIFO while it is empty + // To enforce this, we will MUX is it with an X which should propagate everywhere in simulation + assign oldest_packet_desc = empty_o ? 'x : oldest_packet_desc_raw; + + + // The descriptor table is a random-access FIFO + // i.e. it has a "push" mechanism for writing to incrementing addressed + // and a "pop" mechanism for freeing up the oldest entry (like a conventional FIFO) + // but there is a second read interface which is addressable and allows arbitrary reads + // Read addresses are relative to the head of the queue, and attempting to read beyond the number of valid entries will result in an error + random_access_fifo #( + .dtype_t(pkt_desc_t), + .AlmostFullThreshold(TableAlmostFullThreshold), + .Depth(2**PACKET_INDEX_W) + ) description_table ( + .clk_i, + .rst_ni, + .clr_i(1'b0), // not used + + // Write IF for pushing new packets onto queue + .wr_push_i(commit_pkt), + .wr_data_i(current_pkt_desc), // should capture the incoming metadata, and pointer to start of packet + + // Read IF for popping packets off the queue + .rd_pop_i(table_pop_i), + .rd_head_data_o(oldest_packet_desc_raw), // should return the metadata, and pointer for the oldest packet in the queue + + // Random-access read interface + .rd_req_i(table_req_i), + .rd_gnt_o(table_gnt_o), + .rd_addr_i(table_index_i), + .rd_data_o(random_access_packet_desc), + .rd_err_o(table_err_o), + + // FIFO status + .full_o(table_full_o), + .almost_full_o(table_almost_full_o), + .n_buffered_o(n_pkts_buffered_o), // number of packets currently buffered in the descriptor table + .empty_o(empty_o) + ); + logic unused; + assign unused = ^{oldest_packet_desc.metadata,oldest_packet_desc.pkt_len}; // we only need the head_ptr of the oldest packet but the FIFO exposes all of it. Assigning to 'unused' waives lint warnings + + assign current_pkt_desc.metadata = pkt_metadata_i; // incoming metadata (only valid on tlast) + assign current_pkt_desc.head_ptr = current_packet_head_ptr; + assign current_pkt_desc.pkt_len = pkt_len_t'(buf_write_ptr + 1'b1 - current_packet_head_ptr); // this relies on unsigned overflow to work correctly + + // random access read interface: + assign table_metadata_o = random_access_packet_desc.metadata; + assign table_ptr_o = random_access_packet_desc.head_ptr; + assign table_pkt_len_o = random_access_packet_desc.pkt_len; + + //////////////////////////// + // Create the ring buffer // + //////////////////////////// + ram_upsizer_w8_r64 #( + .RD_ADDR_W (BUF_ADDR_W-BUF_ADDR_LSBS) // the read address is narrower because we are reading out more bits at a time than we are writing in (i.e. we are upsizing) + ) ram_upsizer_w8_r64_inst ( + .clk_i, + .rst_ni, + + // Write interface + .wr_en_i (buf_write), + .wr_addr_i (buf_write_ptr), + .wr_data_i (pkt_tdata_i), + + // Read interface + .rd_en_i (buf_req_i), + .rd_addr_i (buf_addr_i[BUF_ADDR_W-1:BUF_ADDR_LSBS]), // trim the LSBs off the address for reading because the RAM is word addressed, not byte addressed + .rd_data_o (buf_data_o) + ); + + // Read logic + assign buf_gnt_o = buf_req_i; // grant immediately (protocol expects data on the following cycle as below) + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + buf_err_o <= 1'b0; + end else + if (buf_req_i) begin + buf_err_o <= buf_addr_i[BUF_ADDR_LSBS-1:0] != '0; // error for unaligned accesses + end + end + + //////////////// + // Assertions // + //////////////// + // Assert all outputs are known + `ASSERT_KNOWN(TableGntO_A, table_gnt_o); + `ASSERT_KNOWN_IF(TableErrO_A, table_err_o, $past(table_req_i && table_gnt_o)); + `ASSERT_KNOWN_IF(TableMetadataO_A, table_metadata_o, $past(table_req_i && table_gnt_o)); + `ASSERT_KNOWN_IF(TablePtrO_A, table_ptr_o, $past(table_req_i && table_gnt_o)); + + `ASSERT_KNOWN(BufGntO_A, buf_gnt_o); + `ASSERT_KNOWN_IF(BufDataO_A, buf_data_o, $past(buf_req_i && buf_gnt_o)); + `ASSERT_KNOWN_IF(BufErrO_A, buf_err_o, $past(buf_req_i && buf_gnt_o)); + + `ASSERT_KNOWN(EmptyO_A, empty_o); + `ASSERT_KNOWN(BufAlmostFullO_A, buf_almost_full_o); + `ASSERT_KNOWN(TableFullO_A, table_full_o); + `ASSERT_KNOWN(TableAlmostFullO_A, table_almost_full_o); + `ASSERT_KNOWN(PacketLostO_A, packet_lost_o); + `ASSERT_KNOWN(NPktsBufferedO_A, n_pkts_buffered_o); + + // Some basic assertions about behaviour + // cannot be both empty and full/nearly full + `ASSERT(NotEmptyAndTableFull_A, ~(empty_o && table_full_o)); + `ASSERT(NotEmptyAndTableAlmostFull_A, ~(empty_o && table_almost_full_o)); + `ASSERT(NotEmptyAndBufferAlmostFull_A, ~(empty_o && buf_almost_full_o)); + + // 'almost full' is a subset of 'full' + `ASSERT(BufAlmostFullWhenFull_A, buf_full -> buf_almost_full_o); + `ASSERT(TableAlmostFullWhenFull_A, table_full_o -> table_almost_full_o); + + // gnt given immediately on both interfaces + `ASSERT(BufGntImmediately_A, buf_req_i -> buf_gnt_o); + `ASSERT(TableGntImmediately_A, table_req_i -> table_gnt_o); + + `ASSERT(NPktsDecreaseWithoutPop_A, !table_pop_i |=> n_pkts_buffered_o >= $past(n_pkts_buffered_o)); // cannot decrease the number of packets buffered without popping an entry from the descriptor table + `ASSERT(NPktsIncreaseWithoutLast_A, !pkt_tlast_i |=> n_pkts_buffered_o <= $past(n_pkts_buffered_o)); // cannot increase the number of packets buffered without finishing a packet + + // values during reset + `ASSERT(EmptyDuringReset_A, !rst_ni |-> empty_o, clk_i, 0); + `ASSERT(TableNotFullDuringReset_A, !rst_ni |-> !table_full_o, clk_i, 0); + `ASSERT(TableNotAlmostFullDuringReset_A, !rst_ni |-> !table_almost_full_o, clk_i, 0); + `ASSERT(BufNotAlmostFullDuringReset_A, !rst_ni |-> !buf_almost_full_o, clk_i, 0); + `ASSERT(NPktsZeroDuringReset_A, !rst_ni |-> n_pkts_buffered_o == '0, clk_i, 0); + `ASSERT(PacketLostNotDuringReset_A, !rst_ni |-> !packet_lost_o, clk_i, 0); + + // values immediately after reset + `ASSERT(EmptyAfterReset_A, !rst_ni |=> empty_o, clk_i, 0); + `ASSERT(TableNotFullAfterReset_A, !rst_ni |=> !table_full_o, clk_i, 0); + `ASSERT(TableNotAlmostFullAfterReset_A, !rst_ni |=> !table_almost_full_o, clk_i, 0); + `ASSERT(BufNotAlmostFullAfterReset_A, !rst_ni |=> !buf_almost_full_o, clk_i, 0); + `ASSERT(NPktsZeroAfterReset_A, !rst_ni |=> n_pkts_buffered_o == '0, clk_i, 0); + `ASSERT(PacketLostNotAfterReset_A, !rst_ni |=> !packet_lost_o, clk_i, 0); + // ^ means we have just left reset ^clock ^ should be a reset condition which disables the assertion, but we want these assertions to hold regardless of the reset +endmodule diff --git a/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer_wr_ptr_logic.sv b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer_wr_ptr_logic.sv new file mode 100644 index 000000000..3e50c849e --- /dev/null +++ b/hw/vendor/ethernet/hw/util/axis_pkt_ring_buffer/axis_pkt_ring_buffer_wr_ptr_logic.sv @@ -0,0 +1,96 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: axis_pkt_ring_buffer_wr_ptr_logic +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Simple write pointer logic with signals 'valid', 'commit', 'abandon'. Tracks a +write pointer and a head pointer (which points to the start of the current +packet being captured). + +* When 'valid' is asserted alone, the write pointer is incremented. +* When 'valid' and 'abandon' are asserted, the write pointer moves back to the head pointer +* When 'valid' and 'commit' are asserted, the head pointer update to the current value of the write pointer, then the write pointer increments. + +'valid', 'abandon', and 'commit' should never be asserted all together. + +Write pointer will simply wrap when it saturates. + +DOES NOT: + - Does not actually handle any data - this is purely for write pointer logic, the memory and associated datapath should be elsewhere + - Does not detect buffer overflows - you are responsible for comparing wr_ptr_o with a 'free pointer' or similar to detect overflows + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module but it is exercised by the +axis_pkt_ring_buffer testbench. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +// This simple module will advance the write pointer whenever valid_i is asserted +// and also track the head of the current packet. If 'abandon' is asserted, the write +// pointer moves back to the head of the packet. If 'commit' is asserted, the head +// of the current packet is updated to where the write pointer is. See inside +// for more details. +axis_pkt_ring_buffer_wr_ptr_logic #( + .PTR_W() +) buf_wr_ptr_logic ( + .clk_i, + .rst_ni, + .valid_i (), // increments write pointer + .commit_i (), // valid with valid_i, causes head pointer to be updated to write pointer + .abandon_i (), // valid with valid_i, causes the write pointer to revert to the head pointer + .wr_ptr_o (), // next location to write to + .head_ptr_o () // head of current packet +); +*/ + +`include "prim_assert.sv" // for ASSERT and ASSERT_KNOWN macros + +module axis_pkt_ring_buffer_wr_ptr_logic #( + parameter PTR_W = 17, // pointer width + localparam type ptr_t = logic [PTR_W-1:0] // pointer type +) ( + input clk_i, // clock + input rst_ni, // active-low asynchronous reset + + // A packet is ended by 'valid' being asserted with either 'abandon' or 'commit' + input valid_i, // indicates data was written this cycle, so the write pointer should advance + input commit_i, // indicates data was written this cycle and was the last piece of data in this packet (write pointer will advanced, and head pointer will be updated) + input abandon_i, // valid when valid_i is asserted. Indicates this packet should be abandoned. wr_ptr_o will revert to the value of head_ptr_o + + output ptr_t wr_ptr_o, // write pointer, points to the next location to write to + output ptr_t head_ptr_o // head pointer, points to the start of the current packet (updated by commit_i && valid_i) +); + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + wr_ptr_o <= '0; + end else if (valid_i) begin + if (abandon_i) wr_ptr_o <= head_ptr_o; + else wr_ptr_o <= wr_ptr_o + 1; + end + end + + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + head_ptr_o <= '0; + end else begin + if (commit_i && valid_i) head_ptr_o <= wr_ptr_o + 1; // at the start of a new packet, update the head pointer to the current write pointer (which points to the next location to write to, so effectively the end of the previous packet) + end + end + + //////////////// + // Assertions // + //////////////// + `ASSERT_KNOWN(WrPtrO_A, wr_ptr_o); + `ASSERT_KNOWN(HeadPtrO_A, head_ptr_o); + + `ASSERT(WrPtrLogicInputsGood_A, ~(valid_i && commit_i && abandon_i), "valid, commit, and abandon should never be asserted all together"); +endmodule diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/bus.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/bus.sv new file mode 100644 index 000000000..0111fa1c7 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/bus.sv @@ -0,0 +1,194 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: bus +Author: Taken from lowRISC Ibex + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Simple memory interface crossbar. +Known issue: does not support wait states. This is fine for now. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- + bus #( + .NrDevices (), + .NrHosts (), + .DataWidth (), + .AddressWidth () + ) bus_inst ( + .clk_i, + .rst_ni, + + .host_req_i (), + .host_addr_i (), + .host_we_i (), + .host_be_i (), + .host_wdata_i (), + .host_rvalid_o (), + .host_gnt_o (), + .host_rdata_o (), + .host_err_o (), + + .device_req_o (), + .device_addr_o (), + .device_we_o (), + .device_be_o (), + .device_wdata_o (), + .device_gnt_i (), + .device_rdata_i (), + .device_err_i (), + + // Example memory map definition for 4 devices is shown below + // CSR Address space \/ TX buffer address space \/ RX buffer address space \/ RX metadata address space \/ + .cfg_device_addr_base ({ 64'h0000_0000_0000_0800 , 64'h0000_0000_0000_1000 , 64'h0000_0000_0000_4000 , 64'h0000_0000_0000_6000 }), + .cfg_device_addr_mask ({~(64'h0000_0000_0000_007F) , ~(64'h0000_0000_0000_07FF) , ~(64'h0000_0000_0000_1FFF) , ~(64'h0000_0000_0000_003F) }), + .device_rvalid_i () + ); + +*/ + +// TODO fix this bus: +// - support pipelining +// - support wait states +// - remove option for multiple hosts + +`include "prim_assert.sv" + +module bus #( + parameter int NrDevices = 1, + parameter int NrHosts = 1, + parameter int DataWidth = 32, + parameter int AddressWidth = 32 +) ( + input clk_i, + input rst_ni, + + // Hosts + input host_req_i [NrHosts], + output logic host_gnt_o [NrHosts], + + input [AddressWidth-1:0] host_addr_i [NrHosts], + input host_we_i [NrHosts], + input [ DataWidth/8-1:0] host_be_i [NrHosts], + input [ DataWidth-1:0] host_wdata_i [NrHosts], + output logic host_rvalid_o [NrHosts], + output logic [ DataWidth-1:0] host_rdata_o [NrHosts], + output logic host_err_o [NrHosts], + + // Devices + output logic device_req_o [NrDevices], + + output logic [AddressWidth-1:0] device_addr_o [NrDevices], + output logic device_we_o [NrDevices], + output logic [ DataWidth/8-1:0] device_be_o [NrDevices], + output logic [ DataWidth-1:0] device_wdata_o [NrDevices], + input device_rvalid_i [NrDevices], + input [ DataWidth-1:0] device_rdata_i [NrDevices], + input device_gnt_i [NrDevices], + input device_err_i [NrDevices], + + // Device address map + input [AddressWidth-1:0] cfg_device_addr_base [NrDevices], + input [AddressWidth-1:0] cfg_device_addr_mask [NrDevices] +); + + localparam int unsigned NumBitsHostSel = NrHosts > 1 ? $clog2(NrHosts) : 1; + localparam int unsigned NumBitsDeviceSel = NrDevices > 1 ? $clog2(NrDevices) : 1; + + // Wait states are currently not supported + for (genvar i = 0; i < NrDevices; i++) begin : gen_device_gnt_check + logic unused; + assign unused = device_gnt_i[i]; + `ASSERT(BusNoWaitStates_A, ~device_req_o[i] | device_gnt_i[i]); + end + + logic host_sel_valid; + logic device_sel_valid; + logic decode_err_resp; + + logic [NumBitsHostSel-1:0] host_sel_req, host_sel_resp; + logic [NumBitsDeviceSel-1:0] device_sel_req, device_sel_resp; + + // Host select priority arbiter + always_comb begin + host_sel_valid = 1'b0; + host_sel_req = '0; + for (integer host = NrHosts - 1; host >= 0; host = host - 1) begin + if (host_req_i[host]) begin + host_sel_valid = 1'b1; + host_sel_req = NumBitsHostSel'(host); + end + end + end + + // Device select + always_comb begin + device_sel_valid = 1'b0; + device_sel_req = '0; + for (integer device = 0; device < NrDevices; device = device + 1) begin + if ((host_addr_i[host_sel_req] & cfg_device_addr_mask[device]) + == cfg_device_addr_base[device]) begin + device_sel_valid = 1'b1; + device_sel_req = NumBitsDeviceSel'(device); + end + end + end + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + host_sel_resp <= '0; + device_sel_resp <= '0; + decode_err_resp <= 1'b0; + end else begin + // Responses are always expected 1 cycle after the request + device_sel_resp <= device_sel_req; + host_sel_resp <= host_sel_req; + // Decode failed; no device matched? + decode_err_resp <= host_sel_valid & !device_sel_valid; + end + end + + always_comb begin + for (integer device = 0; device < NrDevices; device = device + 1) begin + if (device_sel_valid && NumBitsDeviceSel'(device) == device_sel_req) begin + device_req_o[device] = host_req_i[host_sel_req]; + device_we_o[device] = host_we_i[host_sel_req]; + device_addr_o[device] = host_addr_i[host_sel_req]; + device_wdata_o[device] = host_wdata_i[host_sel_req]; + device_be_o[device] = host_be_i[host_sel_req]; + end else begin + device_req_o[device] = 1'b0; + device_we_o[device] = 1'b0; + device_addr_o[device] = 'b0; + device_wdata_o[device] = 'b0; + device_be_o[device] = 'b0; + end + end + end + + always_comb begin + for (integer host = 0; host < NrHosts; host = host + 1) begin + host_gnt_o[host] = 1'b0; + if (NumBitsHostSel'(host) == host_sel_resp) begin + host_rvalid_o[host] = device_rvalid_i[device_sel_resp] | decode_err_resp; + host_err_o[host] = device_err_i[device_sel_resp] | decode_err_resp; + host_rdata_o[host] = device_rdata_i[device_sel_resp]; + end else begin + host_rvalid_o[host] = 1'b0; + host_err_o[host] = 1'b0; + host_rdata_o[host] = 'b0; + end + end + host_gnt_o[host_sel_req] = host_req_i[host_sel_req]; + end +endmodule diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_ro_mem.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_ro_mem.sv new file mode 100644 index 000000000..c2e610d96 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_ro_mem.sv @@ -0,0 +1,75 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mem_to_ro_mem +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Takes in a mem_if_utils_pkg:: memory interface and forwards it to a downstream +memory interface, but blocks any write requests and responds to them +immediately with an error response. Negligible hardware is used. + +A lot of input signals are ignored and outputs tied to constants, so expect to +see considerable constant propagation which may substantially optimise +neighbouring logic. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +mem_to_ro_mem u_mem_to_ro_mem ( + .clk_i, + .rst_ni, + .mem_req_i(), + .mem_rsp_o(), + .mem_req_o(), + .mem_rsp_i() +); + +*/ + +module mem_to_ro_mem #( + parameter RESPONSE_TO_WRITE = 64'hBADD_BADD_BADD_BADD // what to put on rdata in response to a write transaction. Usually ignored but can be used to supply an error code to the host +) ( + input clk_i, + input rst_ni, + + // incoming requests + input mem_if_utils_pkg::mem_req_t mem_req_i, + output mem_if_utils_pkg::mem_rsp_t mem_rsp_o, + + // forwarded request, with writes blocked + output mem_if_utils_pkg::mem_req_t mem_req_o, + input mem_if_utils_pkg::mem_rsp_t mem_rsp_i +); + logic unused; + assign unused = ^{mem_req_i.data,mem_req_i.be}; + + assign mem_req_o.we = 1'b0; // block all writes + assign mem_req_o.data = '0; // wdata is irrelevant for a read-only memory interface + assign mem_req_o.be = '0; // wstrb is irrelevant for a read-only memory interface + + assign mem_req_o.addr = mem_req_i.addr; + + // For write requests, hide them from the downstream memory and respond immediately here + assign mem_req_o.req = mem_req_i.we ? 1'b0 : mem_req_i.req; + assign mem_rsp_o.gnt = mem_req_i.we ? 1'b1 : mem_rsp_i.gnt; + + // The response lags the grant by a cycle (https://github.com/pulp-platform/hwpe-doc/blob/master/protocols.rst#hwpe-mem) + logic was_err; + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) was_err <= '0; + else was_err <= mem_req_i.req && mem_req_i.we; // make a note if we are responding to an error + end + + assign mem_rsp_o.data = was_err ? RESPONSE_TO_WRITE : mem_rsp_i.data; // return the specified response for write requests, pass through data for read requests + assign mem_rsp_o.err = was_err ? 1'b1 : mem_rsp_i.err; // indicate an error for write requests, pass through err for read requests +endmodule diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_wo_mem.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_wo_mem.sv new file mode 100644 index 000000000..1193d83f3 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/filters/mem_to_wo_mem.sv @@ -0,0 +1,75 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mem_to_wo_mem +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Takes in a mem_if_utils_pkg:: memory interface and forwards it to a downstream +memory interface, but blocks any read requests and responds to them +immediately with an error response. Negligible hardware is used. + +A lot of input signals are ignored and outputs tied to constants, so expect to +see considerable constant propagation which may substantially optimise +neighbouring logic. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +mem_to_wo_mem u_mem_to_wo_mem ( + .clk_i(), + .rst_ni, + .mem_req_i(), + .mem_rsp_o(), + .mem_req_o(), + .mem_rsp_i() +); + +*/ + +module mem_to_wo_mem #( + parameter RESPONSE_TO_READ = 64'hBADD_BADD_BADD_BADD // what to put on rdata in response to a read transaction. Usually ignored but can be used to supply an error code to the host +) ( + input clk_i, + input rst_ni, + + // incoming requests + input mem_if_utils_pkg::mem_req_t mem_req_i, + output mem_if_utils_pkg::mem_rsp_t mem_rsp_o, + + // forwarded request, with reads blocked + output mem_if_utils_pkg::mem_req_t mem_req_o, + input mem_if_utils_pkg::mem_rsp_t mem_rsp_i +); + logic unused; + assign unused = ^{mem_rsp_i.data}; + + // For write requests, forward them to the output + assign mem_req_o.req = mem_req_i.req && mem_req_i.we; // only forward write requests, block read requests + assign mem_req_o.we = mem_req_i.we; + assign mem_req_o.data = mem_req_i.data; + assign mem_req_o.be = mem_req_i.be; + assign mem_req_o.addr = mem_req_i.addr; + + // For read requests, hide them from the downstream memory and respond immediately here + assign mem_rsp_o.gnt = mem_req_i.we ? mem_rsp_i.gnt : 1'b1; // grant reads immediately (but error), pass through grant for writes + + // The response lags the grant by a cycle (https://github.com/pulp-platform/hwpe-doc/blob/master/protocols.rst#hwpe-mem) + logic was_err; + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (!rst_ni) was_err <= '0; + else was_err <= mem_req_i.req && ~mem_req_i.we; // make a note if we are responding to an error (read attempt) + end + + assign mem_rsp_o.data = RESPONSE_TO_READ; + assign mem_rsp_o.err = was_err ? 1'b1 : mem_rsp_i.err; // indicate an error for write requests, pass through err for read requests +endmodule diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_assertions.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_assertions.sv new file mode 100644 index 000000000..129afa909 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_assertions.sv @@ -0,0 +1,63 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mem_if_assertions +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Assertions governing the behaviour of the memory interface defined in +mem_if_utils_pkg. Uses lowRISC prim_assert.sv for assertion macros. + +This module only contains assertions - it has not functional behaviour and +will not affect synthesis. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +N/A + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +// This module is assertion only, no logic +mem_if_assertions u_mem_if_assertions ( + .clk_i, + .rst_ni, + .mem_req_i(), + .mem_rsp_o() +); + +*/ + +`include "prim_assert.sv" + +module mem_if_assertions ( + input clk_i, // This signal may look unused, but the `ASSERT macro has clk_i and rst_ni as defaults hardcoded + input rst_ni, // This signal may look unused, but the `ASSERT macro has clk_i and rst_ni as defaults hardcoded + input mem_if_utils_pkg::mem_req_t mem_req_i, + input mem_if_utils_pkg::mem_rsp_t mem_rsp_o +); + logic unused; + assign unused = ^{clk_i,rst_ni,mem_req_i,mem_rsp_o}; // if `ASSERT is not used, these signals go nowhere after pre-processing, so we need to mark them as unused to avoid lint warnings + + // Once asserted, req should stay asserted until the handshake is complete + `ASSERT(MemIfReqValidStaysUntilRsp_A, mem_req_i.req && !mem_rsp_o.gnt |=> mem_req_i.req); + + // Once asserted, the request signals should stay stable until the handshake is complete + `ASSERT(MemIfReqInvariantUntilRsp_A, mem_req_i.req && !mem_rsp_o.gnt |=> $stable({mem_req_i.addr,mem_req_i.data,mem_req_i.we,mem_req_i.be})); + + // Assert that all signals are known when the protocol requires it + `ASSERT_KNOWN(MemIfReqKnown_A, mem_req_i.req) // req always known + `ASSERT_KNOWN(MemIfGntKnown_A, mem_rsp_o.gnt) // gnt always known + `ASSERT_KNOWN_IF(MemIfAddrKnown_A, mem_req_i.addr, mem_req_i.req) // addr known when req is asserted + `ASSERT_KNOWN_IF(MemIfWeKnown_A, mem_req_i.we, mem_req_i.req) // 'we' known when req is asserted + `ASSERT_KNOWN_IF(MemIfDataKnown_A, mem_req_i.data, mem_req_i.req && mem_req_i.we) // data known when req and we are asserted (though arguably data where req.be is not asserted can be undefined, it is unlikely to be relevant) + `ASSERT_KNOWN_IF(MemIfBeKnown_A, mem_req_i.be, mem_req_i.req && mem_req_i.we) // 'be' known when req and we are asserted + + `ASSERT_KNOWN_IF(MemIfRspErrValid_A, mem_rsp_o.err, $past(mem_req_i.req) && $past(mem_rsp_o.gnt)); + `ASSERT_KNOWN_IF(MemIfRspDataValid_A, mem_rsp_o.data, $past(mem_req_i.req) && $past(mem_rsp_o.gnt)); +endmodule diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils.core b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils.core new file mode 100644 index 000000000..336ada426 --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils.core @@ -0,0 +1,26 @@ +CAPI=2: + +# This is a FuseSoC .core file +# https://fusesoc.readthedocs.io/_/downloads/en/stable/pdf/ + +name: "lowrisc:ethernet:mem_if_utils" +description: "Utilities for simple memory interface" + +filesets: + rtl: + files: + - mem_if_utils_pkg.sv # Package with structs for memory IF request and response + - mem_if_assertions.sv # Module that takes a request and response struct both as inputs and asserts behavioural properties + - filters/mem_to_ro_mem.sv # Simple filter that passes through read requests, return 'err' for write requests + - filters/mem_to_wo_mem.sv # Simple filter that passes through write requests, return 'err' for read requests + - reshaping_ram/ram_downsizer_w64_r8.sv # RAM with a 64-bit write port and 8-bit read port + - reshaping_ram/ram_upsizer_w8_r64.sv # RAM with an 8-bit write port and 64-bit read port + - bus.sv # Simple bus for routing one memory interface to several downstream based on a memory map. WIP. + file_type: systemVerilogSource + depend: + - lowrisc:prim:assert:0.1 + +targets: + default: + filesets: + - rtl diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils_pkg.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils_pkg.sv new file mode 100644 index 000000000..62487588e --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/mem_if_utils_pkg.sv @@ -0,0 +1,61 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: mem_if_utils_pkg +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Package of types and constants for mem_if_utils modules. Defines a simple +memory interface with ready/gnt handshakes. See README.md for details including +sample waveforms and a protocol specification. + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +N/A + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +N/A + +*/ + +package mem_if_utils_pkg; + ////////////////////////////////// + // Main memory interface params // + ////////////////////////////////// + localparam ADDR_W = 64; + localparam DATA_W_BYTES = 8; + localparam DATA_W = 8*DATA_W_BYTES; + + ////////////////////////////////// + // Addressable memory interface // + ////////////////////////////////// + typedef struct packed { + logic req; // requests a transaction + logic we; // write enable + logic [DATA_W_BYTES-1:0] be; // byte enable (for writes) + logic [ADDR_W-1:0] addr; + logic [DATA_W-1:0] data; + } mem_req_t; + + typedef struct packed { + logic gnt; // transaction is accepted. Response data will be given the next cycle + logic [DATA_W-1:0] data; // read data + logic err; // error signal. Valid with rvalid. 'data' MAY be used to communicate error type + } mem_rsp_t; + + // Simple memory interface with ready/gnt handshakes + // 'err' and 'data' should be valid on the cycle FOLLOWING 'gnt && req' + // NOT on the same cycle as 'gnt' + // (This allows you to tie 'gnt' to 1 while still having pipelined single-cycle access) + + // 'be' is byte enables and is only meaningful for writes + // addresses should always be byte addresses + // The lower bits of the byte address are essentially ignored because 'be' is used for unaligned writes +endpackage diff --git a/hw/vendor/ethernet/hw/util/mem_if_utils/reshaping_ram/ram_downsizer_w64_r8.sv b/hw/vendor/ethernet/hw/util/mem_if_utils/reshaping_ram/ram_downsizer_w64_r8.sv new file mode 100644 index 000000000..14632a36d --- /dev/null +++ b/hw/vendor/ethernet/hw/util/mem_if_utils/reshaping_ram/ram_downsizer_w64_r8.sv @@ -0,0 +1,125 @@ +// Copyright lowRISC contributors (COSMIC project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/* +Module: ram_downsizer_w64_r8 +Author: Thomas O'Dea + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Creates a RAM with an 8-bit read port and 64-bit write port. + +If SYNTHESIS is defined by the preprocessor, then a Xilinx XPM is instantiated. +Otherwise, a simple packed logic is instantiated to model the memory. + +Read latency of 1 cycle (i.e. data is output on the next clock after the address +is provided). + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +ram_downsizer_w64_r8 #( + .WR_ADDR_W () +) ram_downsizer_w64_r8_inst ( + .clk_i, + .rst_ni, + + // Writes interface + .wr_en_i (), + .wr_addr_i (), + .wr_data_i (), + + // Read interface + .rd_en_i (), + .rd_addr_i (), + .rd_data_o () +); + +*/ + +module ram_downsizer_w64_r8 #( + parameter WR_ADDR_W = 8, + localparam RD_ADDR_W = WR_ADDR_W + 3, // 8x more addresses for reads than writes (i.e. 64/8 = 8) + localparam MEM_SIZE = (1< + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +Creates a RAM with an 8-bit write port and 64-bit read port. + +If SYNTHESIS is defined by the preprocessor, then a Xilinx XPM is instantiated. +Otherwise, a simple packed logic is instantiated to model the memory. + +Read latency of 1 cycle (i.e. data is output on the next clock after the address +is provided). + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +ram_upsizer_w8_r64 #( + .RD_ADDR_W () +) ram_upsizer_w8_r64_inst ( + .clk_i, + .rst_ni, + + // Write interface + .wr_en_i (), + .wr_addr_i (), + .wr_data_i (), + + // Read interface + .rd_en_i (), + .rd_addr_i (), + .rd_data_o () +); + +*/ + +module ram_upsizer_w8_r64 #( + parameter RD_ADDR_W = 8, + localparam WR_ADDR_W = RD_ADDR_W + 3, // 8x more addresses for reads than writes (i.e. 64/8 = 8) + localparam MEM_SIZE = (1< + +------------------------------------------------------------------------------- + DESCRIPTION +------------------------------------------------------------------------------- +FIFO with typical push/pop interface, but also a random-access read interface. +You may only pop the oldest entry but can read any entry. +Read 'addresses' are indices relative to the head of the FIFO i.e. reading 0 +will read the oldest entry, reading 1 will read the second-oldest entry, etc. + +The random-access interface behaves according to mem_if_utils_pkg. + +Includes its own memory for the FIFO storage as a packed array of type dtype_t. +Therefore it MAY not be suitable for large FIFOs if your synthesis tool cannot +infer RAM from the packet array (however, most synthesis tools can do this). + +------------------------------------------------------------------------------- + TESTS +------------------------------------------------------------------------------- +There are no dedicated tests for this module yet. +TODO write tests and assertions for this module. + +------------------------------------------------------------------------------- + INSTANTIATION TEMPLATE +------------------------------------------------------------------------------- +random_access_fifo #( + .dtype_t (), + .AlmostFullThreshold (), + .Depth () +) description_table ( + .clk_i, + .rst_ni, + .clr_i (1'b0), // optional + + // Write IF for pushing onto queue + .wr_push_i (), + .wr_data_i (), + + // Read IF for popping off the queue + .rd_pop_i (), + .rd_head_data_o (), + + // Random-access read interface + .rd_req_i (), + .rd_gnt_o (), + .rd_addr_i (), + .rd_data_o (), + .rd_err_o (), + + // FIFO status + .full_o (), + .almost_full_o (), + .n_buffered_o (), + .empty_o () +); +*/ + +`include "prim_assert.sv" + +module random_access_fifo #( + parameter type dtype_t = logic, + parameter int unsigned AlmostFullThreshold = 2, // number of free entries at which the almost_full_o flag is raised (must be less than Depth) + parameter int unsigned Depth = 8, // maximum number of entries that can be buffered in the FIFO (must be a power of 2) + parameter bit NeverClears = 1'b0, // if set, the clr_i port is never high + parameter bit Secure = 1'b0, // use prim count for pointers + // derived parameters + localparam int DepthW = prim_util_pkg::vbits(Depth+1), + localparam int AddrW = prim_util_pkg::vbits(Depth) +) ( + input clk_i, + input rst_ni, + input clr_i, // synchronous clear / flush port + + // FIFO write interface + input logic wr_push_i, // when asserted, push onto the FIFO queue + input dtype_t wr_data_i, // data to push onto FIFO + + // FIFO read interface + input logic rd_pop_i, // when asserted, pop the head of the FIFO + output dtype_t rd_head_data_o, // data at the head of the FIFO (i.e. the next to be popped) + + // Random-access read interface + input logic rd_req_i, + output logic rd_gnt_o, + input logic [AddrW-1:0] rd_addr_i, // address for random access read (relative to the head of the FIFO) + output dtype_t rd_data_o, + output logic rd_err_o, // indicates an attempt to read from bad address (i.e. beyond the number of buffered entries) + + // Statuses + output full_o, + output almost_full_o, + output [DepthW-1:0] n_buffered_o, // number of entries currently buffered in the FIFO + output empty_o +); + // Normal FIFO construction + localparam int unsigned PtrW = prim_util_pkg::vbits(Depth); + + dtype_t [Depth-1:0] fifo_storage; // create main FIFO storage + logic [PtrW-1:0] fifo_wptr, fifo_rptr; // create read and write pointers + logic fifo_incr_wptr, fifo_incr_rptr; // whether to increment each pointer this cycle + + /////////////////////// + // Reset status flag // + /////////////////////// + logic under_rst; + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + under_rst <= 1'b1; + end else if (under_rst) begin + under_rst <= ~under_rst; + end + end + + /////////////////// + // Pointer logic // + /////////////////// + + // prim_fifo_sync_cnt provides logic for incrementing read/write counters and detecting overflows + // it does NOT provide an actual FIFO (memory), only the pointer logic and conflict detection + + logic [DepthW-1:0] n_buffered; + assign n_buffered_o = n_buffered; + + prim_fifo_sync_cnt #( + .Depth(Depth), + .Secure(Secure), + .NeverClears(NeverClears) + ) u_fifo_cnt ( + .clk_i, + .rst_ni, + .clr_i, + .incr_wptr_i(fifo_incr_wptr), + .incr_rptr_i(fifo_incr_rptr), + .wptr_o(fifo_wptr), + .rptr_o(fifo_rptr), + .full_o, + .empty_o, + .depth_o(n_buffered), + .err_o() // not used here + ); + // "full" and "not ready for write" are two different concepts. + // The latter can be '0' when under reset, while the former is an indication that no more entries can be written. + assign fifo_incr_wptr = wr_push_i & ~full_o & ~under_rst; + assign fifo_incr_rptr = rd_pop_i & ~empty_o & ~under_rst; + + assign almost_full_o = (n_buffered >= (DepthW)'(Depth - AlmostFullThreshold)); + // |--- this is a constant, ---| + // |--- so no arithmetic will---| + // |--- be synthesised ---| + + ////////////////////// + // FIFO write logic // + ////////////////////// + always_ff @(posedge clk_i) begin + if (fifo_incr_wptr) begin + fifo_storage[fifo_wptr] <= wr_data_i; + end + end + + ///////////////////// + // FIFO read logic // + ///////////////////// + assign rd_head_data_o = fifo_storage[fifo_rptr]; + + ///////////////////////// + // Random-access reads // + ///////////////////////// + assign rd_gnt_o = rd_req_i; // grant immediately (protocol expects data on the following cycle as below) + always_ff @ (posedge clk_i or negedge rst_ni) begin + if (~rst_ni) begin + rd_data_o <= '0; + rd_err_o <= '0; + end else begin + if (rd_req_i) begin + rd_err_o <= (DepthW)'(rd_addr_i) >= n_buffered; // error if attempting to read beyond the number of buffered data + rd_data_o <= fifo_storage[fifo_rptr + rd_addr_i]; // return the data at the requested address (which is relative to the queue head) + end + end + end + + //////////////// + // Assertions // + //////////////// + `ASSERT(depthShallNotExceedParamDepth, !empty_o |-> n_buffered <= DepthW'(Depth)) + + if (NeverClears) begin : gen_never_clears + `ASSERT(NeverClears_A, !clr_i) + end + + ////////////////////////////////// + // Assertions outputs are known // + ////////////////////////////////// + + `ASSERT_KNOWN_IF(RdRandDataKnown_O, rd_data_o, rd_gnt_o) // it's fine if read data is undefined when rd_gnt_o is deasserted, so we use a condition for the assertion + `ASSERT_KNOWN_IF(RdHeadDataKnown_O, rd_head_data_o, ~empty_o) // head data should always be defined unless the FIFO is empty + + `ASSERT_KNOWN(RdGntKnown_A, rd_gnt_o ) + `ASSERT_KNOWN(RdErrKnown_A, rd_err_o ) + `ASSERT_KNOWN(FullKnown_A, full_o ) + `ASSERT_KNOWN(AlmostFullKnown_A, almost_full_o ) + `ASSERT_KNOWN(EmptyKnown, empty_o ) + + // TODO add assertion that pop will decrease depth + // and push will increase depth +endmodule diff --git a/hw/vendor/ethernet/rgmii_soc.sv b/hw/vendor/ethernet/rgmii_soc.sv deleted file mode 100644 index ffc4eade3..000000000 --- a/hw/vendor/ethernet/rgmii_soc.sv +++ /dev/null @@ -1,223 +0,0 @@ -/* - -Copyright (c) 2014-2018 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. - -based on fpga.v - -*/ - -// Language: Verilog 2001 - -/* - * RGMII top-level module - */ - -module rgmii_soc ( - // Internal 125 MHz clock - input clk_int, - input rst_int, - input clk90_int, - input clk_200_int, - - /* - * Ethernet: 1000BASE-T RGMII - */ - input wire phy_rx_clk, - input wire [3:0] phy_rxd, - input wire phy_rx_ctl, - output wire phy_tx_clk, - output wire [3:0] phy_txd, - output wire phy_tx_ctl, - output wire phy_reset_n, - input wire phy_int_n, - input wire phy_pme_n, - output wire mac_gmii_tx_en, - - /* - * AXI input - */ - input wire tx_axis_tvalid, - input wire tx_axis_tlast, - input wire [7:0] tx_axis_tdata, - output wire tx_axis_tready, - 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 rx_axis_tuser, - - /* - * Status - */ - - output wire [31:0] rx_fcs_reg, - output wire [31:0] tx_fcs_reg - -); - -// IODELAY elements for RGMII interface to PHY -wire [3:0] phy_rxd_delay; -wire phy_rx_ctl_delay; - -IDELAYCTRL -idelayctrl_inst -( - .REFCLK(clk_200_int), - .RST(rst_int), - .RDY() -); - -IDELAYE2 #( - .IDELAY_TYPE("FIXED") -) -phy_rxd_idelay_0 -( - .IDATAIN(phy_rxd[0]), - .DATAOUT(phy_rxd_delay[0]), - .DATAIN(1'b0), - .C(1'b0), - .CE(1'b0), - .INC(1'b0), - .CINVCTRL(1'b0), - .CNTVALUEIN(5'd0), - .CNTVALUEOUT(), - .LD(1'b0), - .LDPIPEEN(1'b0), - .REGRST(1'b0) -); - -IDELAYE2 #( - .IDELAY_TYPE("FIXED") -) -phy_rxd_idelay_1 -( - .IDATAIN(phy_rxd[1]), - .DATAOUT(phy_rxd_delay[1]), - .DATAIN(1'b0), - .C(1'b0), - .CE(1'b0), - .INC(1'b0), - .CINVCTRL(1'b0), - .CNTVALUEIN(5'd0), - .CNTVALUEOUT(), - .LD(1'b0), - .LDPIPEEN(1'b0), - .REGRST(1'b0) -); - -IDELAYE2 #( - .IDELAY_TYPE("FIXED") -) -phy_rxd_idelay_2 -( - .IDATAIN(phy_rxd[2]), - .DATAOUT(phy_rxd_delay[2]), - .DATAIN(1'b0), - .C(1'b0), - .CE(1'b0), - .INC(1'b0), - .CINVCTRL(1'b0), - .CNTVALUEIN(5'd0), - .CNTVALUEOUT(), - .LD(1'b0), - .LDPIPEEN(1'b0), - .REGRST(1'b0) -); - -IDELAYE2 #( - .IDELAY_TYPE("FIXED") -) -phy_rxd_idelay_3 -( - .IDATAIN(phy_rxd[3]), - .DATAOUT(phy_rxd_delay[3]), - .DATAIN(1'b0), - .C(1'b0), - .CE(1'b0), - .INC(1'b0), - .CINVCTRL(1'b0), - .CNTVALUEIN(5'd0), - .CNTVALUEOUT(), - .LD(1'b0), - .LDPIPEEN(1'b0), - .REGRST(1'b0) -); - -IDELAYE2 #( - .IDELAY_VALUE(0), - .IDELAY_TYPE("FIXED") -) -phy_rx_ctl_idelay -( - .IDATAIN(phy_rx_ctl), - .DATAOUT(phy_rx_ctl_delay), - .DATAIN(1'b0), - .C(1'b0), - .CE(1'b0), - .INC(1'b0), - .CINVCTRL(1'b0), - .CNTVALUEIN(5'd0), - .CNTVALUEOUT(), - .LD(1'b0), - .LDPIPEEN(1'b0), - .REGRST(1'b0) -); - -rgmii_core -core_inst ( - /* - * Clock: 125MHz - * Synchronous reset - */ - .clk(clk_int), - .clk90(clk90_int), - .rst(rst_int), - /* - * Ethernet: 1000BASE-T RGMII - */ - .phy_rx_clk(phy_rx_clk), - .phy_rxd(phy_rxd_delay), - .phy_rx_ctl(phy_rx_ctl_delay), - .phy_tx_clk(phy_tx_clk), - .phy_txd(phy_txd), - .phy_tx_ctl(phy_tx_ctl), - .phy_reset_n(phy_reset_n), - .phy_int_n(phy_int_n), - .phy_pme_n(phy_pme_n), - .mac_gmii_tx_en(mac_gmii_tx_en), - .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), - .rx_fcs_reg(rx_fcs_reg), - .tx_fcs_reg(tx_fcs_reg) -); - -endmodule diff --git a/sw/device/devicetree/mocha.S b/sw/device/devicetree/mocha.S index eef017702..34374687f 100644 --- a/sw/device/devicetree/mocha.S +++ b/sw/device/devicetree/mocha.S @@ -648,6 +648,103 @@ cpu0intc_end: .byte 0x00 .byte 0x97 .balign 4, 0 + .globl ethernet +ethernet: + /* FDT_BEGIN_NODE */ + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x01 + .asciz "ethernet@30000000" + .balign 4, 0 + /* FDT_PROP */ + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x03 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x0f + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x1b + .byte 0x6c + .byte 0x6f + .byte 0x77 + .byte 0x72 + .byte 0x69 + .byte 0x73 + .byte 0x63 + .byte 0x2c + .byte 0x65 + .byte 0x74 + .byte 0x68 + .byte 0x2d + .byte 0x76 + .byte 0x31 + .byte 0x0 + .balign 4, 0 + /* FDT_PROP */ + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x03 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x10 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x38 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x30 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x60 + .byte 0x40 + .balign 4, 0 + /* FDT_PROP */ + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x03 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x08 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x9e + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x01 + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x1f + .balign 4, 0 + /* FDT_END_NODE */ + .byte 0x00 + .byte 0x00 + .byte 0x00 + .byte 0x02 + .globl ethernet_end +ethernet_end: .globl gpio gpio: /* FDT_BEGIN_NODE */ @@ -739,7 +836,7 @@ gpio: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0x9e + .byte 0xb2 .balign 4, 0 /* FDT_PROP */ .byte 0x00 @@ -753,7 +850,7 @@ gpio: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xae + .byte 0xc2 .byte 0x00 .byte 0x00 .byte 0x00 @@ -873,7 +970,7 @@ gpio_end: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xb5 + .byte 0x9e .byte 0x00 .byte 0x00 .byte 0x00 @@ -981,7 +1078,7 @@ gpio_end: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xb5 + .byte 0x9e .byte 0x00 .byte 0x00 .byte 0x00 @@ -1231,7 +1328,7 @@ gpio_end: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xaf + .byte 0xc3 .byte 0x00 .byte 0x00 .byte 0x00 @@ -1429,7 +1526,7 @@ plic: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xb5 + .byte 0x9e .byte 0x00 .byte 0x00 .byte 0x00 @@ -1630,7 +1727,7 @@ plicsw: .byte 0x00 .byte 0x00 .byte 0x00 - .byte 0xb5 + .byte 0x9e .byte 0x00 .byte 0x00 .byte 0x00 @@ -1682,9 +1779,9 @@ _dt_strings_start: .asciz "#interrupt-cells" .asciz "phandle" .asciz "ranges" + .asciz "interrupts-extended" .asciz "gpio-controller" .asciz "ngpios" - .asciz "interrupts-extended" .asciz "tx-fifo-depth" .asciz "rx-fifo-depth" .asciz "num-cs" diff --git a/sw/device/devicetree/mocha.dts b/sw/device/devicetree/mocha.dts index 415e8b39f..9da5168c1 100644 --- a/sw/device/devicetree/mocha.dts +++ b/sw/device/devicetree/mocha.dts @@ -42,6 +42,12 @@ compatible = "simple-bus"; ranges; + ethernet: ethernet@30000000 { + compatible = "lowrisc,eth-v1"; + reg = <0x0 0x30000000 0x0 0x6040>; + interrupts-extended = <&plic 31>; // Ethernet is PLIC interrupt source 31 + }; + gpio: gpio@40000000 { compatible = "lowrisc,opentitan-gpio-v1"; reg = <0x0 0x40000000 0x0 0x48>; diff --git a/sw/device/examples/CMakeLists.txt b/sw/device/examples/CMakeLists.txt index 17fb1daba..d1dc520bb 100644 --- a/sw/device/examples/CMakeLists.txt +++ b/sw/device/examples/CMakeLists.txt @@ -73,9 +73,3 @@ target_compile_options(${NAME} PUBLIC ${CHERI_FLAGS}) target_link_options(${NAME} PUBLIC "-T${LDS_DIR}/mocha_sram.ld" "-L${LDS_DIR}") mocha_add_example(${NAME}) -set(NAME ethernet_example) -add_executable(${NAME} ethernet_example.c) -target_link_libraries(${NAME} hal_cheri startup_cheri runtime_cheri) -target_compile_options(${NAME} PUBLIC ${CHERI_FLAGS}) -target_link_options(${NAME} PUBLIC "-T${LDS_DIR}/mocha_sram.ld" "-L${LDS_DIR}") -mocha_add_example(${NAME}) diff --git a/sw/device/examples/ethernet_example.c b/sw/device/examples/ethernet_example.c deleted file mode 100644 index 254e5dc2d..000000000 --- a/sw/device/examples/ethernet_example.c +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright lowRISC contributors (COSMIC project). -// Licensed under the Apache License, Version 2.0, see LICENSE for details. -// SPDX-License-Identifier: Apache-2.0 - -#include "boot/trap.h" -#include "hal/mocha.h" -#include "runtime/print.h" - -int main(void) -{ - uart_t uart = mocha_system_uart(); - ethernet_t ethernet = mocha_system_ethernet(); - - uprintf(uart, "Ethernet example\n"); - - ethernet_init(ethernet); - - // Set MAC address to 9A:BC:12:34:56:78 - ethernet_mac_address_set(ethernet, 0x9ABC12345678); - - // Initialise Tx buffer - for (int i = 0; i < 1600 / 8; ++i) { - ethernet_tx_buffer_write64(ethernet, i, - (i & 0b100) == 0 ? 0x5555555500000000UL : 0xAAAAAAAAFFFFFFFFUL); - } - - uprintf(uart, "Sending packets\n"); - - // Wait until Tx is not busy - while (ethernet_tx_is_busy(ethernet)) { - } - - for (int i = 0; i < 2; ++i) { - // Write to Tx buffer - ethernet_tx_buffer_write64(ethernet, 0, 0x1122334455667788UL); - ethernet_tx_buffer_write64(ethernet, 1, 0x5A5A5A5AA5A5A5A5UL); - - // Send 64 byte packet - ethernet_tx_packet_send(ethernet, 64); - - // Wait until Tx is not busy - while (ethernet_tx_is_busy(ethernet)) { - } - - // Write to Tx buffer - ethernet_tx_buffer_write64(ethernet, 0, 0xFFEEDDCCBBAA9988UL); - ethernet_tx_buffer_write64(ethernet, 1, 0x5A5A5A5AA5A5FFEEUL); - - for (int j = 0; j < 3; ++j) { - // Send 1500 byte packet - ethernet_tx_packet_send(ethernet, 1500); - - // Wait until Tx is not busy - while (ethernet_tx_is_busy(ethernet)) { - } - } - - // Write to Tx buffer - ethernet_tx_buffer_write64(ethernet, 0, 0x0102030450607080UL); - ethernet_tx_buffer_write64(ethernet, 1, 0x5A5A5A5AA5A54455UL); - - // Send 100 byte packet - ethernet_tx_packet_send(ethernet, 100); - - // Wait until Tx is not busy - while (ethernet_tx_is_busy(ethernet)) { - } - } - - // Receive 100 packets - uprintf(uart, "Receiving packets\n"); - uint8_t firstbuf; - uint16_t len; - uint16_t len_words; - for (int i = 0; i < 100; ++i) { - // Wait until there is a received packet - while (!ethernet_rx_packet_pending(ethernet)) { - } - - // Get Rx buffer id (4 bits) - firstbuf = ethernet_rx_first_buffer_get(ethernet); - // Increment first buffer ID (4 bits) - firstbuf = (firstbuf + 1) & 0b1111; - - // Get packet length - // This includes an additional 4 bytes for the FCS - len = ethernet_rx_buffer_packet_length_get(ethernet, firstbuf & 0b111); - len_words = (len + sizeof(uint64_t) - 1) / sizeof(uint64_t); - - uprintf(uart, "Received packet: buf = 0x%x (0x%x), len = 0x%x\n", (uint32_t)firstbuf, - (uint32_t)(firstbuf & 0b111), (uint32_t)len); - - // Display packet content - uprintf(uart, "hex content ="); - for (uint32_t i = 0; i < (uint32_t)len_words; ++i) { - uprintf(uart, " %lx", ethernet_rx_buffer_read64(ethernet, firstbuf & 0b111, i)); - } - uprintf(uart, "\n\n"); - - // Update first buffer - ethernet_rx_first_buffer_set(ethernet, firstbuf); - } - - return 0; -} - -void _trap_handler(struct trap_registers *registers, struct trap_context *context) -{ - (void)registers; - (void)context; -} diff --git a/sw/device/lib/hal/autogen/ethernet.h b/sw/device/lib/hal/autogen/ethernet.h index ff4e72b1e..6c14a2bb3 100644 --- a/sw/device/lib/hal/autogen/ethernet.h +++ b/sw/device/lib/hal/autogen/ethernet.h @@ -8,266 +8,154 @@ #include #include -typedef struct [[gnu::aligned(4)]] { - uint32_t mac_addr_hi : 16; - uint32_t cooked : 1; - uint32_t loopback : 1; - uint32_t spare : 4; - uint32_t promiscuous : 1; - uint32_t irq_en : 1; - uint32_t : 8; -} ethernet_machi; +typedef enum [[clang::flag_enum]] ethernet_intr : uint32_t { + ethernet_intr_none = 0, + ethernet_intr_rx_not_empty = (1u << 0), + ethernet_intr_rx_table_almost_full = (1u << 1), + ethernet_intr_rx_table_full = (1u << 2), + ethernet_intr_rx_buf_almost_full = (1u << 3), + ethernet_intr_packet_lost = (1u << 4), + ethernet_intr_tx_done = (1u << 5), + ethernet_intr_manual_irq = (1u << 6), +} ethernet_intr; typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 5; - uint32_t frame_addr : 11; - uint32_t : 4; - uint32_t busy : 1; -} ethernet_tplr; + uint32_t set_manual_irq : 1; + uint32_t : 31; +} ethernet_intr_test; -typedef enum [[clang::flag_enum]] ethernet_mdioctrl : uint32_t { - ethernet_mdioctrl_none = 0, - ethernet_mdioctrl_mdioclk = (1u << 0), - ethernet_mdioctrl_mdioout = (1u << 1), - ethernet_mdioctrl_mdiooen = (1u << 2), - ethernet_mdioctrl_mdioin = (1u << 3), -} ethernet_mdioctrl; - -typedef struct [[gnu::aligned(4)]] { - uint32_t last_buf : 4; - uint32_t : 28; -} ethernet_rfcs; +typedef enum [[clang::flag_enum]] ethernet_ctrl : uint32_t { + ethernet_ctrl_none = 0, + ethernet_ctrl_promiscuous_mode = (1u << 0), + ethernet_ctrl_loopback = (1u << 1), +} ethernet_ctrl; typedef struct [[gnu::aligned(4)]] { - uint32_t first_buf : 4; - uint32_t next_buf : 4; - uint32_t last_buf : 4; - uint32_t rx_avail : 1; - uint32_t rx_irq : 1; - uint32_t : 18; -} ethernet_rsr; - -typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr0; - -typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr1; - -typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr2; + uint32_t rx_not_empty : 1; + uint32_t rx_table_almost_full : 1; + uint32_t rx_table_full : 1; + uint32_t rx_buf_almost_full : 1; + uint32_t packet_lost : 1; + uint32_t : 2; + uint32_t tx_busy : 1; + uint32_t n_packets_in_rx_buf : 4; + uint32_t : 20; +} ethernet_status; typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr3; - -typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr4; - -typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr5; + uint32_t mac_hi : 16; + uint32_t : 16; +} ethernet_machi; typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; + uint32_t tx_packet_len : 11; uint32_t : 21; -} ethernet_rplr6; +} ethernet_tx_ctrl; typedef struct [[gnu::aligned(4)]] { - uint32_t packet_len : 11; - uint32_t : 21; -} ethernet_rplr7; + uint32_t pop : 1; + uint32_t : 31; +} ethernet_rx_pop; + +typedef enum [[clang::flag_enum]] ethernet_mdio_ctrl : uint32_t { + ethernet_mdio_ctrl_none = 0, + ethernet_mdio_ctrl_mdio_clk = (1u << 0), + ethernet_mdio_ctrl_mdio_o = (1u << 1), + ethernet_mdio_ctrl_mdio_oen = (1u << 2), + ethernet_mdio_ctrl_mdio_i = (1u << 3), +} ethernet_mdio_ctrl; typedef volatile struct [[gnu::aligned(4)]] ethernet_memory_layout { const uint8_t __reserved0[0x800 - 0x0]; - /* ethernet.maclo (0x800) */ - uint32_t maclo; - - const uint8_t __reserved1[0x808 - 0x804]; - - /* ethernet.machi (0x808) */ - ethernet_machi machi; - - const uint8_t __reserved2[0x810 - 0x80c]; - - /* ethernet.tplr (0x810) */ - ethernet_tplr tplr; - - const uint8_t __reserved3[0x818 - 0x814]; - - /* ethernet.tfcs (0x818) */ - uint32_t tfcs; - - const uint8_t __reserved4[0x820 - 0x81c]; - - /* ethernet.mdioctrl (0x820) */ - ethernet_mdioctrl mdioctrl; - - const uint8_t __reserved5[0x828 - 0x824]; - - /* ethernet.rfcs (0x828) */ - ethernet_rfcs rfcs; - - const uint8_t __reserved6[0x830 - 0x82c]; - - /* ethernet.rsr (0x830) */ - ethernet_rsr rsr; - - const uint8_t __reserved7[0x840 - 0x834]; - - /* ethernet.rplr0 (0x840) */ - const ethernet_rplr0 rplr0; + /* ethernet.intr_state (0x800) */ + ethernet_intr intr_state; - const uint8_t __reserved8[0x848 - 0x844]; + /* ethernet.intr_mask (0x804) */ + ethernet_intr intr_mask; - /* ethernet.rplr1 (0x848) */ - const ethernet_rplr1 rplr1; + /* ethernet.intr_test (0x808) */ + ethernet_intr_test intr_test; - const uint8_t __reserved9[0x850 - 0x84c]; + const uint8_t __reserved1[0x810 - 0x80c]; - /* ethernet.rplr2 (0x850) */ - const ethernet_rplr2 rplr2; + /* ethernet.ctrl (0x810) */ + ethernet_ctrl ctrl; - const uint8_t __reserved10[0x858 - 0x854]; + /* ethernet.status (0x814) */ + const ethernet_status status; - /* ethernet.rplr3 (0x858) */ - const ethernet_rplr3 rplr3; - - const uint8_t __reserved11[0x860 - 0x85c]; - - /* ethernet.rplr4 (0x860) */ - const ethernet_rplr4 rplr4; - - const uint8_t __reserved12[0x868 - 0x864]; - - /* ethernet.rplr5 (0x868) */ - const ethernet_rplr5 rplr5; - - const uint8_t __reserved13[0x870 - 0x86c]; - - /* ethernet.rplr6 (0x870) */ - const ethernet_rplr6 rplr6; - - const uint8_t __reserved14[0x878 - 0x874]; - - /* ethernet.rplr7 (0x878) */ - const ethernet_rplr7 rplr7; - - const uint8_t __reserved15[0x1000 - 0x87c]; + /* ethernet.maclo (0x818) */ + uint32_t maclo; - /* ethernet.tx_buffer (0x1000-0x17f8) */ - uint64_t tx_buffer[256]; + /* ethernet.machi (0x81c) */ + ethernet_machi machi; - const uint8_t __reserved16[0x4000 - 0x1800]; + /* ethernet.tx_ctrl (0x820) */ + ethernet_tx_ctrl tx_ctrl; - /* ethernet.rx_buffer0 (0x4000-0x47f8) */ - uint64_t rx_buffer0[256]; + const uint8_t __reserved2[0x828 - 0x824]; - /* ethernet.rx_buffer1 (0x4800-0x4ff8) */ - uint64_t rx_buffer1[256]; + /* ethernet.rx_pop (0x828) */ + ethernet_rx_pop rx_pop; - /* ethernet.rx_buffer2 (0x5000-0x57f8) */ - uint64_t rx_buffer2[256]; + /* ethernet.mdio_ctrl (0x82c) */ + ethernet_mdio_ctrl mdio_ctrl; - /* ethernet.rx_buffer3 (0x5800-0x5ff8) */ - uint64_t rx_buffer3[256]; + const uint8_t __reserved3[0x1000 - 0x830]; - /* ethernet.rx_buffer4 (0x6000-0x67f8) */ - uint64_t rx_buffer4[256]; + /* ethernet.tx_data_buf (0x1000-0x17f8) */ + uint64_t tx_data_buf[256]; - /* ethernet.rx_buffer5 (0x6800-0x6ff8) */ - uint64_t rx_buffer5[256]; + const uint8_t __reserved4[0x4000 - 0x1800]; - /* ethernet.rx_buffer6 (0x7000-0x77f8) */ - uint64_t rx_buffer6[256]; + /* ethernet.rx_data_buf (0x4000-0x5ff8) */ + const uint64_t rx_data_buf[1024]; - /* ethernet.rx_buffer7 (0x7800-0x7ff8) */ - uint64_t rx_buffer7[256]; + /* ethernet.rx_desc_table (0x6000-0x6038) */ + const uint64_t rx_desc_table[8]; } *ethernet_t; -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, maclo) == 0x800ul, +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, intr_state) == 0x800ul, + "incorrect register intr_state offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, intr_mask) == 0x804ul, + "incorrect register intr_mask offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, intr_test) == 0x808ul, + "incorrect register intr_test offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, ctrl) == 0x810ul, + "incorrect register ctrl offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, status) == 0x814ul, + "incorrect register status offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, maclo) == 0x818ul, "incorrect register maclo offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, machi) == 0x808ul, +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, machi) == 0x81cul, "incorrect register machi offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, tplr) == 0x810ul, - "incorrect register tplr offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, tfcs) == 0x818ul, - "incorrect register tfcs offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, mdioctrl) == 0x820ul, - "incorrect register mdioctrl offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rfcs) == 0x828ul, - "incorrect register rfcs offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rsr) == 0x830ul, - "incorrect register rsr offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr0) == 0x840ul, - "incorrect register rplr0 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr1) == 0x848ul, - "incorrect register rplr1 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr2) == 0x850ul, - "incorrect register rplr2 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr3) == 0x858ul, - "incorrect register rplr3 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr4) == 0x860ul, - "incorrect register rplr4 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr5) == 0x868ul, - "incorrect register rplr5 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr6) == 0x870ul, - "incorrect register rplr6 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rplr7) == 0x878ul, - "incorrect register rplr7 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, tx_buffer) == 0x1000ul, - "incorrect register window tx_buffer offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer0) == 0x4000ul, - "incorrect register window rx_buffer0 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer1) == 0x4800ul, - "incorrect register window rx_buffer1 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer2) == 0x5000ul, - "incorrect register window rx_buffer2 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer3) == 0x5800ul, - "incorrect register window rx_buffer3 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer4) == 0x6000ul, - "incorrect register window rx_buffer4 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer5) == 0x6800ul, - "incorrect register window rx_buffer5 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer6) == 0x7000ul, - "incorrect register window rx_buffer6 offset"); -_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_buffer7) == 0x7800ul, - "incorrect register window rx_buffer7 offset"); - +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, tx_ctrl) == 0x820ul, + "incorrect register tx_ctrl offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_pop) == 0x828ul, + "incorrect register rx_pop offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, mdio_ctrl) == 0x82cul, + "incorrect register mdio_ctrl offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, tx_data_buf) == 0x1000ul, + "incorrect register window tx_data_buf offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_data_buf) == 0x4000ul, + "incorrect register window rx_data_buf offset"); +_Static_assert(__builtin_offsetof(struct ethernet_memory_layout, rx_desc_table) == 0x6000ul, + "incorrect register window rx_desc_table offset"); + +_Static_assert(sizeof(ethernet_intr) == sizeof(uint32_t), + "register type ethernet_intr is not register sized"); +_Static_assert(sizeof(ethernet_intr_test) == sizeof(uint32_t), + "register type ethernet_intr_test is not register sized"); +_Static_assert(sizeof(ethernet_ctrl) == sizeof(uint32_t), + "register type ethernet_ctrl is not register sized"); +_Static_assert(sizeof(ethernet_status) == sizeof(uint32_t), + "register type ethernet_status is not register sized"); _Static_assert(sizeof(ethernet_machi) == sizeof(uint32_t), "register type ethernet_machi is not register sized"); -_Static_assert(sizeof(ethernet_tplr) == sizeof(uint32_t), - "register type ethernet_tplr is not register sized"); -_Static_assert(sizeof(ethernet_mdioctrl) == sizeof(uint32_t), - "register type ethernet_mdioctrl is not register sized"); -_Static_assert(sizeof(ethernet_rfcs) == sizeof(uint32_t), - "register type ethernet_rfcs is not register sized"); -_Static_assert(sizeof(ethernet_rsr) == sizeof(uint32_t), - "register type ethernet_rsr is not register sized"); -_Static_assert(sizeof(ethernet_rplr0) == sizeof(uint32_t), - "register type ethernet_rplr0 is not register sized"); -_Static_assert(sizeof(ethernet_rplr1) == sizeof(uint32_t), - "register type ethernet_rplr1 is not register sized"); -_Static_assert(sizeof(ethernet_rplr2) == sizeof(uint32_t), - "register type ethernet_rplr2 is not register sized"); -_Static_assert(sizeof(ethernet_rplr3) == sizeof(uint32_t), - "register type ethernet_rplr3 is not register sized"); -_Static_assert(sizeof(ethernet_rplr4) == sizeof(uint32_t), - "register type ethernet_rplr4 is not register sized"); -_Static_assert(sizeof(ethernet_rplr5) == sizeof(uint32_t), - "register type ethernet_rplr5 is not register sized"); -_Static_assert(sizeof(ethernet_rplr6) == sizeof(uint32_t), - "register type ethernet_rplr6 is not register sized"); -_Static_assert(sizeof(ethernet_rplr7) == sizeof(uint32_t), - "register type ethernet_rplr7 is not register sized"); +_Static_assert(sizeof(ethernet_tx_ctrl) == sizeof(uint32_t), + "register type ethernet_tx_ctrl is not register sized"); +_Static_assert(sizeof(ethernet_rx_pop) == sizeof(uint32_t), + "register type ethernet_rx_pop is not register sized"); +_Static_assert(sizeof(ethernet_mdio_ctrl) == sizeof(uint32_t), + "register type ethernet_mdio_ctrl is not register sized"); diff --git a/sw/device/lib/hal/ethernet.c b/sw/device/lib/hal/ethernet.c index 605d2e4f3..3c69d936f 100644 --- a/sw/device/lib/hal/ethernet.c +++ b/sw/device/lib/hal/ethernet.c @@ -7,101 +7,476 @@ #include #include -void ethernet_mac_address_set(ethernet_t ethernet, uint64_t address) -{ - // Write lower 32 bits - DEV_WRITE(ethernet + ETHERNET_MACLO_REG, (uint32_t)address); +////////////////////////// +/// Interrupt behaviour // +////////////////////////// - // Write upper 16 bits - uint32_t reg = DEV_READ(ethernet + ETHERNET_MACHI_REG); - reg = reg & ~ETHERNET_MACHI_MACADDR_MASK; - reg = reg | (uint32_t)((address >> 32) & ETHERNET_MACHI_MACADDR_MASK); - DEV_WRITE(ethernet + ETHERNET_MACHI_REG, reg); +// Retrieve the interrupt state register (unmasked) +// which has flags indicating things like outstanding packets or completed transmissions +ethernet_intr ethernet_intr_state_get(ethernet_t ethernet) { + return VOLATILE_READ(ethernet->intr_state); } -uint64_t ethernet_mac_address_get(ethernet_t ethernet) -{ - // Read lower 32 bits - uint64_t lower = (uint64_t)DEV_READ(ethernet + ETHERNET_MACLO_REG); +// Retrieve the interrupt mask register (non-volatile register) +ethernet_intr ethernet_intr_mask_get(ethernet_t ethernet) { + return VOLATILE_READ(ethernet->intr_mask); +} + +// Set the interrupt mask register +void ethernet_intr_mask_set(ethernet_t ethernet, ethernet_intr intr_mask) { + VOLATILE_WRITE(ethernet->intr_mask, intr_mask); +} + +// Fire a test interrupt +void ethernet_test_intr_fire(ethernet_t ethernet) { + ethernet_intr_test wr_val = {.set_manual_irq = 1}; + VOLATILE_WRITE( ethernet->intr_test, wr_val ); +} + +////////////////////// +// Clear interrupts // +////////////////////// +// status-type interrupts cannot be cleared directly, you must service the underlying condition causing the status +// however event-type interrupts can be cleared as below +// failing to clear them will cause them to immediately re-fire, so you must clear them or mask them after servicing +// It is perfectly acceptable to leave the interrupt asserted if you are not interested in it and have masked it + +// Clear a test interrupt +void ethernet_test_intr_clear(ethernet_t ethernet) { + // write-one-to-clear the manual_irq bit in the interrupt state register (INTR_STATE) + VOLATILE_WRITE(ethernet->intr_state, ethernet_intr_manual_irq); +} + +// Clear a TX done interrupt +void ethernet_tx_done_intr_clear(ethernet_t ethernet) { + // write-one-to-clear the interrupt state register (INTR_STATE) + VOLATILE_WRITE(ethernet->intr_state, ethernet_intr_tx_done); +} + +// Clear a packet lost interrupt +void ethernet_packet_lost_intr_clear(ethernet_t ethernet) { + // write-one-to-clear the interrupt state register (INTR_STATE) + VOLATILE_WRITE(ethernet->intr_state, ethernet_intr_packet_lost); +} + +/////////////////////// +// Status and config // +/////////////////////// + +// Retrieve an ethernet status struct +ethernet_status ethernet_status_get(ethernet_t ethernet) { + return VOLATILE_READ(ethernet->status); +} + +// Set the mode: promiscuous or not, loopback or not +// When in loopback mode, ONLY TX packets will arrive on the RX path, so other traffic will be lost +// Only use loopback for testing +void ethernet_mode_set(ethernet_t ethernet, bool promiscuous_en, bool loopback_en) { + ethernet_ctrl config = ethernet_ctrl_none; + if (promiscuous_en) { + config = (ethernet_ctrl)(config | ethernet_ctrl_promiscuous_mode); + } + if (loopback_en) { + config = (ethernet_ctrl)(config | ethernet_ctrl_loopback); + } + VOLATILE_WRITE(ethernet->ctrl , config); +} + +// Retrieve the mode (non-volatile register) +ethernet_ctrl ethernet_mode_get(ethernet_t ethernet) { + return VOLATILE_READ(ethernet->ctrl); +} + +// Set the MAC address ('address' is 64-bit, but only 48 lower bits are used) +void ethernet_mac_address_set(ethernet_t ethernet, uint64_t address) { + ethernet_machi machi = {.mac_hi = (address >> 32) & 0xffff}; + VOLATILE_WRITE( ethernet->maclo , (uint32_t)(address & 0xffffffff)); + VOLATILE_WRITE( ethernet->machi , machi); +} + +// Retrieve the MAC address (non-volatile register) +uint64_t ethernet_mac_address_get(ethernet_t ethernet) { + uint64_t maclo = VOLATILE_READ(ethernet->maclo); + uint64_t machi = VOLATILE_READ(ethernet->machi).mac_hi; + return (machi << 32) | maclo; +} + +///////////////////// +// Buffer accesses // +///////////////////// + +// Write a 64-bit word to the TX buffer at the given word offset +void ethernet_tx_buffer_write64(ethernet_t ethernet, uint32_t word_offset, uint64_t data) { + VOLATILE_WRITE(ethernet->tx_data_buf[word_offset], data); +} + +// Read a 64-bit word from the RX buffer at the given word offset +uint64_t ethernet_rx_buffer_read64(ethernet_t ethernet, uint32_t word_offset) { + return VOLATILE_READ(ethernet->rx_data_buf[word_offset]); +} + +// Read a single byte from the RX buffer at the given byte offset +static uint16_t last_word_offset_read = -1; +// word offset of the last word read from the RX buffer, used to cache the last word read to avoid redundant reads (the compiler won't optimise this for us because the RX buffer is volatile) +uint8_t ethernet_rx_buffer_read_byte(ethernet_t ethernet, uint16_t byte_offset) { + // Reads one byte from the RX buffer. + // Because the buffer has to be marked as volatile to avoid being cached, repeated byte reads will + // not be optimised into a wide read. To avoid this, we can store one word in a static variable in RAM + // But then we need to make sure we invalidate that cached word if the buffer could have changed + // The buffer can't change until we pop the packet* so we invalidate on pop + // *strictly speaking if we read outside of valid memory, the buffer could change without a pop, but why are you reading outside of valid memory? + static uint64_t word = 0; + + if (last_word_offset_read != byte_offset / 8) { + word = ethernet_rx_buffer_read64(ethernet, byte_offset / 8); + last_word_offset_read = byte_offset / 8; + } + return (word >> ((byte_offset & 7)*8)) & 0xFF; +} + +// Retrieve metadata for the nth oldest packet (index=0 being the oldest packet) +ethernet_pkt_metadata_t ethernet_rx_buffer_metadata_get(ethernet_t ethernet, uint8_t index) { + if (index >= ETHERNET_RX_TABLE_DEPTH) { + ethernet_pkt_metadata_t invalid = {0}; + return invalid; + } + uint64_t reg = VOLATILE_READ(ethernet->rx_desc_table[index]); + ethernet_pkt_metadata_t metadata; + metadata.reason = (ethernet_capture_reason_e)((reg & ETHERNET_RXTABLE_REASON_MASK) >> ETHERNET_RXTABLE_REASON_OFFSET); + metadata.pkt_ptr = (reg & ETHERNET_RXTABLE_PKT_PTR_MASK) >> ETHERNET_RXTABLE_PKT_PTR_OFFSET; + metadata.pkt_len = (reg & ETHERNET_RXTABLE_PKT_LEN_MASK) >> ETHERNET_RXTABLE_PKT_LEN_OFFSET; + return metadata; +} + +// Copy a packet's data from the RX buffer into a provided buffer +// Does NOT free the packet +void ethernet_read_packet_data_raw(ethernet_t ethernet, uint16_t packet_start_ptr, uint16_t len, uint8_t *data) { + // Copy the whole packet into the provided data buffer + for (uint16_t i = 0; i < len; i++) { + data[i] = ethernet_rx_buffer_read_byte(ethernet, packet_start_ptr + i); + } +} + +/////////////////// +// TX/RX Control // +/////////////////// + +// Trigger a transmission of a packet of length len_bytes +uint8_t ethernet_tx_packet_trigger(ethernet_t ethernet, uint16_t len_bytes) { + if ((len_bytes > 1518) || (len_bytes < 56)) { // 1518 is the max size without the CRC, 56 is the min size without the CRC + return 1; // packet length is invalid + } + + ethernet_tx_ctrl tx_ctrl = {.tx_packet_len = len_bytes}; + VOLATILE_WRITE(ethernet->tx_ctrl, tx_ctrl); + // writing the length has the side effect of triggering the send + + return 0; +} + +// Pop the oldest packet from the RX buffer, freeing up space for new packets +void ethernet_rx_pop_packet(ethernet_t ethernet) { + last_word_offset_read = -1; // once a pop happens some data in the RX buffer COULD change, so we invalidate the cached word + ethernet_rx_pop pop = {.pop = 1}; + VOLATILE_WRITE(ethernet->rx_pop, pop); +} + +// Check if TX is busy +bool ethernet_tx_is_busy(ethernet_t ethernet) { + return ethernet_status_get(ethernet).tx_busy; +} + +// Check if RX has 1 or more packets buffered +bool ethernet_rx_packet_pending(ethernet_t ethernet) { + return ethernet_status_get(ethernet).rx_not_empty; +} - // Read upper 16 bits - uint64_t upper = - (uint64_t)(DEV_READ(ethernet + ETHERNET_MACHI_REG) & ETHERNET_MACHI_MACADDR_MASK); +////////// +// MDIO // +////////// - return (upper << 32) | lower; +// For internal use +ethernet_mdio_ctrl ethernet_mdio_ctrl_raw_get(ethernet_t ethernet) { + return VOLATILE_READ(ethernet->mdio_ctrl); } -void ethernet_rx_promiscuous_enable(ethernet_t ethernet) -{ - DEV_WRITE(ethernet + ETHERNET_MACHI_REG, - DEV_READ(ethernet + ETHERNET_MACHI_REG) | ETHERNET_MACHI_ALLPKTS_MASK); +// For internal use +void ethernet_mdio_ctrl_raw_set(ethernet_t ethernet, ethernet_mdio_ctrl mdio_ctrl) { + VOLATILE_WRITE(ethernet->mdio_ctrl, mdio_ctrl); } -void ethernet_rx_promiscuous_disable(ethernet_t ethernet) -{ - DEV_WRITE(ethernet + ETHERNET_MACHI_REG, - DEV_READ(ethernet + ETHERNET_MACHI_REG) & ~ETHERNET_MACHI_ALLPKTS_MASK); +// Set MDIO direction (true = output, false = input) +void ethernet_mdio_dir_set(ethernet_t ethernet, bool mdio_out_en) { + ethernet_mdio_ctrl mdio_ctrl = ethernet_mdio_ctrl_raw_get(ethernet); + if (mdio_out_en) { + mdio_ctrl |= ethernet_mdio_ctrl_mdio_oen; + } else { + mdio_ctrl &= ~ethernet_mdio_ctrl_mdio_oen; + } + ethernet_mdio_ctrl_raw_set(ethernet, mdio_ctrl); } -bool ethernet_rx_promiscuous_get(ethernet_t ethernet) -{ - return (DEV_READ(ethernet + ETHERNET_MACHI_REG) & ETHERNET_MACHI_ALLPKTS_MASK) != 0; +// Set output MDIO value +void ethernet_mdio_out_set(ethernet_t ethernet, bool mdio_out) { + ethernet_mdio_ctrl mdio_ctrl = ethernet_mdio_ctrl_raw_get(ethernet); + if (mdio_out) { + mdio_ctrl |= ethernet_mdio_ctrl_mdio_o; + } else { + mdio_ctrl &= ~ethernet_mdio_ctrl_mdio_o; + } + ethernet_mdio_ctrl_raw_set(ethernet, mdio_ctrl); } -bool ethernet_tx_is_busy(ethernet_t ethernet) -{ - return (DEV_READ(ethernet + ETHERNET_TPLR_REG) & ETHERNET_TPLR_BUSY_MASK) != 0; +// Get input MDIO value +bool ethernet_mdio_in_get(ethernet_t ethernet) { + ethernet_mdio_ctrl mdio_ctrl = ethernet_mdio_ctrl_raw_get(ethernet); + return (mdio_ctrl & ethernet_mdio_ctrl_mdio_i) != 0; } -void ethernet_tx_packet_send(ethernet_t ethernet, uint16_t len_bytes) -{ - DEV_WRITE(ethernet + ETHERNET_TPLR_REG, (uint32_t)len_bytes); +// Set MDIO clock value +void ethernet_mdio_c_set(ethernet_t ethernet, bool mdio_clk) { + ethernet_mdio_ctrl mdio_ctrl = ethernet_mdio_ctrl_raw_get(ethernet); + if (mdio_clk) { + mdio_ctrl |= ethernet_mdio_ctrl_mdio_clk; + } else { + mdio_ctrl &= ~ethernet_mdio_ctrl_mdio_clk; + } + ethernet_mdio_ctrl_raw_set(ethernet, mdio_ctrl); } -void ethernet_rx_first_buffer_set(ethernet_t ethernet, uint8_t buf) -{ - DEV_WRITE(ethernet + ETHERNET_RSR_REG, (uint32_t)buf); +// Pulse the MDIO clock line high then low (with a small delay) +void ethernet_mdc_pulse(ethernet_t ethernet) { + // The delays are probably unnecessary but included to be safe + // system clock is 50 MHz = 8ns period and MDC has to be asserted for at least 160 ns = 20 clocks + // I would be absolutely shocked if the bus turns around that fast but we'll add a little busywait to be sure + + for(volatile int i = 0; i < 10; i++) {}; // busy wait + ethernet_mdio_c_set(ethernet, true); + for(volatile int i = 0; i < 10; i++) {}; // busy wait + ethernet_mdio_c_set(ethernet, false); } -uint8_t ethernet_rx_first_buffer_get(ethernet_t ethernet) -{ - return (uint8_t)((DEV_READ(ethernet + ETHERNET_RSR_REG) >> ETHERNET_RSR_FIRST_BUF_OFFSET) & - ETHERNET_BUF_MASK); +// Set MDIO output data t hen pulse clock line +void ethernet_mdio_write_bit(ethernet_t ethernet, bool bit) { + ethernet_mdio_out_set(ethernet, bit); + ethernet_mdc_pulse(ethernet); } -uint8_t ethernet_rx_next_buffer_get(ethernet_t ethernet) -{ - return (uint8_t)((DEV_READ(ethernet + ETHERNET_RSR_REG) >> ETHERNET_RSR_NEXT_BUF_OFFSET) & - ETHERNET_BUF_MASK); +// Pulse clock line then read MDIO data bit +bool ethernet_mdio_read_bit(ethernet_t ethernet) { + ethernet_mdc_pulse(ethernet); + return ethernet_mdio_in_get(ethernet); } -void ethernet_rx_last_buffer_set(ethernet_t ethernet, uint8_t buf) -{ - DEV_WRITE(ethernet + ETHERNET_RFCS_REG, (uint32_t)buf); +//////////////////////// +// High-level methods // +//////////////////////// + +// Initialise the ethernet +// Sets the MAC address, promiscuity, and interrupt mask +// +// This function ALMOST perfectly resets the internal state of the Ethernet hardware, with a few exceptions: +// - the location of the pointer in the RX write buffer will be retained, but this is not relevant as you should always read from the RX buffer using a pointer provided by the metadata table +// - any in-progress transmissions will not be interrupted and will complete +// - the data in the RX memory will not be wiped (but it will be marked as free so could be overwritten at any time by an incoming packet) +// - the data in the TX memory will not be wiped (but you can overwrite it any time you like) +// +// * intr_mask * +// A typical interrupt mask is (ethernet_intr_rx_not_empty) +// i.e. interrupt for all new packets or for manually triggered interrupts +// An alternative would be (ethernet_intr_rx_table_almost_full | ethernet_intr_rx_buf_almost_full) +// i.e. only interrupt when the table or buffer is almost full (i.e. >=6 packets are buffered, or there are fewer than 1522 bytes left in the buffer) +// this will generate fewer interrupts and you could have the ISR pop ALL packets when it fires +// +// * promiscuous * +// If promiscuous is true, the ethernet will accept all packets, regardless of destination MAC address +// If it is false, it will only accept packets destined for its MAC address, or broadcast/multicast packets +void ethernet_init(ethernet_t ethernet, uint64_t mac_address, bool promiscuous, ethernet_intr intr_mask) { + // Write config registers + ethernet_mode_set(ethernet, promiscuous, false); // set to promiscuous or non-promiscuous mode, but not loopback mode + ethernet_mac_address_set(ethernet, mac_address); + ethernet_intr_mask_set(ethernet,intr_mask); + + // Clear any outstanding interrupts + ethernet_test_intr_clear(ethernet); + ethernet_tx_done_intr_clear(ethernet); + ethernet_packet_lost_intr_clear(ethernet); + + // Pop any packets left in the buffer + while(ethernet_rx_packet_pending(ethernet)) { + ethernet_rx_pop_packet(ethernet); + } + + // Possible improvement would be to include a 'reset' CSR that forcefully clears the internal state of the Ethernet hardware + // though as far as I know, clearing all interrupts and popping all packets is almost equivalent to that +} + +// Reads the oldest packet from the RX buffer and copies its data (including 4 CRC bytes) into the provided buffer +// Also pops the packet from the RX buffer, freeing up space for new packets +// Returns the metadata for the packet, including its length (including the CRC) +ethernet_pkt_metadata_t ethernet_read_and_pop_oldest_packet(ethernet_t ethernet, uint8_t *data) { + if (!ethernet_rx_packet_pending(ethernet)) { + ethernet_pkt_metadata_t bad_pkt; + bad_pkt.pkt_len = 0; + bad_pkt.pkt_ptr = 0; + return bad_pkt; // no packet to read + } + + // Read the oldest packet in the RX buffer + ethernet_pkt_metadata_t metadata = ethernet_rx_buffer_metadata_get(ethernet, 0); + uint16_t packet_pointer = metadata.pkt_ptr; + uint16_t packet_end = metadata.pkt_ptr + metadata.pkt_len; // end byte of the packet + + // Copy the whole packet into the provided data buffer + while (packet_pointer < packet_end) { + *data++ = ethernet_rx_buffer_read_byte(ethernet, packet_pointer++); + // Remember, the RX buffer is volatile, so if we just 'memcpy' it will compile as a whole long series of individual + // byte reads. As designers, we know that while the buffer is technically volatile, it cannot change until a pop occurs + // so we can need to do some trickery here to read a whole word at a time, then break it into bytes once it's in non-volatile RAM + // read_byte is clever: it caches the last word read to avoid redundant reads, and invalidates the cache on pop + // so we should only have to read from the RX buffer once per word, even if we read one byte at a time here + // the compiler should also be able to figure out that we are copying whole words here and leverage that, since the + // only volatile part happens inside read_byte + } + + // Free the packet from the hardware + ethernet_rx_pop_packet(ethernet); + + // The data has been copied out to the provided buffer, but we will also return the metadata if the caller wants it + // Importantly this includes the packet length + return metadata; } -uint8_t ethernet_rx_last_buffer_get(ethernet_t ethernet) -{ - return (uint8_t)((DEV_READ(ethernet + ETHERNET_RSR_REG) >> ETHERNET_RSR_LAST_BUF_OFFSET) & - ETHERNET_BUF_MASK); +// Copies packet data into the TX buffer then kicks off a transmission and returns immediately +// The data should include the source MAC, destination MAC, ethertype/length, and payload +// It should not include the preamble, SFD, CRC, or inter-frame gap - those are handled by the hardware +// Returns 0 if the packet was sent successfully, or a nonzero error code if it was not +uint8_t ethernet_send_packet(ethernet_t ethernet, const uint8_t *data, uint16_t len_bytes) { + if (ethernet_tx_is_busy(ethernet)) { + return 1; // TX is busy, can't send a packet right now + } + if (len_bytes > 1518) { // 1518 is the max size without the CRC + return 2; // packet is too long, can't send it + } + if (len_bytes < 56) { // min size without the CRC + return 3; // packet is too short, can't send it + } + + uint8_t len_words = (len_bytes + 7) / 8; // round up to nearest word + + // Copy the whole packet into the TX buffer + for (uint16_t i = 0; i < len_words; i++) { + uint64_t word = 0; + for (uint8_t j = 0; j < 8; j++) { + word |= ((uint64_t)data[i*8 + j]) << (j*8); + // Check endianness on this? + } + // The compiler should be able to optimise this -- we are going doing some bitwise algebra here + // to get a 64-bit word, but its layout in memory is already exactly as we need it so no arithmetic + // is needed if the compiler is clever + ethernet_tx_buffer_write64(ethernet, i, word); + } + // Trigger the transmission + ethernet_tx_packet_trigger(ethernet, len_bytes); + + // The call is responsible for attaching metadata: source MAC, destination MAC, ethertype/length, and the payload + // The hardware will automatically add the preamble, SFD, CRC, and inter-frame gap + + return 0; // packet sending (though not necessarily done yet) } -bool ethernet_rx_packet_pending(ethernet_t ethernet) -{ - return (DEV_READ(ethernet + ETHERNET_RSR_REG) & ETHERNET_RSR_RX_DONE_MASK) != 0; +// Mask all interrupts and ignore incoming traffic +// outstanding interrupts won't be cleared +void ethernet_disable(ethernet_t ethernet) { + ethernet_intr_mask_set(ethernet, ethernet_intr_none); // mask all interrupts + ethernet_mode_set(ethernet, false, true); // disable promiscuous and enter loopback mode + // in loopback we won't receive anything unless we TX it ourselves } -uint16_t ethernet_rx_buffer_packet_length_get(ethernet_t ethernet, uint8_t buf) -{ - if (buf >= ETHERNET_RXBUFF_COUNT) { - return (uint16_t)(-1); +// MDIO write PHY register +void ethernet_mdio_write(ethernet_t ethernet, uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { + // Set the MDIO direction to output + ethernet_mdio_dir_set(ethernet, true); + + // Write the preamble - 32 continuous 1's + for (uint8_t i = 0; i < 32; i++) { + ethernet_mdio_write_bit(ethernet, true); + } + + // Write the start of frame - 01 + ethernet_mdio_write_bit(ethernet, false); + ethernet_mdio_write_bit(ethernet, true); + + // Write the opcode - 01 for write + ethernet_mdio_write_bit(ethernet, false); + ethernet_mdio_write_bit(ethernet, true); + + // Write the PHY address - 5 bits + for (int8_t i = 4; i >= 0; i--) { + ethernet_mdio_write_bit(ethernet, (phy_addr >> i) & 0x1); + } + + // Write the register address - 5 bits + for (int8_t i = 4; i >= 0; i--) { + ethernet_mdio_write_bit(ethernet, (reg_addr >> i) & 0x1); } - return DEV_READ(ethernet + ETHERNET_RPLR_REG + (buf << 3)) & ETHERNET_PACKET_LEN_MASK; + + // Write the turnaround - 2 bits (10) + ethernet_mdio_write_bit(ethernet, true); + ethernet_mdio_write_bit(ethernet, false); + + // Write the data - 16 bits + for (int8_t i = 15; i >= 0; i--) { + ethernet_mdio_write_bit(ethernet, (data >> i) & 0x1); + } + + // Release MDIO line + ethernet_mdio_dir_set(ethernet, false); } -void ethernet_init(ethernet_t ethernet) -{ - ethernet_rx_promiscuous_disable(ethernet); - ethernet_rx_first_buffer_set(ethernet, 0); - ethernet_rx_last_buffer_set(ethernet, ETHERNET_RXBUFF_COUNT - 1); +// MDIO read PHY register +uint16_t ethernet_mdio_read(ethernet_t ethernet, uint8_t phy_addr, uint8_t reg_addr) { + // Set the MDIO direction to output + ethernet_mdio_dir_set(ethernet, true); + + // Write the preamble - 32 continuous 1's + for (uint8_t i = 0; i < 32; i++) { + ethernet_mdio_write_bit(ethernet, true); + } + + // Write the start of frame - 01 + ethernet_mdio_write_bit(ethernet, false); + ethernet_mdio_write_bit(ethernet, true); + + // Write the opcode - 10 for read + ethernet_mdio_write_bit(ethernet, true); + ethernet_mdio_write_bit(ethernet, false); + + // Write the PHY address - 5 bits + for (int8_t i = 4; i >= 0; i--) { + ethernet_mdio_write_bit(ethernet, (phy_addr >> i) & 0x1); + } + + // Write the register address - 5 bits + for (int8_t i = 4; i >= 0; i--) { + ethernet_mdio_write_bit(ethernet, (reg_addr >> i) & 0x1); + } + + // Turnaround - set MDIO to input and read two bits + ethernet_mdio_dir_set(ethernet, false); + //(void)ethernet_mdio_read_bit(ethernet); // Discard the first turnaround bit + bool turnaround_bit_2 = ethernet_mdio_read_bit(ethernet); + + if (turnaround_bit_2 != false) { + return -1; // Error: invalid turnaround bit + } + + // Read the data - 16 bits + uint16_t data = 0; + for (int8_t i = 15; i >= 0; i--) { + bool bit = ethernet_mdio_read_bit(ethernet); + data |= (bit << i); + } + + return data; } diff --git a/sw/device/lib/hal/ethernet.h b/sw/device/lib/hal/ethernet.h index fa3080f34..e2ff6c431 100644 --- a/sw/device/lib/hal/ethernet.h +++ b/sw/device/lib/hal/ethernet.h @@ -4,87 +4,80 @@ #pragma once +#include "hal/autogen/ethernet.h" #include "hal/mmio.h" #include #include -#define ETHERNET_MACLO_REG (0x800) - -#define ETHERNET_MACHI_REG (0x808) -#define ETHERNET_MACHI_MACADDR_MASK (0xFFFF) -#define ETHERNET_MACHI_ALLPKTS_MASK (0x400000) -#define ETHERNET_MACHI_IRQ_EN (0x800000) - -#define ETHERNET_TPLR_REG (0x810) -#define ETHERNET_TPLR_BUSY_MASK (0x80000000) - -#define ETHERNET_MDIOCTRL_REG (0x820) -#define ETHERNET_MDIOCTRL_MDIOCLK_MASK (0x1) -#define ETHERNET_MDIOCTRL_MDIOOUT_MASK (0x2) -#define ETHERNET_MDIOCTRL_MDIOOEN_MASK (0x4) -#define ETHERNET_MDIOCTRL_MDIOIN_MASK (0x8) - -#define ETHERNET_RFCS_REG (0x828) - -#define ETHERNET_RSR_REG (0x830) -#define ETHERNET_RSR_FIRST_BUF_OFFSET (0) -#define ETHERNET_RSR_NEXT_BUF_OFFSET (4) -#define ETHERNET_RSR_LAST_BUF_OFFSET (8) -#define ETHERNET_RSR_RX_DONE_MASK (0x1000) -#define ETHERNET_RSR_RX_IRQ_MASK (0x2000) - -#define ETHERNET_RPLR_REG (0x840) - -#define ETHERNET_TXBUFF_OFFSET (0x1000) -#define ETHERNET_RXBUFF_OFFSET (0x4000) -#define ETHERNET_RXBUFF_COUNT (8) -#define ETHERNET_BUFF_SIZE_BYTES (0x800) - -#define ETHERNET_PACKET_LEN_MASK (0x7FF) -#define ETHERNET_BUF_MASK (0xF) - -typedef void *ethernet_t; - -void ethernet_mac_address_set(ethernet_t ethernet, uint64_t address); +// Depth in 64-bit words +#define ETHERNET_RX_BUF_DEPTH 1024 +#define ETHERNET_RX_TABLE_DEPTH 8 +#define ETHERNET_TX_BUF_DEPTH 256 + +// entries in the RXTABLE have the following format: +#define ETHERNET_RXTABLE_PKT_LEN_MASK (0x00000000FFFF0000) // bits 31:16 are the packet length in bytes +#define ETHERNET_RXTABLE_PKT_PTR_MASK (0xFFFFFFFF00000000) // upper 32 bits are the packet pointer +#define ETHERNET_RXTABLE_REASON_MASK (0x3) // bits 1:0 indicate the reason the packet was captured (see ethernet_capture_reason_e) + +#define ETHERNET_RXTABLE_PKT_LEN_OFFSET (16) +#define ETHERNET_RXTABLE_PKT_PTR_OFFSET (32) +#define ETHERNET_RXTABLE_REASON_OFFSET (0) + + +typedef enum { + MAC_MATCHES = 0, + MAC_BROADCAST = 1, + MAC_MULTICAST = 2, + NON_MAC_MATCH = 3 +} ethernet_capture_reason_e; + +typedef struct [[gnu::aligned(4)]] { + ethernet_capture_reason_e reason : 2; + uint32_t : 14; // reserved + uint16_t pkt_len : 16; + uint32_t pkt_ptr : 32; +} ethernet_pkt_metadata_t; + +// Low-level methods +ethernet_intr ethernet_intr_state_get(ethernet_t ethernet); +ethernet_intr ethernet_intr_mask_get(ethernet_t ethernet); +void ethernet_intr_mask_set(ethernet_t ethernet, ethernet_intr intr_mask); +void ethernet_test_intr_fire(ethernet_t ethernet); +void ethernet_test_intr_clear(ethernet_t ethernet); +void ethernet_tx_done_intr_clear(ethernet_t ethernet); +void ethernet_packet_lost_intr_clear(ethernet_t ethernet); +ethernet_status ethernet_status_get(ethernet_t ethernet); +void ethernet_mode_set(ethernet_t ethernet, bool promiscuous_en, bool loopback_en); +ethernet_ctrl ethernet_mode_get(ethernet_t ethernet); uint64_t ethernet_mac_address_get(ethernet_t ethernet); -void ethernet_rx_promiscuous_enable(ethernet_t ethernet); -void ethernet_rx_promiscuous_disable(ethernet_t ethernet); -bool ethernet_rx_promiscuous_get(ethernet_t ethernet); +void ethernet_mac_address_set(ethernet_t ethernet, uint64_t address); +void ethernet_tx_buffer_write64(ethernet_t ethernet, uint32_t word_offset, uint64_t data); +uint64_t ethernet_rx_buffer_read64(ethernet_t ethernet, uint32_t word_offset); +uint8_t ethernet_rx_buffer_read_byte(ethernet_t ethernet, uint16_t byte_offset); +ethernet_pkt_metadata_t ethernet_rx_buffer_metadata_get(ethernet_t ethernet, uint8_t index); +void ethernet_read_packet_data_raw(ethernet_t ethernet, uint16_t packet_start_ptr, uint16_t len, uint8_t *data); +uint8_t ethernet_tx_packet_trigger(ethernet_t ethernet, uint16_t len_bytes); +void ethernet_rx_pop_packet(ethernet_t ethernet); bool ethernet_tx_is_busy(ethernet_t ethernet); -void ethernet_tx_packet_send(ethernet_t ethernet, uint16_t len_bytes); -void ethernet_rx_first_buffer_set(ethernet_t ethernet, uint8_t buf); -uint8_t ethernet_rx_first_buffer_get(ethernet_t ethernet); -uint8_t ethernet_rx_next_buffer_get(ethernet_t ethernet); -void ethernet_rx_last_buffer_set(ethernet_t ethernet, uint8_t buf); -uint8_t ethernet_rx_last_buffer_get(ethernet_t ethernet); bool ethernet_rx_packet_pending(ethernet_t ethernet); -uint16_t ethernet_rx_buffer_packet_length_get(ethernet_t ethernet, uint8_t buf); -void ethernet_init(ethernet_t ethernet); - -static inline void -ethernet_tx_buffer_write64(ethernet_t ethernet, uint32_t word_offset, uint64_t data) -{ - if (((word_offset + 1) * sizeof(uint64_t)) > ETHERNET_BUFF_SIZE_BYTES) { - return; - } - DEV_WRITE64(ethernet + ETHERNET_TXBUFF_OFFSET + word_offset * sizeof(uint64_t), data); -} - -static inline uint64_t ethernet_tx_buffer_read64(ethernet_t ethernet, uint32_t word_offset) -{ - if (((word_offset + 1) * sizeof(uint64_t)) > ETHERNET_BUFF_SIZE_BYTES) { - return 0; - } - return DEV_READ64(ethernet + ETHERNET_TXBUFF_OFFSET + word_offset * sizeof(uint64_t)); -} - -static inline uint64_t -ethernet_rx_buffer_read64(ethernet_t ethernet, uint8_t buf, uint32_t word_offset) -{ - if (buf >= ETHERNET_RXBUFF_COUNT || - ((word_offset + 1) * sizeof(uint64_t)) > ETHERNET_BUFF_SIZE_BYTES) { - return 0; - } - return DEV_READ64(ethernet + ETHERNET_RXBUFF_OFFSET + buf * ETHERNET_BUFF_SIZE_BYTES + - word_offset * sizeof(uint64_t)); -} +ethernet_mdio_ctrl ethernet_mdio_ctrl_raw_get(ethernet_t ethernet); +void ethernet_mdio_ctrl_raw_set(ethernet_t ethernet, ethernet_mdio_ctrl mdio_ctrl); + +/// MDIO bit banging interface +void ethernet_mdio_dir_set(ethernet_t ethernet, bool mdio_out_en); +void ethernet_mdio_out_set(ethernet_t ethernet, bool mdio_out); +bool ethernet_mdio_in_get(ethernet_t ethernet); +void ethernet_mdio_c_set(ethernet_t ethernet, bool mdio_clk); + +void ethernet_mdc_pulse(ethernet_t ethernet); +void ethernet_mdio_write_bit(ethernet_t ethernet, bool bit); +bool ethernet_mdio_read_bit(ethernet_t ethernet); + +// High-level interface +void ethernet_init(ethernet_t ethernet, uint64_t mac_address, bool promiscuous, ethernet_intr intr_mask); +ethernet_pkt_metadata_t ethernet_read_and_pop_oldest_packet(ethernet_t ethernet, uint8_t *data); +uint8_t ethernet_send_packet(ethernet_t ethernet, const uint8_t *data, uint16_t len_bytes); +void ethernet_disable(ethernet_t ethernet); + +void ethernet_mdio_write(ethernet_t ethernet, uint8_t phy_addr, uint8_t reg_addr, uint16_t data); +uint16_t ethernet_mdio_read(ethernet_t ethernet, uint8_t phy_addr, uint8_t reg_addr); diff --git a/sw/device/tests/ethernet/smoketest.c b/sw/device/tests/ethernet/smoketest.c index 5256c1125..5a2473cd4 100644 --- a/sw/device/tests/ethernet/smoketest.c +++ b/sw/device/tests/ethernet/smoketest.c @@ -2,109 +2,372 @@ // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 -#include "hal/mmio.h" +#include "boot/trap.h" #include "hal/mocha.h" #include "runtime/print.h" -bool reg_test(ethernet_t ethernet) -{ - ethernet_mac_address_set(ethernet, 0x55AA55AA55AA); - if (ethernet_mac_address_get(ethernet) != 0x55AA55AA55AA) { - return false; - } +///////////////////////////////////////////////////////// +/// XOR shift to provide pseudorandom data for testing // +///////////////////////////////////////////////////////// +uint64_t xorshift() { + static uint64_t state = 0xDEADBEEFCAFEBABEUL; + state ^= (state << 13); + state ^= (state >> 17); + state ^= (state << 5); + return state; +} - ethernet_mac_address_set(ethernet, 0xAA55AA55AA55); - if (ethernet_mac_address_get(ethernet) != 0xAA55AA55AA55) { - return false; - } +//////////////////// +// Test utilities // +//////////////////// +struct { + uint16_t fails; // checks within a test + uint16_t passes; // checks within a test + char *test_name; + bool tests_failed; +} ethernet_test_status; - ethernet_rx_promiscuous_disable(ethernet); - if (ethernet_rx_promiscuous_get(ethernet)) { - return false; - } +void ethernet_test_init() { + ethernet_test_status.fails = 0; + ethernet_test_status.passes = 0; + ethernet_test_status.test_name = ""; + ethernet_test_status.tests_failed = false; +} - ethernet_rx_promiscuous_enable(ethernet); - if (!ethernet_rx_promiscuous_get(ethernet)) { - return false; - } +void ethernet_report_test(bool expect_no_checks) { + uart_t uart = mocha_system_uart(); - ethernet_rx_first_buffer_set(ethernet, 0x5); - if (ethernet_rx_first_buffer_get(ethernet) != 0x5) { - return false; + char prefix; + if (ethernet_test_status.fails > 0) { + prefix = '!'; + ethernet_test_status.tests_failed = true; + } else if (ethernet_test_status.fails == 0 && ethernet_test_status.passes == 0) { + if (expect_no_checks) { + prefix = ' '; + } else { + prefix = '?'; + ethernet_test_status.tests_failed = true; + } + } else { + prefix = ' '; } - ethernet_rx_first_buffer_set(ethernet, 0xA); - if (ethernet_rx_first_buffer_get(ethernet) != 0xA) { - return false; - } + uprintf(uart, "[%c%c%c%c] Ethernet test %s ended. Passes: 0x%x, Fails: 0x%x\n", + prefix, prefix, prefix, prefix, + ethernet_test_status.test_name, ethernet_test_status.passes, ethernet_test_status.fails); +} - if (ethernet_rx_next_buffer_get(ethernet) >= 16) { - return false; +void ethernet_begin_test(char *test_name) { + uart_t uart = mocha_system_uart(); + uprintf(uart, "------ Ethernet test %s started\n", test_name); + + ethernet_test_status.test_name = test_name; + ethernet_test_status.fails = 0; + ethernet_test_status.passes = 0; +} + +void ethernet_expect_equal(uint64_t measured, uint64_t expected, char *measurement_name) { + uart_t uart = mocha_system_uart(); + if (measured != expected) { + uprintf(uart, " Ethernet test %s. Expected %s = 0x%lx, got 0x%lx\n", + ethernet_test_status.test_name, + measurement_name, + expected, + measured); + ethernet_test_status.fails++; + } else { + ethernet_test_status.passes++; } +} + +// Print a prefix for a test message +void ethernet_test_info() { + uart_t uart = mocha_system_uart(); + uprintf(uart, " Ethernet test %s: ", ethernet_test_status.test_name); +} + +/////////////////// +// Register test // +/////////////////// +void ethernet_reg_test(ethernet_t ethernet) { + ethernet_begin_test("REGISTER TEST"); + uint64_t dummy_data; + + ethernet_init(ethernet, 0xDEADBEEFCAFEBABEUL, false, ethernet_intr_none); + + //////////////////////////////////////////////////// + // Test interrupt state, mask, and test registers // + //////////////////////////////////////////////////// + ethernet_intr intr_measured; - ethernet_rx_last_buffer_set(ethernet, 0x5); - if (ethernet_rx_last_buffer_get(ethernet) != 0x5) { - return false; + intr_measured = ethernet_intr_state_get(ethernet); + ethernet_expect_equal(intr_measured, 0, "interrupt state after init"); + + intr_measured = ethernet_intr_mask_get(ethernet); + ethernet_expect_equal(intr_measured, 0, "interrupt mask after init"); + + for (uint8_t i = 0; i < 5; i++) { + dummy_data = xorshift() & 0x7F; + /* NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) */ + ethernet_intr_mask_set(ethernet, (ethernet_intr)dummy_data); // enable all interrupts + intr_measured = ethernet_intr_mask_get(ethernet); + ethernet_expect_equal(intr_measured, dummy_data, "interrupt mask after write"); } + ethernet_intr_mask_set(ethernet, 0); // disable all interrupts again + + ethernet_test_intr_fire(ethernet); // fire test interrupt + intr_measured = ethernet_intr_state_get(ethernet); + ethernet_expect_equal(intr_measured,ethernet_intr_manual_irq,"interrupt state after test interrupt"); + ethernet_test_intr_clear(ethernet); + intr_measured = ethernet_intr_state_get(ethernet); + ethernet_expect_equal(intr_measured,0,"interrupt state after test interrupt cleared"); + // TODO test tx_done interrupt too - ethernet_rx_last_buffer_set(ethernet, 0xA); - if (ethernet_rx_last_buffer_get(ethernet) != 0xA) { - return false; + ////////////////////////// + // Test status register // + ////////////////////////// + ethernet_status status_measured; + status_measured = ethernet_status_get(ethernet); + ethernet_expect_equal(status_measured.rx_not_empty, 0, "status.rx_not_empty"); + ethernet_expect_equal(status_measured.rx_table_almost_full, 0, "status.rx_table_almost_full"); + ethernet_expect_equal(status_measured.rx_table_full, 0, "status.rx_table_full"); + ethernet_expect_equal(status_measured.rx_buf_almost_full, 0, "status.rx_buf_almost_full"); + ethernet_expect_equal(status_measured.packet_lost, 0, "status.packet_lost"); + ethernet_expect_equal(status_measured.tx_busy, 0, "status.tx_busy"); + ethernet_expect_equal(status_measured.n_packets_in_rx_buf, 0, "status.n_packets_in_rx_buf"); + // TODO test status after receiving a packet too + + //////////////// + // Test modes // + //////////////// + ethernet_expect_equal(ethernet_mode_get(ethernet),ethernet_ctrl_none,"eth mode at init"); + ethernet_mode_set(ethernet, true, false); // promiscuous = true, loopback = false + ethernet_expect_equal(ethernet_mode_get(ethernet),ethernet_ctrl_promiscuous_mode,"eth mode promiscuous"); + ethernet_mode_set(ethernet, false, true); // promiscuous = false, loopback = true + ethernet_expect_equal(ethernet_mode_get(ethernet),ethernet_ctrl_loopback,"eth mode loopback"); + ethernet_mode_set(ethernet, true, true); // promiscuous = true, loopback = true + ethernet_expect_equal(ethernet_mode_get(ethernet),ethernet_ctrl_promiscuous_mode | ethernet_ctrl_loopback,"eth mode loopback & promiscuous"); + ethernet_mode_set(ethernet, false, false); + + //////////////////////// + // Test MAC addresses // + //////////////////////// + for(uint8_t i = 0; i < 5; ++i) { + dummy_data = xorshift() & 0xFFFFFFFFFFFF; // 48-bit MAC address + ethernet_mac_address_set(ethernet, dummy_data); + ethernet_expect_equal( + ethernet_mac_address_get(ethernet), + (dummy_data & 0xFFFFFFFFFFFF), + "MAC address test" + ); } - return true; + // TODO test packet_send, pop_packet, buffer_write, buffer_read, metadata_get, tx_is_busy, rx_packet_pending + ethernet_report_test(false); } -bool tx_buf_test(ethernet_t ethernet) -{ - for (uint32_t i = 0; i < 10; ++i) { - ethernet_tx_buffer_write64(ethernet, i, 0x3747F7781A13BDFBUL); - } - for (uint32_t i = 0; i < 10; ++i) { - ethernet_tx_buffer_write64(ethernet, (ETHERNET_BUFF_SIZE_BYTES / sizeof(uint64_t)) - 1 - i, - 0x8D4B4FBDA7BC77DFUL); + +/////////////////// +// Loopback test // +/////////////////// +void ethernet_loopback_test(ethernet_t ethernet, uint16_t pkt_len_bytes) { + ethernet_begin_test("LOOPBACK TEST"); + uint8_t pkt_len_words = (pkt_len_bytes+7)/8; + + uint64_t dummy_data [191]; + ethernet_status status_measured; + ethernet_init(ethernet, 0xDEADBEEFCAFEBABEUL, true, ethernet_intr_none); + + // Populate the dummy data + for(uint8_t i = 0; i < pkt_len_words; i++) { + uint64_t word = xorshift(); + dummy_data[i] = word; + //ethernet_test_info(); uprintf(mocha_system_uart(), "loopback: dummy_data[%x] = %lx\n",i,word); } + //for(uint8_t i = 0; i < pkt_len_words; i++) dummy_data[i] = xorshift(); - for (int i = 0; i < 10; ++i) { - if (ethernet_tx_buffer_read64(ethernet, i) != 0x3747F7781A13BDFBUL) { - return false; - } + /////////////////////// + // Set loopback mode // + /////////////////////// + ethernet_mode_set(ethernet, true, true); // promiscuous, loopback on + + /////////////////////////////////////////// + // Write the dummy data to the TX buffer // + /////////////////////////////////////////// + for(uint8_t i = 0; i < pkt_len_words; i++) { + ethernet_tx_buffer_write64(ethernet,i,dummy_data[i]); } - for (int i = 0; i < 10; ++i) { - if (ethernet_tx_buffer_read64(ethernet, (ETHERNET_BUFF_SIZE_BYTES / sizeof(uint64_t)) - 1 - - i) != 0x8D4B4FBDA7BC77DFUL) { - return false; - } + + ////////////////////// + // Fire the packet! // + ////////////////////// + ethernet_tx_packet_trigger(ethernet,pkt_len_bytes); + + /////////////////////////// + // Wait for it to arrive // + /////////////////////////// + while(ethernet_tx_is_busy(ethernet)) {}; + while(!ethernet_rx_packet_pending(ethernet)) {}; + + ////////////////////// + // Check the status // + ////////////////////// + status_measured = ethernet_status_get(ethernet); + ethernet_expect_equal(status_measured.rx_not_empty, 1, "loopback: status.rx_not_empty"); + ethernet_expect_equal(status_measured.rx_table_almost_full, 0, "loopback: status.rx_table_almost_full"); + ethernet_expect_equal(status_measured.rx_table_full, 0, "loopback: status.rx_table_full"); + ethernet_expect_equal(status_measured.rx_buf_almost_full, 0, "loopback: status.rx_buf_almost_full"); + ethernet_expect_equal(status_measured.packet_lost, 0, "loopback: status.packet_lost"); + ethernet_expect_equal(status_measured.tx_busy, 0, "loopback: status.tx_busy"); + ethernet_expect_equal(status_measured.n_packets_in_rx_buf, 1, "loopback: status.n_packets_in_rx_buf"); + + /////////////////////// + // Read the metadata // + /////////////////////// + ethernet_pkt_metadata_t metadata; + metadata = ethernet_rx_buffer_metadata_get(ethernet,0); // 0 is the oldest packet in the buffer, always + ethernet_expect_equal(metadata.reason,NON_MAC_MATCH,"loopback metadata capture reason"); + ethernet_expect_equal(metadata.pkt_len,pkt_len_bytes,"loopback metadata packet length"); + + /////////////////// + // Read the data // + /////////////////// + for (uint16_t byte_idx = 0; byte_idx < pkt_len_bytes; byte_idx++) { + ethernet_expect_equal( + ethernet_rx_buffer_read_byte(ethernet, metadata.pkt_ptr + byte_idx), + (dummy_data[byte_idx/8] >> (8*(byte_idx%8))) & 0xFF, + "loopback data mismatch" + ); } - return true; + //////////////////// + // Pop the packet // + //////////////////// + ethernet_rx_pop_packet(ethernet); + + ////////////////// + // Check status // + ////////////////// + status_measured = ethernet_status_get(ethernet); + ethernet_expect_equal(status_measured.rx_not_empty, 0, "loopback after pop: status.rx_not_empty"); + ethernet_expect_equal(status_measured.rx_table_almost_full, 0, "loopback after pop: status.rx_table_almost_full"); + ethernet_expect_equal(status_measured.rx_table_full, 0, "loopback after pop: status.rx_table_full"); + ethernet_expect_equal(status_measured.rx_buf_almost_full, 0, "loopback after pop: status.rx_buf_almost_full"); + ethernet_expect_equal(status_measured.packet_lost, 0, "loopback after pop: status.packet_lost"); + ethernet_expect_equal(status_measured.tx_busy, 0, "loopback after pop: status.tx_busy"); + ethernet_expect_equal(status_measured.n_packets_in_rx_buf, 0, "loopback after pop: status.n_packets_in_rx_buf"); + + ethernet_report_test(false); } -bool send_packet_test(ethernet_t ethernet) -{ - // Wait until Tx is not busy - while (ethernet_tx_is_busy(ethernet)) { - } - // Send a large 1500 byte packet - ethernet_tx_packet_send(ethernet, 1500); +///////////// +// TX test // +///////////// +void ethernet_tx_test(ethernet_t ethernet) { + ethernet_begin_test("TX TEST"); + uint8_t dummy_data[64]; + ethernet_init(ethernet, 0xDEADBEEFCAFEBABEUL, true, ethernet_intr_none); - // Tx should now be busy - if (!ethernet_tx_is_busy(ethernet)) { - return false; + // Populate the dummy data + for(uint8_t i = 0; i < 64; i++) { + dummy_data[i] = i; + //ethernet_test_info(); uprintf(mocha_system_uart(), "loopback: dummy_data[%x] = %x\n",i,dummy_data[i]); } - // Tx should eventually become not busy - while (ethernet_tx_is_busy(ethernet)) { - } + ethernet_send_packet(ethernet, dummy_data, 64); + ethernet_test_info(); uprintf(mocha_system_uart(), "Ethernet test sequence: sent 64 bytes\n"); + while(ethernet_tx_is_busy(ethernet)) {}; + ethernet_test_info(); uprintf(mocha_system_uart(), "Ethernet test sequence completed sending\n"); + + ethernet_report_test(true); +} + +/////////////// +// MDIO Test // +/////////////// - return true; +// Test the MDIO bit-banging interface by reading/writing some PHY registers +// The PHY on the Genesys2 is a Realtek RTL8211E-VL PHY +// Manuals are available online with the register map +void ethernet_mdio_test(ethernet_t ethernet) { + ethernet_begin_test("MDIO TEST"); + + uint8_t phy_addr = 0x01; // we have only one PHY which has default address 1 on the MDIO bus + uint16_t register_data; + + // Read PHYID register (1 and 2) + register_data = ethernet_mdio_read(ethernet, phy_addr, 0x2); + ethernet_expect_equal(register_data, 0x1c, "PHYID1 register"); // magic number from the datasheet + register_data = ethernet_mdio_read(ethernet, phy_addr, 0x3); + ethernet_expect_equal(register_data, 0b1100100100010101, "PHYID2 register"); // magic number from the datasheet + + // Read address 1 - status register + register_data = ethernet_mdio_read(ethernet, phy_addr, 0x1); + ethernet_expect_equal(register_data, 0b0111100101001001, "PHY status register"); // see PHY datasheet for this magic number + + // Read address 0 - control register + register_data = ethernet_mdio_read(ethernet, phy_addr, 0x0); + ethernet_expect_equal(register_data, 0x1140, "PHY control register"); + + // Write address 0 - control register + ethernet_mdio_write(ethernet, phy_addr, 0x0, register_data | 0x4000); + + // Read again + register_data = ethernet_mdio_read(ethernet, phy_addr, 0x0); + ethernet_expect_equal(register_data & 0x4000, 0x4000, "PHY loopback enable"); + + // Return to old state + ethernet_mdio_write(ethernet, phy_addr, 0x0, register_data & ~0x4000); + + ethernet_report_test(false); } -bool test_main() -{ +////////////////////// +// Main test runner // +////////////////////// +bool test_main(void) { ethernet_t ethernet = mocha_system_ethernet(); - ethernet_init(ethernet); + uart_t uart = mocha_system_uart(); + + ethernet_test_init(); + + //////////////////// + // Run test cases // + //////////////////// + ethernet_mdio_test(ethernet); + ethernet_tx_test(ethernet); + ethernet_reg_test(ethernet); + ethernet_loopback_test(ethernet, 1518); // max packet length + ethernet_loopback_test(ethernet, 60); // min packet length + ethernet_loopback_test(ethernet, 255); + ethernet_loopback_test(ethernet, 256); + ethernet_loopback_test(ethernet, 257); + + //////////////////////////////////////////////////////// + // Receive one packet and dump its contents over UART // + //////////////////////////////////////////////////////// + // Note: no checks are performed here, you must manually cross-check the UART output with a Wireshark capture of the packet sent + ethernet_init(ethernet, 0xDEADBEEFCAFEBABEUL, true, ethernet_intr_none); + uint8_t pkt_data[1518]; + ethernet_pkt_metadata_t metadata; + while (!ethernet_rx_packet_pending(ethernet)) {}; + metadata = ethernet_read_and_pop_oldest_packet(ethernet, pkt_data); + uprintf(uart, "Received packet of length %x with reason %x\n",metadata.pkt_len,metadata.reason); + + uint64_t word; + word = 0; + for (uint16_t byte_idx = 0; byte_idx < metadata.pkt_len; byte_idx++) { + word |= ((uint64_t)pkt_data[byte_idx]) << ((byte_idx%8)*8); + if (byte_idx%8 == 7 || byte_idx == metadata.pkt_len-1) { + uprintf(uart, "pkt_data[%x] = %lx\n",byte_idx/8,word); + word = 0; + } + } + uprintf(uart, "\n\n"); - return reg_test(ethernet) && tx_buf_test(ethernet) && send_packet_test(ethernet); + //////////// + // Return // + //////////// + return (ethernet_test_status.tests_failed == 0); }