├── example ├── .gitignore ├── constraints │ ├── debug.xdc │ └── example_10g_eth.xdc ├── build_example.sh ├── scripts │ └── sweep_packet_latency.tcl ├── hdl │ ├── eth_perf.sv │ └── example_10g_eth.sv └── example_10g_eth_build.tcl ├── src ├── ip │ ├── .gitignore │ ├── gen_eth_10g_ip.sh │ └── eth_10g_ip.tcl ├── tb │ ├── pcs │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── pcs_test_vector.py │ │ └── test_pcs.py │ ├── gearbox │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── test_gearboxes.py │ │ ├── test_tx_gearbox.py │ │ ├── test_rx_gearbox.py │ │ ├── gearbox_model.py │ │ └── gearbox_model.ipynb │ ├── mac │ │ ├── .gitignore │ │ ├── Makefile │ │ └── test_mac.py │ └── mac_pcs │ │ ├── mac_pcs_config.yaml │ │ ├── requirements.txt │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── mac_pcs_bfm.py │ │ └── test_mac_pcs.py └── hdl │ ├── pcs │ ├── gearbox_seq.sv │ ├── scrambler.sv │ ├── tx_gearbox.sv │ ├── lock_state.sv │ ├── rx_gearbox.sv │ ├── encoder.sv │ ├── pcs.sv │ └── decoder.sv │ ├── serdes │ ├── gtwizard_ultrascale_0_example_bit_sync.v │ ├── gtwizard_ultrascale_0_example_reset_sync.v │ ├── gtwizard_ultrascale_0_example_init.v │ └── gtwizard_ultrascale_0_example_wrapper.v │ ├── include │ └── code_defs_pkg.svh │ ├── mac │ ├── mac.sv │ └── rx_mac.sv │ ├── mac_pcs.sv │ └── eth_10g.sv ├── .gitignore ├── .gitmodules ├── .vscode └── launch.json ├── .github └── workflows │ └── cocotb-test.yaml ├── LICENSE └── README.md /example/.gitignore: -------------------------------------------------------------------------------- 1 | build*/ -------------------------------------------------------------------------------- /example/constraints/debug.xdc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ip/.gitignore: -------------------------------------------------------------------------------- 1 | vivado_project/ 2 | gen/ -------------------------------------------------------------------------------- /src/tb/pcs/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | sim_build 3 | *.fst 4 | *.vcd 5 | *.xml 6 | iverilog_dump.v -------------------------------------------------------------------------------- /src/tb/gearbox/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | sim_build 3 | *.fst* 4 | *.vcd 5 | *.xml 6 | iverilog_dump.v -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vivado_project/* 2 | msim/* 3 | *.log 4 | doc/ 5 | notes 6 | .sv*.toml 7 | .vscode/ 8 | TODO 9 | .Xil/ -------------------------------------------------------------------------------- /src/tb/mac/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | sim_build 3 | *.fst 4 | *.vcd 5 | *.xml 6 | iverilog_dump.v 7 | crc_tables.mem -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/ip/slicing_crc"] 2 | path = src/lib/slicing_crc 3 | url = git@github.com:ttchuk/slicing_crc.git 4 | -------------------------------------------------------------------------------- /src/ip/gen_eth_10g_ip.sh: -------------------------------------------------------------------------------- 1 | # !/bin/sh 2 | 3 | export ETH10G_FPGA_PART=xczu49dr-ffvf1760-2-e 4 | export ETH10G_CHANNEL=X0Y12 5 | 6 | mkdir -p gen 7 | cd gen 8 | vivado -mode tcl -source ../eth_10g_ip.tcl -------------------------------------------------------------------------------- /src/tb/mac_pcs/mac_pcs_config.yaml: -------------------------------------------------------------------------------- 1 | debug: False 2 | print_axis: False 3 | seed: 0 4 | startup_pause: 5000 5 | tx_seq_length: 100 6 | loopback_cycle_slip: 1 7 | loopback_bit_slip: 3 8 | dbg_manual_gearbox_slip : False -------------------------------------------------------------------------------- /src/tb/mac_pcs/requirements.txt: -------------------------------------------------------------------------------- 1 | cocotb==1.7.2 2 | cocotb-bus==0.2.1 3 | cocotb-test==0.2.4 4 | cocotbext-axi==0.1.18 5 | debugpy==1.6.3 6 | numpy==1.23.5 7 | pytest==7.1.3 8 | pyuvm==2.9.0 9 | PyYAML==6.0 10 | -------------------------------------------------------------------------------- /src/tb/mac_pcs/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | sim_build 3 | *.fst 4 | *.vcd 5 | *.xml 6 | iverilog_dump.v 7 | crc_tables.mem 8 | *.hier 9 | 10 | transcript 11 | modelsim.ini 12 | vsim.wlf 13 | *.ucdb 14 | covhtmlreport -------------------------------------------------------------------------------- /example/constraints/example_10g_eth.xdc: -------------------------------------------------------------------------------- 1 | # Timing 2 | create_clock -name init_clk -period 10 [get_ports i_init_clk] 3 | create_clock -name mgtrefclk0_x0y3 -period 6.4 [get_ports i_mgtrefclk0_x0y3_p] 4 | 5 | set_clock_groups -async -group [get_clocks init_clk] -group [get_clocks -include_generated_clocks mgtrefclk0_x0y3] 6 | 7 | # Pins 8 | set_property PACKAGE_PIN G12 [get_ports i_init_clk] 9 | set_property IOSTANDARD LVCMOS18 [get_ports i_init_clk] 10 | 11 | set_property PACKAGE_PIN P34 [get_ports i_mgtrefclk0_x0y3_p] 12 | 13 | -------------------------------------------------------------------------------- /example/build_example.sh: -------------------------------------------------------------------------------- 1 | # !/bin/sh 2 | 3 | # Ensure IP is built using src/ip/gen_eth_10g_ip and part matches below. 4 | 5 | # Default build parameters 6 | export FPGA_PART=xczu49dr-ffvf1760-2-e 7 | export SCRAMBLER_BYPASS=0 8 | export EXTERNAL_GEARBOX=0 9 | export TX_XVER_BUFFER=0 10 | export INIT_CLK_FREQ=100.0 11 | 12 | # get arguments k=v 13 | build_config="" 14 | for ARGUMENT in "$@" 15 | do 16 | KEY=$(echo $ARGUMENT | cut -f1 -d=) 17 | 18 | KEY_LENGTH=${#KEY} 19 | VALUE="${ARGUMENT:$KEY_LENGTH+1}" 20 | 21 | export "$KEY"="$VALUE" 22 | 23 | build_config="${build_config}-${KEY}_${VALUE}" 24 | done 25 | 26 | mkdir -p build$build_config 27 | cd build$build_config 28 | vivado -mode tcl -source ../example_10g_eth_build.tcl -notrace -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Remote Attach", 9 | "type": "python", 10 | "request": "attach", 11 | "connect": { 12 | "host": "localhost", 13 | "port": 5678 14 | }, 15 | "pathMappings": [ 16 | { 17 | "localRoot": "${workspaceFolder}", 18 | "remoteRoot": "${workspaceFolder}" 19 | } 20 | ], 21 | "justMyCode": false 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /example/scripts/sweep_packet_latency.tcl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | set packet_lengths {64 128 256 512 1024 2048 4096 8192 16364} 5 | 6 | foreach x $packet_lengths { 7 | 8 | set x_hex [format %04x $x] 9 | 10 | set_property OUTPUT_VALUE $x_hex [get_hw_probes packet_length -of_objects [get_hw_vios -of_objects [get_hw_devices xczu49dr_0] -filter {CELL_NAME=~"u_packet_control_vio"}]] 11 | commit_hw_vio [get_hw_probes {packet_length} -of_objects [get_hw_vios -of_objects [get_hw_devices xczu49dr_0] -filter {CELL_NAME=~"u_packet_control_vio"}]] 12 | run_hw_ila [get_hw_ilas -of_objects [get_hw_devices xczu49dr_0] -filter {CELL_NAME=~"tx_packet_ila"}] 13 | wait_on_hw_ila [get_hw_ilas -of_objects [get_hw_devices xczu49dr_0] -filter {CELL_NAME=~"tx_packet_ila"}] 14 | display_hw_ila_data [upload_hw_ila_data [get_hw_ilas -of_objects [get_hw_devices xczu49dr_0] -filter {CELL_NAME=~"tx_packet_ila"}]] 15 | write_hw_ila_data -force -csv_file ./iladata_${x}.csv hw_ila_data_2 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/cocotb-test.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: cocotb-test 3 | 4 | on: [push] 5 | 6 | jobs: 7 | run-pytest: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Checkout repository and submodules 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | 18 | - uses: actions/cache@v3 19 | with: 20 | path: ~/.cache/pip 21 | key: ${{ runner.os }}-pip-${{ hashFiles('**/src/tb/mac_pcs/requirements.txt') }} 22 | restore-keys: | 23 | ${{ runner.os }}-pip- 24 | 25 | - name: Set up Python 3.9 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: 3.9 29 | 30 | - name: Install dependencies 31 | run: | 32 | pip3 install -r src/tb/mac_pcs/requirements.txt 33 | sudo apt install -y --no-install-recommends iverilog 34 | - name: Verify with cocotb & icarus 35 | run: | 36 | cd src/tb/mac_pcs 37 | pytest -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tom Chisholm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/tb/gearbox/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= icarus 5 | TOPLEVEL_LANG ?= verilog 6 | WAVES ?= 1 7 | 8 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/rx_gearbox.sv 9 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/tx_gearbox.sv 10 | 11 | 12 | # MODULE is the basename of the Python test file 13 | MODULE ?= test_rx_gearbox 14 | 15 | 16 | ifeq ($(MODULE), test_tx_gearbox) 17 | TOPLEVEL ?= tx_gearbox 18 | endif 19 | 20 | ifeq ($(MODULE), test_rx_gearbox) 21 | TOPLEVEL ?= rx_gearbox 22 | endif 23 | 24 | export REGISTER_OUTPUT ?= 0 25 | 26 | ifeq ($(SIM), icarus) 27 | PLUSARGS += -fst 28 | 29 | COMPILE_ARGS += -P $(TOPLEVEL).REGISTER_OUTPUT=$(REGISTER_OUTPUT) 30 | 31 | ifeq ($(WAVES), 1) 32 | VERILOG_SOURCES += iverilog_dump.v 33 | COMPILE_ARGS += -s iverilog_dump 34 | endif 35 | 36 | endif 37 | 38 | # include cocotb's make rules to take care of the simulator setup 39 | include $(shell cocotb-config --makefiles)/Makefile.sim 40 | 41 | 42 | iverilog_dump.v: 43 | echo 'module iverilog_dump();' > $@ 44 | echo 'initial begin' >> $@ 45 | echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ 46 | echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ 47 | echo 'end' >> $@ 48 | echo 'endmodule' >> $@ 49 | 50 | clean:: 51 | @rm -rf iverilog_dump.v 52 | @rm -rf crc32.mem 53 | @rm -rf dump.fst $(TOPLEVEL).fst -------------------------------------------------------------------------------- /src/tb/gearbox/test_gearboxes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from cocotb_test.simulator import run 5 | 6 | @pytest.mark.parametrize( 7 | "parameters", [ 8 | {"REGISTER_OUTPUT": "0"}, 9 | ]) 10 | def test_rx_gearbox(parameters): 11 | 12 | sim_build = "./sim_build/rx_gearbox" 13 | os.makedirs(sim_build, exist_ok=True) 14 | 15 | run( 16 | verilog_sources=['../../hdl/pcs/rx_gearbox.sv'], 17 | toplevel="rx_gearbox", 18 | 19 | module="test_rx_gearbox", 20 | simulator="icarus", 21 | verilog_compile_args=["-g2012"], 22 | includes=["../hdl", "../../", "../../../"], 23 | parameters=parameters, 24 | extra_env=parameters, 25 | sim_build=sim_build 26 | ) 27 | 28 | @pytest.mark.parametrize( 29 | "parameters", [ 30 | {"REGISTER_OUTPUT": "0"}, 31 | ]) 32 | def test_tx_gearbox(parameters): 33 | 34 | sim_build = "./sim_build/tx_gearbox" 35 | os.makedirs(sim_build, exist_ok=True) 36 | 37 | run( 38 | verilog_sources=['../../hdl/pcs/tx_gearbox.sv'], 39 | toplevel="tx_gearbox", 40 | 41 | module="test_tx_gearbox", 42 | simulator="icarus", 43 | verilog_compile_args=["-g2012"], 44 | includes=["../hdl", "../../", "../../../"], 45 | parameters=parameters, 46 | extra_env=parameters, 47 | sim_build=sim_build 48 | ) -------------------------------------------------------------------------------- /src/tb/mac/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= icarus 5 | TOPLEVEL_LANG ?= verilog 6 | WAVES ?= 1 7 | 8 | VERILOG_SOURCES += $(PWD)/../../hdl/include/code_defs_pkg.svh 9 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/mac.sv 10 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/tx_mac.sv 11 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/rx_mac.sv 12 | VERILOG_SOURCES += $(PWD)/../../lib/slicing_crc/hdl/slicing_crc.sv 13 | 14 | 15 | 16 | CUSTOM_SIM_DEPS += crc_tables.mem 17 | 18 | 19 | # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file 20 | TOPLEVEL = mac 21 | 22 | # MODULE is the basename of the Python test file 23 | MODULE = test_mac 24 | 25 | 26 | 27 | # module parameters 28 | export DATA_WIDTH ?= 32 29 | 30 | ifeq ($(SIM), icarus) 31 | PLUSARGS += -fst 32 | 33 | COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(DATA_WIDTH) 34 | COMPILE_ARGS += -I $(PWD)/../../hdl/include 35 | 36 | ifeq ($(WAVES), 1) 37 | VERILOG_SOURCES += iverilog_dump.v 38 | COMPILE_ARGS += -s iverilog_dump 39 | endif 40 | 41 | endif 42 | 43 | # include cocotb's make rules to take care of the simulator setup 44 | include $(shell cocotb-config --makefiles)/Makefile.sim 45 | 46 | 47 | 48 | crc_tables.mem: $(PWD)/../../lib/slicing_crc/hdl/crc_tables.mem 49 | cp $< $@ 50 | 51 | iverilog_dump.v: 52 | echo 'module iverilog_dump();' > $@ 53 | echo 'initial begin' >> $@ 54 | echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ 55 | echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ 56 | echo 'end' >> $@ 57 | echo 'endmodule' >> $@ 58 | 59 | clean:: 60 | @rm -rf iverilog_dump.v 61 | @rm -rf crc32.mem 62 | @rm -rf dump.fst $(TOPLEVEL).fst -------------------------------------------------------------------------------- /src/tb/pcs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= icarus 5 | TOPLEVEL_LANG ?= verilog 6 | WAVES ?= 1 7 | 8 | VERILOG_SOURCES += $(PWD)/../../hdl/include/code_defs_pkg.svh 9 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/pcs.sv 10 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/decoder.sv 11 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/encoder.sv 12 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/rx_gearbox.sv 13 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/tx_gearbox.sv 14 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/gearbox_seq.sv 15 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/lock_state.sv 16 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/scrambler.sv 17 | # use VHDL_SOURCES for VHDL files 18 | 19 | # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file 20 | TOPLEVEL = pcs 21 | 22 | # MODULE is the basename of the Python test file 23 | MODULE = test_pcs 24 | 25 | 26 | 27 | # module parameters 28 | export SCRAMBLER_BYPASS ?= 0 29 | export EXTERNAL_GEARBOX ?= 1 30 | export DATA_WIDTH ?= 32 31 | 32 | ifeq ($(SIM), icarus) 33 | PLUSARGS += -fst 34 | 35 | COMPILE_ARGS += -P $(TOPLEVEL).SCRAMBLER_BYPASS=$(SCRAMBLER_BYPASS) 36 | COMPILE_ARGS += -P $(TOPLEVEL).EXTERNAL_GEARBOX=$(EXTERNAL_GEARBOX) 37 | COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(DATA_WIDTH) 38 | COMPILE_ARGS += -I $(PWD)/../../hdl/include 39 | 40 | ifeq ($(WAVES), 1) 41 | VERILOG_SOURCES += iverilog_dump.v 42 | COMPILE_ARGS += -s iverilog_dump 43 | endif 44 | 45 | endif 46 | 47 | 48 | # include cocotb's make rules to take care of the simulator setup 49 | include $(shell cocotb-config --makefiles)/Makefile.sim 50 | 51 | 52 | iverilog_dump.v: 53 | echo 'module iverilog_dump();' > $@ 54 | echo 'initial begin' >> $@ 55 | echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ 56 | echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ 57 | echo 'end' >> $@ 58 | echo 'endmodule' >> $@ 59 | 60 | clean:: 61 | @rm -rf iverilog_dump.v 62 | @rm -rf dump.fst $(TOPLEVEL).fst -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 10G Low Latency Ethernet 2 | 3 | [![cocotb-test](https://github.com/ttchisholm/10g-low-latency-ethernet/actions/workflows/cocotb-test.yaml/badge.svg?branch=master)](https://github.com/ttchisholm/10g-low-latency-ethernet/actions/workflows/cocotb-test.yaml) 4 | 5 | ## Overview 6 | 7 | For more information, refer to my blog series for this project - [Designing a Low Latency 10G Ethernet Core - Part 1 (Introduction)](https://ttchisholm.github.io/ethernet/2023/05/01/designing-10g-eth-1.html) 8 | 9 | This repository contains: 10 | - A low latency 10G Ethernet MAC/PCS, written in SystemVerilog and tested with pyuvm/cocotb 11 | - An integrated low latency 10G Ethernet core, with MAC/PCS and GTY wrapper/IP for Xilinx UltraScale+ 12 | - An example design containing packet latency measurement in loopback 13 | 14 | Repository structure: 15 | 16 | ``` 17 | .github/ # GitHub workflow 18 | example/ # Example design 19 | src/ # Ethernet core source 20 | hdl/ # HDL source 21 | ip/ # IP generation 22 | lib/ # Submodules 23 | tb/ # Testbenches 24 | ``` 25 | 26 | ## Example Design 27 | 28 | **Building the example design:** 29 | 30 | 1. Clone the slicing_crc submodule 31 | 32 | ```console 33 | git submodule update --init --recursive 34 | ``` 35 | 36 | 2. Generate GTY IP. Set the FPGA part and GTY channel in *src/ip/gen_eth_10g_ip.sh* and run 37 | 38 | ```console 39 | cd src/ip 40 | ./gen_eth_10g_ip.sh 41 | ``` 42 | 43 | 3. Modify the constraints file *example/constraints/example_10g_eth.xdc* with appropriate pin assignments. The design requires a 100MHz clock input for initialisation and a low-jitter 156.25MHz clock for the transceivers 44 | 45 | 4. Build the example design. Set the FPGA part again in *example/build_example.sh* 46 | 47 | ```console 48 | cd example 49 | ./build_example.sh 50 | Vivado> all 51 | ``` 52 | **Running the example design:** 53 | 54 | 1. Program the device in Vivado Hardware Manager 55 | 2. Add the VIOs 56 | 3. De-assert *core_reset* in *hw_vio1 (u_core_reset_vio)* 57 | 4. De-assert *packet_gen_reset* in *hw_vio3 (u_packet_control_vio)* 58 | 5. Capture on *hw_ila_2 (tx_packet_ila)* to observe latency -------------------------------------------------------------------------------- /src/hdl/pcs/gearbox_seq.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: gearbox_seq 25 | * 26 | * Description: 64b666 gearbox sequence counter as per UG578 (v1.3.1) pg 119. 27 | * Note module was desinged for TX_DATA_WIDTH = 32, TX_INT_DATAWIDTH = 32. 28 | * 29 | */ 30 | 31 | `timescale 1ns/1ps 32 | `default_nettype none 33 | 34 | module gearbox_seq #( 35 | parameter int WIDTH = 6, 36 | parameter bit [WIDTH-1:0] MAX_VAL = 32, 37 | parameter bit [WIDTH-1:0] PAUSE_VAL = 32, 38 | parameter bit HALF_STEP = 1 39 | ) ( 40 | input wire i_clk, 41 | input wire i_reset, 42 | input wire i_slip, 43 | output logic [WIDTH-1:0] o_count, 44 | output wire o_pause 45 | ); 46 | 47 | logic step; 48 | 49 | always_ff @(posedge i_clk) 50 | if (i_reset) begin 51 | o_count <= '0; 52 | end else begin 53 | if (step && !i_slip) begin 54 | o_count <= (o_count < MAX_VAL) ? o_count + 1 : '0; 55 | end 56 | end 57 | 58 | generate if (HALF_STEP) begin: l_half_step 59 | always_ff @(posedge i_clk) 60 | if (i_reset) begin 61 | step <= '0; 62 | end else if (!i_slip) begin 63 | step <= ~step; 64 | end 65 | end else begin: l_full_step 66 | assign step = 1'b1; 67 | end endgenerate 68 | 69 | assign o_pause = o_count == PAUSE_VAL; 70 | 71 | endmodule 72 | -------------------------------------------------------------------------------- /example/hdl/eth_perf.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: eth_perf 25 | * 26 | * Description: Measure time between i_tx_start and i_rx_stop with CDC. 27 | * 28 | */ 29 | 30 | `timescale 1ns/1ps 31 | `default_nettype none 32 | 33 | module eth_perf ( 34 | input wire i_tx_reset, 35 | input wire i_tx_clk, 36 | input wire i_tx_start, 37 | output logic [15:0] o_latency, 38 | output logic o_test_complete, 39 | 40 | input wire i_rx_stop 41 | ); 42 | 43 | logic test_running; 44 | logic [1:0] rx_stop_sync; 45 | 46 | always_ff @(posedge i_tx_clk) 47 | if (i_tx_reset) begin 48 | test_running <= '0; 49 | rx_stop_sync <= 2'b0; 50 | o_latency <= '0; 51 | o_test_complete <= '0; 52 | end else begin 53 | rx_stop_sync <= {rx_stop_sync[0], i_rx_stop}; 54 | 55 | if (!test_running && i_tx_start) begin 56 | test_running <= 1'b1; 57 | o_test_complete <= 1'b0; 58 | o_latency <= '0; 59 | end else if (test_running && rx_stop_sync[1]) begin 60 | test_running <= 1'b0; 61 | o_test_complete <= 1'b1; 62 | o_latency <= o_latency; 63 | end else if (test_running) begin 64 | test_running <= test_running; 65 | o_test_complete <= 1'b0; 66 | o_latency <= o_latency + 1; 67 | end 68 | end 69 | 70 | endmodule 71 | -------------------------------------------------------------------------------- /src/tb/mac_pcs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | # defaults 4 | SIM ?= icarus 5 | TOPLEVEL_LANG ?= verilog 6 | WAVES ?= 1 7 | 8 | VERILOG_SOURCES += $(PWD)/../../hdl/include/code_defs_pkg.svh 9 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/pcs.sv 10 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/decoder.sv 11 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/encoder.sv 12 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/rx_gearbox.sv 13 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/tx_gearbox.sv 14 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/gearbox_seq.sv 15 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/lock_state.sv 16 | VERILOG_SOURCES += $(PWD)/../../hdl/pcs/scrambler.sv 17 | 18 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/mac.sv 19 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/tx_mac.sv 20 | VERILOG_SOURCES += $(PWD)/../../hdl/mac/rx_mac.sv 21 | 22 | VERILOG_SOURCES += $(PWD)/../../hdl/mac_pcs.sv 23 | 24 | VERILOG_SOURCES += $(PWD)/../../lib/slicing_crc/hdl/slicing_crc.sv 25 | 26 | 27 | CUSTOM_SIM_DEPS += crc_tables.mem 28 | 29 | 30 | # TOPLEVEL is the name of the toplevel module in your Verilog or VHDL file 31 | TOPLEVEL = mac_pcs 32 | 33 | # MODULE is the basename of the Python test file 34 | MODULE = test_mac_pcs 35 | 36 | # module parameters 37 | export SCRAMBLER_BYPASS ?= 0 38 | export EXTERNAL_GEARBOX ?= 0 39 | export DATA_WIDTH ?= 32 40 | 41 | ifeq ($(SIM), icarus) 42 | PLUSARGS += -fst 43 | 44 | COMPILE_ARGS += -P $(TOPLEVEL).SCRAMBLER_BYPASS=$(SCRAMBLER_BYPASS) 45 | COMPILE_ARGS += -P $(TOPLEVEL).EXTERNAL_GEARBOX=$(EXTERNAL_GEARBOX) 46 | COMPILE_ARGS += -I $(PWD)/../../hdl/include 47 | 48 | ifeq ($(WAVES), 1) 49 | VERILOG_SOURCES += iverilog_dump.v 50 | COMPILE_ARGS += -s iverilog_dump 51 | endif 52 | else ifeq ($(SIM), modelsim) 53 | COMPILE_ARGS += "+incdir+$(PWD)/../../hdl/include" 54 | COMPILE_ARGS += +cover=sbceft 55 | 56 | SIM_ARGS += -GSCRAMBLER_BYPASS=$(SCRAMBLER_BYPASS) 57 | SIM_ARGS += -GEXTERNAL_GEARBOX=$(EXTERNAL_GEARBOX) 58 | 59 | SIM_ARGS += -coverage 60 | SIM_ARGS += -no_autoacc 61 | SIM_ARGS += -do \" coverage save -onexit $(TOPLEVEL).ucdb; run -all;exit\" 62 | endif 63 | # "Veriliator currently does not work with cocotb verilog-axi" 64 | # else ifeq ($(SIM), verilator) 65 | 66 | # COMPILE_ARGS += -GSCRAMBLER_BYPASS=$(SCRAMBLER_BYPASS) 67 | # COMPILE_ARGS += -GEXTERNAL_GEARBOX=$(EXTERNAL_GEARBOX) 68 | # COMPILE_ARGS += -I$(PWD)/../../hdl/include 69 | 70 | # ifeq ($(WAVES), 1) 71 | # COMPILE_ARGS += --trace-fst --trace-structs 72 | # endif 73 | # endif 74 | 75 | # include cocotb's make rules to take care of the simulator setup 76 | include $(shell cocotb-config --makefiles)/Makefile.sim 77 | 78 | crc_tables.mem: $(PWD)/../../lib/slicing_crc/hdl/crc_tables.mem 79 | cp $< $@ 80 | 81 | iverilog_dump.v: 82 | echo 'module iverilog_dump();' > $@ 83 | echo 'initial begin' >> $@ 84 | echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ 85 | echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ 86 | echo 'end' >> $@ 87 | echo 'endmodule' >> $@ 88 | 89 | clean:: 90 | @rm -rf iverilog_dump.v 91 | @rm -rf crc_tables.mem 92 | @rm -rf dump.fst $(TOPLEVEL).fst -------------------------------------------------------------------------------- /src/hdl/pcs/scrambler.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: scrambler 25 | * 26 | * Description: 10G Ethernet scrambler/descrabler as per IEEE 802.3-2008, 49.2.6 and 27 | * 49.2.11. 32-bit data I/O. 28 | * 29 | */ 30 | 31 | `timescale 1ns/1ps 32 | `default_nettype none 33 | 34 | module scrambler #( 35 | parameter bit DESCRAMBLE = 0, 36 | 37 | localparam int DATA_WIDTH = 32 38 | ) ( 39 | input wire i_clk, 40 | input wire i_reset, 41 | input wire i_init_done, 42 | input wire i_pause, 43 | input wire [DATA_WIDTH-1:0] i_data, 44 | output wire [DATA_WIDTH-1:0] o_data 45 | ); 46 | 47 | // verilator lint_off UNUSED 48 | logic[127:0] scrambler_data; 49 | // verilator lint_on UNUSED 50 | 51 | logic [127:0] next_scrambler_data; 52 | logic [95:0] next_scrambler_data_split; 53 | 54 | always_ff @(posedge i_clk) begin 55 | if (i_reset || !i_init_done) begin 56 | scrambler_data <= '1; 57 | end 58 | else if (!i_pause) begin 59 | scrambler_data <= next_scrambler_data; 60 | end 61 | end 62 | 63 | // Data here is reversed wrt. polynomial index 64 | // We need to split the scrambler data to avoid circular comb (verilator) 65 | // Shift the scrambler data down by DATA_WIDTH 66 | assign next_scrambler_data_split = {scrambler_data[DATA_WIDTH +: 128 - DATA_WIDTH]}; 67 | 68 | // If descrambling, shift in input data, else scrambler output 69 | assign next_scrambler_data = DESCRAMBLE ? {i_data, next_scrambler_data_split} : 70 | {o_data, next_scrambler_data_split}; 71 | 72 | // Parallel scrambler 73 | // Polynomial is 1 + x^39 + x^58, easier to write as inverse 1 + x^19 + x^58 74 | // and say S0 is first transmitted bit (lsb) 75 | // S58 = D58 + S19 + S0 76 | // ... 77 | // S64 = D64 + S25 + S6 78 | // S65 = D65 + S26 + S7 79 | // ... 80 | // S127 = D127 + S88 + S69 81 | 82 | // For 32-bit mode, as we only shift the scrambler data by 32 each time, need to offset index with (64-DATA_WIDTH) 83 | 84 | generate 85 | for (genvar gi = 0; gi < DATA_WIDTH; gi++) begin: l_assign_odata 86 | assign o_data[gi] = next_scrambler_data_split[(64-DATA_WIDTH) + 6+gi] ^ next_scrambler_data_split[(64-DATA_WIDTH) + 25+gi] ^ i_data[gi]; 87 | end 88 | endgenerate 89 | 90 | endmodule 91 | -------------------------------------------------------------------------------- /src/tb/gearbox/test_tx_gearbox.py: -------------------------------------------------------------------------------- 1 | from asyncore import loop 2 | import asyncio 3 | import enum 4 | import cocotb 5 | import numpy as np 6 | from gearbox_model import TxGearboxModel 7 | 8 | from cocotb.triggers import Timer, RisingEdge, FallingEdge, Edge, NextTimeStep 9 | from cocotb.clock import Clock 10 | 11 | import debugpy 12 | 13 | class TxGearboxTb: 14 | def __init__(self, dut): 15 | self.dut = dut 16 | 17 | self.clk_period = round(1 / (10.3125 / 32), 2) # ps precision 18 | cocotb.start_soon(Clock(dut.i_clk, self.clk_period, units="ns").start()) 19 | self.dut.i_reset.value = 1 20 | self.dut.i_data.value = 0 21 | self.dut.i_header.value = 0 22 | 23 | 24 | async def reset(self): 25 | self.dut.i_reset.value = 0 26 | await RisingEdge(self.dut.i_clk) 27 | self.dut.i_reset.value = 1 28 | await RisingEdge(self.dut.i_clk) 29 | self.dut.i_reset.value = 0 30 | 31 | 32 | 33 | @cocotb.test() 34 | async def tx_gearbox_test(dut): 35 | 36 | # debugpy.listen(5678) 37 | # debugpy.wait_for_client() 38 | # debugpy.breakpoint() 39 | 40 | 41 | tb = TxGearboxTb(dut) 42 | 43 | await tb.reset() 44 | 45 | # Generate random data 46 | np.random.seed(0) 47 | gen_idata = [np.random.randint(0,2,64) for _ in range(200)] 48 | gen_iheader = [np.random.randint(0,2,2) for _ in range(200)] 49 | 50 | # gen_iheader = [[f'{y:02d}-H0{x}' for x in range(2)] for y in range(20)] 51 | # gen_idata = [[f'{y:02d}-D{x:02d}' for x in range(64)] for y in range(20)] 52 | 53 | # Create ref model 54 | model = TxGearboxModel('str') 55 | 56 | 57 | async def run_cycle(iheader, idata): 58 | 59 | for i in range(len(iheader)): 60 | tb.dut.i_header[i].value = int(iheader[i]) 61 | 62 | for i in range(len(idata)): 63 | tb.dut.i_data[i].value = int(idata[i]) 64 | 65 | tb.dut.i_gearbox_seq.value = model.get_count() 66 | tb.dut.i_pause.value = model.get_pause() 67 | 68 | await FallingEdge(tb.dut.i_clk) # Give the sim a tick to update the comb outputs 69 | 70 | dut_odata = str(tb.dut.o_data.value)[::-1] 71 | dut_obuf = str(tb.dut.next_obuf.value)[::-1] 72 | 73 | await RisingEdge(tb.dut.i_clk) 74 | 75 | ret = model.next(iheader, idata) 76 | 77 | model_odata = ''.join([str(x) for x in ret['data']]) 78 | model_obuf = ''.join([str(x) for x in ret['obuf']]) 79 | 80 | all_eq = model_odata == dut_odata 81 | 82 | if not all_eq: 83 | print('OK' if all_eq else 'FAIL') 84 | print('seq: ', int(tb.dut.i_gearbox_seq.value)) 85 | print('pause: ', tb.dut.i_pause.value) 86 | print('dut data: ', dut_odata) 87 | print('model data: ', model_odata) 88 | print('input data: ', str(tb.dut.i_data.value)[::-1]) 89 | print('input header: ', str(tb.dut.i_header.value)[::-1]) 90 | 91 | assert all_eq 92 | 93 | return ret 94 | 95 | 96 | 97 | tb.dut.i_data.value = 0 98 | 99 | for ih, id in zip(gen_iheader, gen_idata): 100 | for _ in range(2): 101 | 102 | idata = id[32:] if model.get_frame_word() else id[:32] 103 | 104 | ret = await run_cycle(ih, idata) 105 | 106 | if (ret['pause']): # do another cycle with same data 107 | ret = await run_cycle(ih, idata) 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/hdl/serdes/gtwizard_ultrascale_0_example_bit_sync.v: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // (c) Copyright 2013-2018 Xilinx, Inc. All rights reserved. 3 | // 4 | // This file contains confidential and proprietary information 5 | // of Xilinx, Inc. and is protected under U.S. and 6 | // international copyright and other intellectual property 7 | // laws. 8 | // 9 | // DISCLAIMER 10 | // This disclaimer is not a license and does not grant any 11 | // rights to the materials distributed herewith. Except as 12 | // otherwise provided in a valid license issued to you by 13 | // Xilinx, and to the maximum extent permitted by applicable 14 | // law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND 15 | // WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES 16 | // AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 17 | // BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- 18 | // INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and 19 | // (2) Xilinx shall not be liable (whether in contract or tort, 20 | // including negligence, or under any other theory of 21 | // liability) for any loss or damage of any kind or nature 22 | // related to, arising under or in connection with these 23 | // materials, including for any direct, or any indirect, 24 | // special, incidental, or consequential loss or damage 25 | // (including loss of data, profits, goodwill, or any type of 26 | // loss or damage suffered as a result of any action brought 27 | // by a third party) even if such damage or loss was 28 | // reasonably foreseeable or Xilinx had been advised of the 29 | // possibility of the same. 30 | // 31 | // CRITICAL APPLICATIONS 32 | // Xilinx products are not designed or intended to be fail- 33 | // safe, or for use in any application requiring fail-safe 34 | // performance, such as life-support or safety devices or 35 | // systems, Class III medical devices, nuclear facilities, 36 | // applications related to the deployment of airbags, or any 37 | // other applications that could lead to death, personal 38 | // injury, or severe property or environmental damage 39 | // (individually and collectively, "Critical 40 | // Applications"). Customer assumes the sole risk and 41 | // liability of any use of Xilinx products in Critical 42 | // Applications, subject only to applicable laws and 43 | // regulations governing limitations on product liability. 44 | // 45 | // THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS 46 | // PART OF THIS FILE AT ALL TIMES. 47 | //------------------------------------------------------------------------------ 48 | 49 | 50 | `timescale 1ps/1ps 51 | 52 | // ********************************************************************************************************************* 53 | // IMPORTANT 54 | // This block is delivered within the example design. If you wish to modify its behavior, be careful to understand the 55 | // existing behavior and the effects of any modifications you may choose to make. 56 | // ********************************************************************************************************************* 57 | 58 | module gtwizard_ultrascale_0_example_bit_synchronizer # ( 59 | 60 | parameter INITIALIZE = 5'b00000, 61 | parameter FREQUENCY = 512 62 | 63 | )( 64 | 65 | input wire clk_in, 66 | input wire i_in, 67 | output wire o_out 68 | 69 | ); 70 | 71 | // Use 5 flip-flops as a single synchronizer, and tag each declaration with the appropriate synthesis attribute to 72 | // enable clustering. Their GSR default values are provided by the INITIALIZE parameter. 73 | 74 | (* ASYNC_REG = "TRUE" *) reg i_in_meta = INITIALIZE[0]; 75 | (* ASYNC_REG = "TRUE" *) reg i_in_sync1 = INITIALIZE[1]; 76 | (* ASYNC_REG = "TRUE" *) reg i_in_sync2 = INITIALIZE[2]; 77 | (* ASYNC_REG = "TRUE" *) reg i_in_sync3 = INITIALIZE[3]; 78 | reg i_in_out = INITIALIZE[4]; 79 | 80 | always @(posedge clk_in) begin 81 | i_in_meta <= i_in; 82 | i_in_sync1 <= i_in_meta; 83 | i_in_sync2 <= i_in_sync1; 84 | i_in_sync3 <= i_in_sync2; 85 | i_in_out <= i_in_sync3; 86 | end 87 | 88 | assign o_out = i_in_out; 89 | 90 | 91 | endmodule 92 | -------------------------------------------------------------------------------- /src/hdl/include/code_defs_pkg.svh: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Package: code_defs_pkg 25 | * 26 | * Description: Collection of definitions for 10G Ethernet 27 | * 28 | */ 29 | 30 | `ifndef CODE_DEFS_PACKAGE 31 | `define CODE_DEFS_PACKAGE 32 | 33 | package code_defs_pkg; 34 | 35 | typedef enum logic [1:0] 36 | { SYNC_DATA = 2'b10 37 | , SYNC_CTL = 2'b01 38 | } sync_t; 39 | 40 | // Block Type 41 | typedef enum logic [7:0] 42 | { BT_IDLE = 8'h1e 43 | , BT_O4 = 8'h2d 44 | , BT_S4 = 8'h33 45 | , BT_O0S4 = 8'h66 46 | , BT_O0O4 = 8'h55 47 | , BT_S0 = 8'h78 48 | , BT_O0 = 8'h4b 49 | , BT_T0 = 8'h87 50 | , BT_T1 = 8'h99 51 | , BT_T2 = 8'haa 52 | , BT_T3 = 8'hb4 53 | , BT_T4 = 8'hcc 54 | , BT_T5 = 8'hd2 55 | , BT_T6 = 8'he1 56 | , BT_T7 = 8'hff 57 | } block_type_t; 58 | 59 | // Control Codes 60 | typedef enum logic [6:0] 61 | { CC_IDLE = 7'b00 62 | , CC_LPI = 7'h06 63 | , CC_ERROR = 7'h1e 64 | , CC_RES0 = 7'h2d 65 | , CC_RES1 = 7'h33 66 | , CC_RES2 = 7'h4b 67 | , CC_RES3 = 7'h55 68 | , CC_RES4 = 7'h66 69 | , CC_RES5 = 7'h78 70 | } control_code_t; 71 | 72 | // O-Codes 73 | typedef enum logic [3:0] 74 | { OC_SEQ = 4'h0 75 | , OC_SIG = 4'hf 76 | } o_code_t; 77 | 78 | // RS Codes 79 | typedef enum logic [7:0] 80 | { RS_IDLE = 8'h07 81 | , RS_LPI = 8'h06 82 | , RS_START = 8'hfb 83 | , RS_TERM = 8'hfd 84 | , RS_ERROR = 8'hfe 85 | , RS_OSEQ = 8'h9c 86 | , RS_RES0 = 8'h1c 87 | , RS_RES1 = 8'h3c 88 | , RS_RES2 = 8'h7c 89 | , RS_RES3 = 8'hbc 90 | , RS_RES4 = 8'hdc 91 | , RS_RES5 = 8'hf7 92 | , RS_OSIG = 8'h5c 93 | } rs_code_t; 94 | 95 | // MAC Codes 96 | typedef enum logic [7:0] 97 | { MAC_PRE = 8'h55 98 | , MAC_SFD = 8'hd5 99 | } mac_code_t; 100 | 101 | // todo can we use enum types? 102 | function automatic logic [7:0] control_to_rs_code(logic [6:0] icode); 103 | case (icode) 104 | CC_IDLE: return RS_IDLE; 105 | CC_LPI: return RS_LPI; 106 | CC_ERROR: return RS_ERROR; 107 | CC_RES0: return RS_RES0; 108 | CC_RES1: return RS_RES1; 109 | CC_RES2: return RS_RES2; 110 | CC_RES3: return RS_RES3; 111 | CC_RES4: return RS_RES4; 112 | CC_RES5: return RS_RES5; 113 | default: begin 114 | return RS_IDLE; 115 | end 116 | endcase 117 | endfunction 118 | 119 | function automatic logic [3:0] rs_to_cc_ocode (input logic [7:0] rs_code); 120 | return rs_code == RS_OSEQ ? OC_SEQ : OC_SIG; 121 | endfunction 122 | 123 | function automatic logic [7:0] cc_to_rs_ocode (input logic [3:0] cc_ocode); 124 | return cc_ocode == OC_SEQ ? RS_OSEQ : RS_OSIG; 125 | endfunction 126 | 127 | endpackage 128 | 129 | `endif 130 | -------------------------------------------------------------------------------- /src/tb/pcs/pcs_test_vector.py: -------------------------------------------------------------------------------- 1 | 2 | # https://www.ieee802.org/3/10GEPON_study/public/july06/thaler_1_0706.pdf 3 | 4 | class PCSTestVector: 5 | 6 | eg_xgmii_data = {64: [ 7 | (int("0b11111111", 2), int("0x0707070707070707", 16)), 8 | (int("0b00000001", 2), int("0xd5555555555555fb", 16)), 9 | (int("0b00000000", 2), int("0x8b0e380577200008", 16)), 10 | (int("0b00000000", 2), int("0x0045000800000000", 16)), 11 | (int("0b00000000", 2), int("0x061b0000661c2800", 16)), 12 | (int("0b00000000", 2), int("0x00004d590000d79e", 16)), 13 | (int("0b00000000", 2), int("0x0000eb4a2839d168", 16)), 14 | (int("0b00000000", 2), int("0x12500c7a00007730", 16)), 15 | (int("0b00000000", 2), int("0x000000008462d21e", 16)), 16 | (int("0b00000000", 2), int("0x79f7eb9300000000", 16)), 17 | (int("0b11111111", 2), int("0x07070707070707fd", 16)), 18 | (int("0b11111111", 2), int("0x0707070707070707", 16)) 19 | ], 20 | 32: [ 21 | (int("0b1111", 2), int("0x07070707", 16)), 22 | (int("0b1111", 2), int("0x07070707", 16)), 23 | (int("0b0001", 2), int("0x555555fb", 16)), 24 | (int("0b0000", 2), int("0xd5555555", 16)), 25 | (int("0b0000", 2), int("0x77200008", 16)), 26 | (int("0b0000", 2), int("0x8b0e3805", 16)), 27 | (int("0b0000", 2), int("0x00000000", 16)), 28 | (int("0b0000", 2), int("0x00450008", 16)), 29 | (int("0b0000", 2), int("0x661c2800", 16)), 30 | (int("0b0000", 2), int("0x061b0000", 16)), 31 | (int("0b0000", 2), int("0x0000d79e", 16)), 32 | (int("0b0000", 2), int("0x00004d59", 16)), 33 | (int("0b0000", 2), int("0x2839d168", 16)), 34 | (int("0b0000", 2), int("0x0000eb4a", 16)), 35 | (int("0b0000", 2), int("0x00007730", 16)), 36 | (int("0b0000", 2), int("0x12500c7a", 16)), 37 | (int("0b0000", 2), int("0x8462d21e", 16)), 38 | (int("0b0000", 2), int("0x00000000", 16)), 39 | (int("0b0000", 2), int("0x00000000", 16)), 40 | (int("0b0000", 2), int("0x79f7eb93", 16)), 41 | (int("0b1111", 2), int("0x070707fd", 16)), 42 | (int("0b1111", 2), int("0x07070707", 16)), 43 | (int("0b1111", 2), int("0x07070707", 16)), 44 | (int("0b1111", 2), int("0x07070707", 16)) 45 | ]} 46 | 47 | eg_scrambled_data = {64: [ 48 | (int("0b01", 2), int("0x7bfff0800000001e", 16)), 49 | (int("0b01", 2), int("0x623016aaaaad1578", 16)), 50 | (int("0b10", 2), int("0x6a767c6ec581e108", 16)), 51 | (int("0b10", 2), int("0x8df4aacc802830e6", 16)), 52 | (int("0b10", 2), int("0x2cdb936dae49ee83", 16)), 53 | (int("0b10", 2), int("0x74905a82db7046f3", 16)), 54 | (int("0b10", 2), int("0xc57a251a6b79511e", 16)), 55 | (int("0b10", 2), int("0x4aca440cd4bf1f41", 16)), 56 | (int("0b10", 2), int("0x2c3f2db5d2122809", 16)), 57 | (int("0b10", 2), int("0x320e33b3c8de9249", 16)), 58 | (int("0b01", 2), int("0xb599add7c83aa32a", 16)) 59 | ], 60 | 32:[ 61 | 62 | (int("0b01", 2), int("0x0000001e", 16)), 63 | (int("0b01", 2), int("0x7bfff080", 16)), 64 | (int("0b01", 2), int("0xaaad1578", 16)), 65 | (int("0b01", 2), int("0x623016aa", 16)), 66 | (int("0b10", 2), int("0xc581e108", 16)), 67 | (int("0b10", 2), int("0x6a767c6e", 16)), 68 | (int("0b10", 2), int("0x802830e6", 16)), 69 | (int("0b10", 2), int("0x8df4aacc", 16)), 70 | (int("0b10", 2), int("0xae49ee83", 16)), 71 | (int("0b10", 2), int("0x2cdb936d", 16)), 72 | (int("0b10", 2), int("0xdb7046f3", 16)), 73 | (int("0b10", 2), int("0x74905a82", 16)), 74 | (int("0b10", 2), int("0x6b79511e", 16)), 75 | (int("0b10", 2), int("0xc57a251a", 16)), 76 | (int("0b10", 2), int("0xd4bf1f41", 16)), 77 | (int("0b10", 2), int("0x4aca440c", 16)), 78 | (int("0b10", 2), int("0xd2122809", 16)), 79 | (int("0b10", 2), int("0x2c3f2db5", 16)), 80 | (int("0b10", 2), int("0xc8de9249", 16)), 81 | (int("0b10", 2), int("0x320e33b3", 16)), 82 | (int("0b01", 2), int("0xc83aa32a", 16)), 83 | (int("0b01", 2), int("0xb599add7", 16)) 84 | ]} 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/ip/eth_10g_ip.tcl: -------------------------------------------------------------------------------- 1 | if {[info exists env(ETH10G_FPGA_PART)]} { 2 | set FPGA_PART $env(ETH10G_FPGA_PART) 3 | puts "FPGA_PART = ${FPGA_PART}" 4 | } else { 5 | puts "Environment variable ETH10G_FPGA_PART not set, generate IP from shell script." 6 | exit 1 7 | } 8 | 9 | if {[info exists env(ETH10G_CHANNEL)]} { 10 | set CHANNEL $env(ETH10G_CHANNEL) 11 | puts "CHANNEL = ${CHANNEL}" 12 | } else { 13 | puts "Environment variable ETH10G_CHANNEL not set, generate IP from shell script." 14 | exit 1 15 | } 16 | 17 | # IP project setup 18 | create_project -in_memory -part $FPGA_PART 19 | set_property target_language Verilog [current_project] 20 | set_property source_mgmt_mode All [current_project] 21 | 22 | # Transceiver - with gearbox 23 | create_ip -name gtwizard_ultrascale -vendor xilinx.com -library ip -version 1.7 -module_name gtwizard_ultrascale_inc_gearbox -dir . -force 24 | set_property -dict [list CONFIG.CHANNEL_ENABLE ${CHANNEL} CONFIG.TX_MASTER_CHANNEL ${CHANNEL} CONFIG.RX_MASTER_CHANNEL ${CHANNEL} \ 25 | CONFIG.TX_REFCLK_FREQUENCY {156.25} CONFIG.TX_DATA_ENCODING {64B66B} CONFIG.TX_USER_DATA_WIDTH {32} \ 26 | CONFIG.TX_BUFFER_MODE {0} CONFIG.TX_OUTCLK_SOURCE {TXPROGDIVCLK} CONFIG.RX_REFCLK_FREQUENCY {156.25} \ 27 | CONFIG.RX_DATA_DECODING {64B66B} CONFIG.RX_USER_DATA_WIDTH {32} CONFIG.RX_INT_DATA_WIDTH {32} \ 28 | CONFIG.RX_BUFFER_MODE {0} CONFIG.RX_REFCLK_SOURCE {} CONFIG.TX_REFCLK_SOURCE {} \ 29 | CONFIG.LOCATE_TX_USER_CLOCKING {CORE} CONFIG.LOCATE_RX_USER_CLOCKING {CORE} \ 30 | CONFIG.TXPROGDIV_FREQ_ENABLE {false} CONFIG.FREERUN_FREQUENCY {100} CONFIG.ENABLE_OPTIONAL_PORTS {loopback_in}] [get_ips gtwizard_ultrascale_inc_gearbox] 31 | generate_target all [get_ips gtwizard_ultrascale_inc_gearbox] 32 | 33 | # Transceiver - without gearbox 34 | create_ip -name gtwizard_ultrascale -vendor xilinx.com -library ip -version 1.7 -module_name gtwizard_ultrascale_raw -dir . -force 35 | set_property -dict [list CONFIG.CHANNEL_ENABLE ${CHANNEL} CONFIG.TX_MASTER_CHANNEL ${CHANNEL} CONFIG.RX_MASTER_CHANNEL ${CHANNEL} \ 36 | CONFIG.TX_REFCLK_FREQUENCY {156.25} CONFIG.TX_DATA_ENCODING {RAW} CONFIG.TX_USER_DATA_WIDTH {32} \ 37 | CONFIG.TX_BUFFER_MODE {0} CONFIG.TX_OUTCLK_SOURCE {TXPROGDIVCLK} CONFIG.RX_REFCLK_FREQUENCY {156.25} \ 38 | CONFIG.RX_DATA_DECODING {RAW} CONFIG.RX_USER_DATA_WIDTH {32} CONFIG.RX_INT_DATA_WIDTH {32} \ 39 | CONFIG.RX_BUFFER_MODE {0} CONFIG.RX_REFCLK_SOURCE {} CONFIG.TX_REFCLK_SOURCE {} \ 40 | CONFIG.LOCATE_TX_USER_CLOCKING {CORE} CONFIG.LOCATE_RX_USER_CLOCKING {CORE} \ 41 | CONFIG.TXPROGDIV_FREQ_ENABLE {false} CONFIG.FREERUN_FREQUENCY {100} CONFIG.ENABLE_OPTIONAL_PORTS {loopback_in}] [get_ips gtwizard_ultrascale_raw] 42 | generate_target all [get_ips gtwizard_ultrascale_raw] 43 | 44 | 45 | # Transceiver bringup VIO 46 | 47 | create_ip -name vio -vendor xilinx.com -library ip -version 3.0 -module_name gtwizard_ultrascale_0_vio_0 -dir . -force 48 | set_property -dict [list CONFIG.C_PROBE_IN7_WIDTH {2} CONFIG.C_PROBE_IN6_WIDTH {2} CONFIG.C_PROBE_IN5_WIDTH {2} CONFIG.C_PROBE_IN4_WIDTH {2} \ 49 | CONFIG.C_PROBE_IN3_WIDTH {4} CONFIG.C_PROBE_OUT6_INIT_VAL {0x2} CONFIG.C_PROBE_OUT6_WIDTH {3} \ 50 | CONFIG.C_NUM_PROBE_OUT {7} CONFIG.C_NUM_PROBE_IN {14} \ 51 | CONFIG.Component_Name {gtwizard_ultrascale_0_vio_0}] [get_ips gtwizard_ultrascale_0_vio_0] 52 | generate_target all [get_ips gtwizard_ultrascale_0_vio_0] 53 | 54 | 55 | # Example core control VIO 56 | 57 | create_ip -name vio -vendor xilinx.com -library ip -version 3.0 -module_name eth_core_control_vio -dir . -force 58 | set_property -dict [list CONFIG.C_PROBE_OUT2_INIT_VAL {0xBEEF0000} CONFIG.C_PROBE_OUT1_INIT_VAL {0x100} CONFIG.C_PROBE_OUT0_INIT_VAL {0x0} CONFIG.C_PROBE_OUT2_WIDTH {32} CONFIG.C_PROBE_OUT1_WIDTH {16} CONFIG.C_NUM_PROBE_OUT {3} CONFIG.C_EN_PROBE_IN_ACTIVITY {0} CONFIG.C_NUM_PROBE_IN {0}] [get_ips eth_core_control_vio] 59 | generate_target all [get_ips eth_core_control_vio] 60 | 61 | # Packet monitor ILAs 62 | 63 | create_ip -name ila -vendor xilinx.com -library ip -version 6.2 -module_name example_packet_ila -dir . -force 64 | set_property -dict [list CONFIG.C_DATA_DEPTH {131072} CONFIG.C_NUM_OF_PROBES {7} CONFIG.C_PROBE0_WIDTH {32} CONFIG.C_PROBE1_WIDTH {4} CONFIG.C_PROBE5_WIDTH {16}] [get_ips example_packet_ila] 65 | generate_target all [get_ips example_packet_ila] 66 | 67 | exit 0 -------------------------------------------------------------------------------- /src/hdl/serdes/gtwizard_ultrascale_0_example_reset_sync.v: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // (c) Copyright 2013-2018 Xilinx, Inc. All rights reserved. 3 | // 4 | // This file contains confidential and proprietary information 5 | // of Xilinx, Inc. and is protected under U.S. and 6 | // international copyright and other intellectual property 7 | // laws. 8 | // 9 | // DISCLAIMER 10 | // This disclaimer is not a license and does not grant any 11 | // rights to the materials distributed herewith. Except as 12 | // otherwise provided in a valid license issued to you by 13 | // Xilinx, and to the maximum extent permitted by applicable 14 | // law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND 15 | // WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES 16 | // AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 17 | // BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- 18 | // INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and 19 | // (2) Xilinx shall not be liable (whether in contract or tort, 20 | // including negligence, or under any other theory of 21 | // liability) for any loss or damage of any kind or nature 22 | // related to, arising under or in connection with these 23 | // materials, including for any direct, or any indirect, 24 | // special, incidental, or consequential loss or damage 25 | // (including loss of data, profits, goodwill, or any type of 26 | // loss or damage suffered as a result of any action brought 27 | // by a third party) even if such damage or loss was 28 | // reasonably foreseeable or Xilinx had been advised of the 29 | // possibility of the same. 30 | // 31 | // CRITICAL APPLICATIONS 32 | // Xilinx products are not designed or intended to be fail- 33 | // safe, or for use in any application requiring fail-safe 34 | // performance, such as life-support or safety devices or 35 | // systems, Class III medical devices, nuclear facilities, 36 | // applications related to the deployment of airbags, or any 37 | // other applications that could lead to death, personal 38 | // injury, or severe property or environmental damage 39 | // (individually and collectively, "Critical 40 | // Applications"). Customer assumes the sole risk and 41 | // liability of any use of Xilinx products in Critical 42 | // Applications, subject only to applicable laws and 43 | // regulations governing limitations on product liability. 44 | // 45 | // THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS 46 | // PART OF THIS FILE AT ALL TIMES. 47 | //------------------------------------------------------------------------------ 48 | 49 | 50 | `timescale 1ps/1ps 51 | 52 | // ********************************************************************************************************************* 53 | // IMPORTANT 54 | // This block is delivered within the example design. If you wish to modify its behavior, be careful to understand the 55 | // existing behavior and the effects of any modifications you may choose to make. 56 | // ********************************************************************************************************************* 57 | 58 | module gtwizard_ultrascale_0_example_reset_synchronizer # ( 59 | 60 | parameter FREQUENCY = 512 61 | 62 | )( 63 | 64 | input wire clk_in, 65 | input wire rst_in, 66 | output wire rst_out 67 | 68 | ); 69 | 70 | // Use 5 flip-flops as a single synchronizer, and tag each declaration with the appropriate synthesis attribute to 71 | // enable clustering. Each flip-flop in the synchronizer is asynchronously reset so that the downstream logic is also 72 | // asynchronously reset but encounters no reset assertion latency. The removal of reset is synchronous, so that the 73 | // downstream logic is also removed from reset synchronously. This module is designed for active-high reset use. 74 | 75 | (* ASYNC_REG = "TRUE" *) reg rst_in_meta = 1'b0; 76 | (* ASYNC_REG = "TRUE" *) reg rst_in_sync1 = 1'b0; 77 | (* ASYNC_REG = "TRUE" *) reg rst_in_sync2 = 1'b0; 78 | (* ASYNC_REG = "TRUE" *) reg rst_in_sync3 = 1'b0; 79 | reg rst_in_out = 1'b0; 80 | 81 | always @(posedge clk_in, posedge rst_in) begin 82 | if (rst_in) begin 83 | rst_in_meta <= 1'b1; 84 | rst_in_sync1 <= 1'b1; 85 | rst_in_sync2 <= 1'b1; 86 | rst_in_sync3 <= 1'b1; 87 | rst_in_out <= 1'b1; 88 | end 89 | else begin 90 | rst_in_meta <= 1'b0; 91 | rst_in_sync1 <= rst_in_meta; 92 | rst_in_sync2 <= rst_in_sync1; 93 | rst_in_sync3 <= rst_in_sync2; 94 | rst_in_out <= rst_in_sync3; 95 | end 96 | end 97 | 98 | assign rst_out = rst_in_out; 99 | 100 | 101 | endmodule 102 | -------------------------------------------------------------------------------- /src/hdl/mac/mac.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: mac 25 | * 26 | * Description: Top Level 10G MAC with 32-bit XGMII interface. Note non-standard 27 | * pause/valid for XGMII to support integration with synchronus 28 | * gearbox. 29 | */ 30 | 31 | `timescale 1ns/1ps 32 | `default_nettype none 33 | `include "code_defs_pkg.svh" 34 | 35 | module mac #( 36 | localparam int DATA_WIDTH = 32, 37 | localparam int DATA_NBYTES = DATA_WIDTH / 8 38 | ) ( 39 | input wire i_tx_reset, 40 | input wire i_rx_reset, 41 | 42 | // Tx PHY 43 | input wire i_tx_clk, 44 | output logic [DATA_WIDTH-1:0] o_xgmii_tx_data, 45 | output logic [DATA_NBYTES-1:0] o_xgmii_tx_ctl, 46 | input wire i_phy_tx_ready, 47 | 48 | // Rx PHY 49 | input wire i_rx_clk, 50 | input wire [DATA_WIDTH-1:0] i_xgmii_rx_data, 51 | input wire [DATA_NBYTES-1:0] i_xgmii_rx_ctl, 52 | input wire i_phy_rx_valid, 53 | input wire [DATA_NBYTES-1:0] i_term_loc, 54 | 55 | /* svlint off prefix_input */ 56 | /* svlint off prefix_output */ 57 | // Tx AXIS 58 | input wire [DATA_WIDTH-1:0] s00_axis_tdata, 59 | input wire [DATA_NBYTES-1:0] s00_axis_tkeep, 60 | input wire s00_axis_tvalid, 61 | output logic s00_axis_tready, 62 | input wire s00_axis_tlast, 63 | 64 | // Rx AXIS 65 | output logic [DATA_WIDTH-1:0] m00_axis_tdata, 66 | output logic [DATA_NBYTES-1:0] m00_axis_tkeep, 67 | output logic m00_axis_tvalid, 68 | output logic m00_axis_tlast, 69 | output logic m00_axis_tuser 70 | /* svlint on prefix_input */ 71 | /* svlint on prefix_output */ 72 | ); 73 | 74 | import code_defs_pkg::*; 75 | 76 | wire [DATA_WIDTH-1:0] phy_tx_data; 77 | wire [DATA_NBYTES-1:0] phy_tx_ctl; 78 | 79 | tx_mac u_tx( 80 | .i_reset(i_tx_reset), 81 | .i_clk(i_tx_clk), 82 | 83 | // Tx PHY 84 | .o_xgmii_tx_data(phy_tx_data), 85 | .o_xgmii_tx_ctl(phy_tx_ctl), 86 | .i_phy_tx_ready(i_phy_tx_ready), 87 | 88 | // Tx User AXIS 89 | .s00_axis_tdata(s00_axis_tdata), 90 | .s00_axis_tkeep(s00_axis_tkeep), 91 | .s00_axis_tvalid(s00_axis_tvalid), 92 | .s00_axis_tready(s00_axis_tready), 93 | .s00_axis_tlast(s00_axis_tlast) 94 | ); 95 | 96 | always_ff @(posedge i_tx_clk) 97 | if (i_tx_reset) begin 98 | o_xgmii_tx_data <= '0; 99 | o_xgmii_tx_ctl <= '0; 100 | end else begin 101 | if (i_phy_tx_ready) begin 102 | o_xgmii_tx_data <= phy_tx_data; 103 | o_xgmii_tx_ctl <= phy_tx_ctl; 104 | end else begin 105 | o_xgmii_tx_data <= o_xgmii_tx_data; 106 | o_xgmii_tx_ctl <= o_xgmii_tx_ctl; 107 | end 108 | end 109 | 110 | // Register AXIS out 111 | wire [DATA_WIDTH-1:0] rx_tdata; 112 | wire [DATA_NBYTES-1:0] rx_tkeep; 113 | wire rx_tvalid; 114 | wire rx_tlast; 115 | wire rx_tuser; 116 | 117 | rx_mac u_rx ( 118 | .i_reset(i_rx_reset), 119 | .i_clk(i_rx_clk), 120 | 121 | // Rx PHY 122 | .i_xgmii_rxd(i_xgmii_rx_data), 123 | .i_xgmii_rxc(i_xgmii_rx_ctl), 124 | .i_phy_rx_valid(i_phy_rx_valid), 125 | .i_term_loc(i_term_loc), 126 | 127 | // Rx AXIS 128 | .m00_axis_tdata(rx_tdata), 129 | .m00_axis_tkeep(rx_tkeep), 130 | .m00_axis_tvalid(rx_tvalid), 131 | .m00_axis_tlast(rx_tlast), 132 | .m00_axis_tuser(rx_tuser) 133 | ); 134 | 135 | always_ff @(posedge i_rx_clk) 136 | if (i_rx_reset) begin 137 | m00_axis_tdata <= '0; 138 | m00_axis_tkeep <= '0; 139 | m00_axis_tvalid <= '0; 140 | m00_axis_tlast <= '0; 141 | m00_axis_tuser <= '0; 142 | end else begin 143 | m00_axis_tdata <= rx_tdata; 144 | m00_axis_tkeep <= rx_tkeep; 145 | m00_axis_tvalid <= rx_tvalid; 146 | m00_axis_tlast <= rx_tlast; 147 | m00_axis_tuser <= rx_tuser; 148 | end 149 | 150 | endmodule 151 | -------------------------------------------------------------------------------- /example/example_10g_eth_build.tcl: -------------------------------------------------------------------------------- 1 | # Vivado build script 2 | 3 | 4 | set module_vars {SCRAMBLER_BYPASS EXTERNAL_GEARBOX TX_XVER_BUFFER INIT_CLK_FREQ} 5 | set build_vars [concat {FPGA_PART} $module_vars] 6 | 7 | foreach x $build_vars { 8 | if {[info exists env($x)]} { 9 | set $x $env($x) 10 | puts "$x = $env($x)" 11 | 12 | } else { 13 | puts "Environment variable $x not set, generate IP from shell script." 14 | exit 1 15 | } 16 | } 17 | 18 | global output_dir 19 | global src_dir 20 | global ip_dir 21 | global flatten_hierarchy 22 | 23 | set project_name example_10g_eth 24 | set output_dir ./out 25 | set src_dir ../ 26 | 27 | set ip_dir ../../src/ip/gen 28 | set core_src_dir ../../src 29 | set core_src_include_dir ../../src/hdl/include 30 | set lib_src_dir ../../src/lib 31 | 32 | set flatten_hierarchy none 33 | set directive PerformanceOptimized 34 | set fanout_limit 512 35 | set use_retiming 1 36 | 37 | proc init {} { 38 | 39 | set_part $::FPGA_PART 40 | set_property target_language Verilog [current_project] 41 | 42 | set_property source_mgmt_mode All [current_project] 43 | } 44 | 45 | proc add_sources {} { 46 | 47 | read_verilog [glob $::src_dir/hdl/*.sv] -sv 48 | read_verilog [glob $::core_src_dir/hdl/*.sv] -sv 49 | read_verilog [glob $::core_src_dir/hdl/**/*.sv] -sv 50 | read_verilog [glob $::core_src_dir/hdl/**/*.v] 51 | read_verilog [glob $::lib_src_dir/**/**/*.sv] 52 | 53 | read_ip [glob $::ip_dir/**/*.xci] 54 | 55 | read_xdc [glob $::src_dir/constraints/*.xdc] 56 | 57 | set params "" 58 | foreach x $::module_vars { 59 | global $x 60 | set params [concat $params "$x=[expr $$x]"] 61 | } 62 | set_property generic $params [current_fileset] 63 | } 64 | 65 | proc gen_ip {} { 66 | 67 | # Out-Of-Context synthesis for IPs 68 | foreach ip [get_ips] { 69 | set ip_filename [get_property IP_FILE $ip] 70 | set ip_dcp [file rootname $ip_filename] 71 | append ip_dcp ".dcp" 72 | set ip_xml [file rootname $ip_filename] 73 | append ip_xml ".xml" 74 | 75 | if {([file exists $ip_dcp] == 0) || [expr {[file mtime $ip_filename ] > [file mtime $ip_dcp ]}]} { 76 | 77 | # re-generate the IP 78 | generate_target all -force $ip 79 | set_property generate_synth_checkpoint true [get_files $ip_filename] 80 | synth_ip -force $ip 81 | } 82 | } 83 | } 84 | 85 | proc synth {} { 86 | 87 | if { $::use_retiming == 1 } { 88 | synth_design -top $::project_name -flatten_hierarchy $::flatten_hierarchy -directive $::directive \ 89 | -include_dirs $::core_src_include_dir -retiming 90 | } else { 91 | synth_design -top $::project_name -flatten_hierarchy $::flatten_hierarchy -directive $::directive \ 92 | -include_dirs $::core_src_include_dir 93 | } 94 | 95 | 96 | write_checkpoint -force $::output_dir/post_synth.dcp 97 | report_timing_summary -file $::output_dir/post_synth_timing_summary.rpt 98 | report_utilization -file $::output_dir/post_synth_util.rpt 99 | } 100 | 101 | proc impl {} { 102 | 103 | # ensure debug hub connected to free running clock 104 | connect_debug_port dbg_hub/clk [get_nets i_init_clk] 105 | 106 | opt_design -hier_fanout_limit $::fanout_limit 107 | # opt_design 108 | place_design 109 | report_clock_utilization -file $::output_dir/clock_util.rpt 110 | 111 | #get timing violations and run optimizations if needed 112 | if {[get_property SLACK [get_timing_paths -max_paths 1 -nworst 1 -setup]] < 0} { 113 | puts "Found setup timing violations => running physical optimization" 114 | phys_opt_design 115 | } 116 | write_checkpoint -force $::output_dir/post_place.dcp 117 | report_utilization -file $::output_dir/post_place_util.rpt 118 | report_timing_summary -file $::output_dir/post_place_timing_summary.rpt 119 | 120 | #Route design and generate bitstream 121 | route_design -directive Explore 122 | 123 | # Re-run phys_opt if timing violations found 124 | if {[get_property SLACK [get_timing_paths -max_paths 1 -nworst 1 -setup]] < 0} { 125 | puts "Found setup timing violations => running physical optimization" 126 | if { $::use_retiming == 1 } { 127 | phys_opt_design -directive AddRetime 128 | } else { 129 | phys_opt_design 130 | } 131 | 132 | } 133 | 134 | 135 | write_checkpoint -force $::output_dir/post_route.dcp 136 | report_route_status -file $::output_dir/post_route_status.rpt 137 | report_timing_summary -file $::output_dir/post_route_timing_summary.rpt 138 | report_power -file $::output_dir/post_route_power.rpt 139 | report_drc -file $::output_dir/post_imp_drc.rpt 140 | } 141 | 142 | proc output {} { 143 | write_verilog -force $::output_dir/cpu_impl_netlist.v -mode timesim -sdf_anno true 144 | write_debug_probes -force $::output_dir/$::project_name.ltx 145 | 146 | write_bitstream -force $::output_dir/$::project_name 147 | write_bitstream -bin_file -force $::output_dir/$::project_name 148 | } 149 | 150 | proc all {} { 151 | init 152 | add_sources 153 | gen_ip 154 | synth 155 | impl 156 | output 157 | } 158 | 159 | proc start_synth {} { 160 | init 161 | add_sources 162 | gen_ip 163 | synth 164 | } 165 | 166 | proc impl_out {} { 167 | impl 168 | output 169 | } 170 | 171 | puts "Build options: " 172 | puts " 'all' : Full build" 173 | puts " 'start_synth' : Initialise, add sources and run synthesis" 174 | puts " 'impl_out' : Run implementation and bitstream generation" 175 | -------------------------------------------------------------------------------- /src/hdl/pcs/tx_gearbox.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: tx_gearbox 25 | * 26 | * Description: 64b66b transmit synchronus gearbox with 32-bit interface. Takes 27 | * 64-bit frame and 2-bit header over two cycles, taking header with 28 | * first (lower) 32-bits of frame. 29 | * 30 | * The design was modelled in python which can be found in src/tb/gearbox. 31 | * 32 | * Input data is constructed in an output buffer depending on the input sequence 33 | * which counts from 0-32. No data is loaded when seq == 32. The lower 32-bits are 34 | * ouput from the buffer. 35 | * 36 | * Output buffer construction: 37 | * | output data | 38 | * buffer index: 0 1 2 3 4 5 6 7 ... 31 | 32 33 ... 63 64 65 39 | * cycle: 40 | * 0 H0 H1 D0 D1 D2 D3 D4 D5 ... D29 | D30 D31 ... X X X 41 | * 1 D30 D31 D32 D33 D34 D35 D36 D37 ... D61 | D62 D63 ... X X X 42 | * 2 D62 D63 H0 H1 D0 D1 D2 D3 ... D27 | D28 D29 ... X X X 43 | * 3 D28 D29 D30 D31 D32 D33 D34 D35 ... D59 | D60 D61 ... X X X 44 | * 4 D60 D61 D62 D63 H0 H1 D0 D1 ... D25 | D26 D27 ... X X X 45 | * ... 46 | * 30 D34 D35 D36 D37 D38 D39 D40 D41 ... H1 | D0 D1 ... D31 47 | * 31 D0 D1 D2 D3 D4 D5 D6 D7 ... D31 | D32 D33 ... D63 48 | * 32 * D32 D33 D34 D35 D36 D37 D38 D39 ... D63 | X X ... X 49 | * 50 | */ 51 | 52 | `timescale 1ns/1ps 53 | `default_nettype none 54 | 55 | module tx_gearbox #( 56 | localparam int DATA_WIDTH = 32, 57 | localparam int HEADER_WIDTH = 2, 58 | localparam int SEQUENCE_WIDTH = 6 59 | ) ( 60 | input wire i_clk, 61 | input wire i_reset, 62 | input wire [DATA_WIDTH-1:0] i_data, 63 | input wire [HEADER_WIDTH-1:0] i_header, 64 | input wire [SEQUENCE_WIDTH-1:0] i_gearbox_seq, 65 | input wire i_pause, 66 | output wire [DATA_WIDTH-1:0] o_data, 67 | output wire o_frame_word 68 | ); 69 | 70 | localparam int BUF_SIZE = 2*DATA_WIDTH + HEADER_WIDTH; 71 | 72 | /********* Buffer Construction ********/ 73 | 74 | // Re-use the gearbox sequnce counter method as used in gty 75 | wire load_header; 76 | logic [2*DATA_WIDTH + HEADER_WIDTH -1:0] obuf, next_obuf, shifted_obuf; 77 | wire [SEQUENCE_WIDTH-1:0] header_idx; 78 | wire [SEQUENCE_WIDTH-1:0] data_idx; 79 | 80 | assign load_header = !i_gearbox_seq[0]; // Load header on even cycles 81 | assign o_frame_word = i_gearbox_seq[0]; // Load bottom word on even cycles (with header), top on odd 82 | assign o_data = next_obuf[0 +: DATA_WIDTH]; 83 | 84 | assign header_idx = i_gearbox_seq; // Location to load H0 85 | assign data_idx = load_header ? i_gearbox_seq + 2 : i_gearbox_seq + 1; // Location to load D0 or D32 86 | 87 | assign shifted_obuf = {{DATA_WIDTH{1'b0}}, obuf[DATA_WIDTH +: BUF_SIZE-DATA_WIDTH]}; 88 | 89 | // Need to assign single bits as iverilog does not support variable width assignments 90 | generate for (genvar gi = 0; gi < BUF_SIZE; gi++) begin: l_assign_obuf 91 | 92 | // Next bit can come from three sources: 93 | // 1. New data (header and data word or just data word) 94 | // 2. Data previously in the buffer shifted down 95 | // 3. Existing data at that index 96 | // The data source depends on the bit in the buffer and current 97 | // sequence value (which informs header_idx and data_idx). 98 | 99 | always @(*) begin 100 | 101 | next_obuf[gi] = obuf[gi]; // Source 3 102 | 103 | if (gi < DATA_WIDTH) begin // Source 2 104 | next_obuf[gi] = shifted_obuf[gi]; 105 | end 106 | 107 | if (!i_pause) begin // Source 1 108 | if (load_header) begin 109 | if (gi >= header_idx && gi < header_idx + 2) begin 110 | next_obuf[gi] = i_header[gi-header_idx]; 111 | end 112 | end 113 | 114 | if (gi >= data_idx && gi < data_idx + DATA_WIDTH) begin 115 | next_obuf[gi] = o_frame_word ? i_data[gi-data_idx] : i_data[gi-data_idx]; 116 | end 117 | end 118 | 119 | end 120 | 121 | end endgenerate 122 | 123 | always_ff @(posedge i_clk) 124 | if (i_reset) begin 125 | obuf <= '0; 126 | end else begin 127 | obuf <= next_obuf; 128 | end 129 | 130 | endmodule 131 | -------------------------------------------------------------------------------- /src/tb/gearbox/test_rx_gearbox.py: -------------------------------------------------------------------------------- 1 | from asyncore import loop 2 | import asyncio 3 | import enum 4 | import cocotb 5 | import numpy as np 6 | from gearbox_model import RxGearboxModel 7 | 8 | from cocotb.triggers import Timer, RisingEdge, FallingEdge, Edge, NextTimeStep 9 | from cocotb.clock import Clock 10 | 11 | import debugpy 12 | 13 | 14 | class RxGearboxTb: 15 | def __init__(self, dut): 16 | self.dut = dut 17 | 18 | self.clk_period = round(1 / (10.3125 / 32), 2) # ps precision 19 | cocotb.start_soon(Clock(dut.i_clk, self.clk_period, units="ns").start()) 20 | self.dut.i_reset.value = 1 21 | self.dut.i_slip.value = 0 22 | self.dut.i_data.value = 0 23 | 24 | 25 | async def reset(self): 26 | self.dut.i_reset.value = 0 27 | await RisingEdge(self.dut.i_clk) 28 | self.dut.i_reset.value = 1 29 | await RisingEdge(self.dut.i_clk) 30 | self.dut.i_reset.value = 0 31 | 32 | 33 | @cocotb.test() 34 | async def rx_gearbox_test_no_slip(dut): 35 | 36 | # debugpy.listen(5678) 37 | # debugpy.wait_for_client() 38 | # debugpy.breakpoint() 39 | 40 | 41 | tb = RxGearboxTb(dut) 42 | 43 | await tb.reset() 44 | 45 | # Generate random data 46 | gen_idata = [np.random.randint(0,2,32) for _ in range(100)] 47 | 48 | # Create ref model 49 | model = RxGearboxModel() 50 | 51 | 52 | tb.dut.i_data.value=0 53 | 54 | for id in gen_idata: 55 | for i in range(len(id)): 56 | tb.dut.i_data[i].value = int(id[i]) 57 | 58 | await FallingEdge(tb.dut.i_clk) # Give the sim a tick to update the comb outputs 59 | 60 | dut_odata = str(tb.dut.o_data.value)[::-1] 61 | dut_oheader = str(tb.dut.o_header.value)[::-1] 62 | dut_odata_valid = str(tb.dut.o_data_valid.value) 63 | dut_oheader_valid = str(tb.dut.o_header_valid.value) 64 | dut_obuf = str(tb.dut.next_obuf.value)[::-1] 65 | 66 | await RisingEdge(tb.dut.i_clk) 67 | 68 | ret = model.next(id) 69 | 70 | model_odata = ''.join([str(x) for x in ret['data']]) 71 | model_oheader = ''.join([str(x) for x in ret['header']]) 72 | model_odata_valid = str(int(ret['data_valid'])) 73 | model_oheader_valid = str(int(ret['header_valid'])) 74 | model_obuf = ''.join([str(x) for x in ret['obuf']]) 75 | 76 | 77 | all_eq = model_odata == dut_odata and \ 78 | model_oheader == dut_oheader and \ 79 | model_odata_valid == dut_odata_valid and \ 80 | model_oheader_valid == dut_oheader_valid 81 | 82 | if not all_eq: 83 | print('dut data: ', dut_odata_valid, dut_odata) 84 | print('model data: ', model_odata_valid, model_odata) 85 | print('dut header: ', dut_oheader_valid, dut_oheader) 86 | print('model header:', model_oheader_valid, model_oheader) 87 | print('dut buf: ', dut_obuf) 88 | print('model buf:', model_obuf) 89 | print('dut cycle: ', tb.dut.gearbox_seq.value) 90 | print('model cycle: ', ret['cycle']) 91 | print('input data: ', str(tb.dut.i_data.value)[::-1]) 92 | assert all_eq 93 | 94 | @cocotb.test() 95 | async def rx_gearbox_test_slip(dut): 96 | 97 | # debugpy.listen(5678) 98 | # debugpy.wait_for_client() 99 | # debugpy.breakpoint() 100 | 101 | for n_slips in list(range(34)): 102 | 103 | tb = RxGearboxTb(dut) 104 | 105 | await tb.reset() 106 | 107 | # Generate random data 108 | gen_idata = [np.random.randint(0,2,32) for _ in range(1000)] 109 | 110 | # Create ref model 111 | model = RxGearboxModel() 112 | 113 | tb.dut.i_data.value=0 114 | 115 | for i, id in enumerate(gen_idata): 116 | 117 | slip = i < n_slips and np.random.randint(0,20) == 0 118 | 119 | for i in range(len(id)): 120 | tb.dut.i_data[i].value = int(id[i]) 121 | 122 | tb.dut.i_slip.value = int(slip) 123 | 124 | await FallingEdge(tb.dut.i_clk) 125 | 126 | dut_odata = str(tb.dut.o_data.value)[::-1] 127 | dut_oheader = str(tb.dut.o_header.value)[::-1] 128 | dut_odata_valid = str(tb.dut.o_data_valid.value) 129 | dut_oheader_valid = str(tb.dut.o_header_valid.value) 130 | dut_obuf = str(tb.dut.next_obuf.value)[::-1] 131 | 132 | await RisingEdge(tb.dut.i_clk) 133 | 134 | ret = model.next(id, slip) 135 | 136 | model_odata = ''.join([str(x) for x in ret['data']]) 137 | model_oheader = ''.join([str(x) for x in ret['header']]) 138 | model_odata_valid = str(int(ret['data_valid'])) 139 | model_oheader_valid = str(int(ret['header_valid'])) 140 | model_obuf = ''.join([str(x) for x in ret['obuf']]) 141 | 142 | 143 | all_eq = model_odata == dut_odata and \ 144 | model_oheader == dut_oheader and \ 145 | model_odata_valid == dut_odata_valid and \ 146 | model_oheader_valid == dut_oheader_valid 147 | 148 | if not all_eq: 149 | print('dut data: ', dut_odata_valid, dut_odata) 150 | print('model data: ', model_odata_valid, model_odata) 151 | print('dut header: ', dut_oheader_valid, dut_oheader) 152 | print('model header:', model_oheader_valid, model_oheader) 153 | print('dut buf: ', dut_obuf) 154 | print('model buf:', model_obuf) 155 | print('dut cycle: ', tb.dut.gearbox_seq.value) 156 | print('model cycle: ', ret['cycle']) 157 | print('input data: ', str(tb.dut.i_data.value)[::-1]) 158 | assert all_eq 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/hdl/mac_pcs.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: mac_pcs 25 | * 26 | * Description: Integrated 10G MAC and PCS with AXIS user I/O. 27 | * 28 | * Note: 29 | * A non-standard implementation of TUSER is used for the AXIS master. 30 | * If TUSER is not asserted with TLAST, this indicates a packet was recieved with 31 | * incorrect CRC. However, TLAST/TUSER can be asserted when all TKEEP == 0, this is to 32 | * provide data for processing ASAP. This may cause TUSER to be dropped if routing 33 | * the AXIS interface through interconnect. 34 | */ 35 | 36 | `timescale 1ns/1ps 37 | `default_nettype none 38 | 39 | module mac_pcs #( 40 | parameter bit SCRAMBLER_BYPASS = 0, 41 | parameter bit EXTERNAL_GEARBOX = 0, 42 | localparam int DATA_WIDTH = 32, 43 | 44 | localparam int DATA_NBYTES = DATA_WIDTH / 8 45 | ) ( 46 | input wire i_tx_reset, 47 | input wire i_rx_reset, 48 | 49 | /* svlint off prefix_input */ 50 | /* svlint off prefix_output */ 51 | // Tx AXIS 52 | input wire [DATA_WIDTH-1:0] s00_axis_tdata, 53 | input wire [DATA_NBYTES-1:0] s00_axis_tkeep, 54 | input wire s00_axis_tvalid, 55 | output logic s00_axis_tready, 56 | input wire s00_axis_tlast, 57 | 58 | // Rx AXIS 59 | output logic [DATA_WIDTH-1:0] m00_axis_tdata, 60 | output logic [DATA_NBYTES-1:0] m00_axis_tkeep, 61 | output logic m00_axis_tvalid, 62 | output logic m00_axis_tlast, 63 | output logic m00_axis_tuser, 64 | /* svlint on prefix_input */ 65 | /* svlint on prefix_output */ 66 | 67 | // Rx XVER 68 | input wire i_xver_rx_clk, 69 | input wire [DATA_WIDTH-1:0] i_xver_rx_data, 70 | input wire [1:0] i_xver_rx_header, 71 | input wire i_xver_rx_data_valid, 72 | input wire i_xver_rx_header_valid, 73 | output wire o_xver_rx_gearbox_slip, 74 | 75 | // TX XVER 76 | input wire i_xver_tx_clk, 77 | output wire [DATA_WIDTH-1:0] o_xver_tx_data, 78 | output wire [1:0] o_xver_tx_header, 79 | output wire [5:0] o_xver_tx_gearbox_sequence 80 | ); 81 | 82 | wire [DATA_WIDTH-1:0] xgmii_rx_data, xgmii_tx_data; 83 | wire [DATA_NBYTES-1:0] xgmii_rx_ctl, xgmii_tx_ctl; 84 | wire phy_rx_valid, phy_tx_ready; 85 | wire [DATA_NBYTES-1:0] term_loc; 86 | 87 | mac u_mac ( 88 | .i_tx_reset(i_tx_reset), 89 | .i_rx_reset(i_rx_reset), 90 | 91 | // Tx PHY 92 | .i_tx_clk(i_xver_tx_clk), 93 | .o_xgmii_tx_data(xgmii_tx_data), 94 | .o_xgmii_tx_ctl(xgmii_tx_ctl), 95 | .i_phy_tx_ready(phy_tx_ready), 96 | 97 | // Tx AXIS 98 | .s00_axis_tdata(s00_axis_tdata), 99 | .s00_axis_tkeep(s00_axis_tkeep), 100 | .s00_axis_tvalid(s00_axis_tvalid), 101 | .s00_axis_tready(s00_axis_tready), 102 | .s00_axis_tlast(s00_axis_tlast), 103 | 104 | // Rx PHY 105 | .i_rx_clk(i_xver_rx_clk), 106 | .i_xgmii_rx_data(xgmii_rx_data), 107 | .i_xgmii_rx_ctl(xgmii_rx_ctl), 108 | .i_phy_rx_valid(phy_rx_valid), 109 | .i_term_loc(term_loc), 110 | 111 | // Rx AXIS 112 | .m00_axis_tdata(m00_axis_tdata), 113 | .m00_axis_tkeep(m00_axis_tkeep), 114 | .m00_axis_tvalid(m00_axis_tvalid), 115 | .m00_axis_tlast(m00_axis_tlast), 116 | .m00_axis_tuser(m00_axis_tuser) 117 | ); 118 | 119 | 120 | pcs #( 121 | .SCRAMBLER_BYPASS(SCRAMBLER_BYPASS), 122 | .EXTERNAL_GEARBOX(EXTERNAL_GEARBOX), 123 | .ENCODER_OCODE_SUPPORT(0) // Mac doesn't generate OCODES, disable to relieve timing pressure 124 | ) u_pcs ( 125 | 126 | // Reset logic 127 | .i_tx_reset(i_tx_reset), 128 | .i_rx_reset(i_rx_reset), 129 | 130 | // Rx from tranceiver 131 | .i_xver_rx_clk(i_xver_rx_clk), 132 | .i_xver_rx_data(i_xver_rx_data), 133 | .i_xver_rx_header(i_xver_rx_header), 134 | .i_xver_rx_data_valid(i_xver_rx_data_valid), 135 | .i_xver_rx_header_valid(i_xver_rx_header_valid), 136 | .o_xver_rx_gearbox_slip(o_xver_rx_gearbox_slip), 137 | 138 | //Rx interface out 139 | .o_xgmii_rx_data(xgmii_rx_data), 140 | .o_xgmii_rx_ctl(xgmii_rx_ctl), 141 | .o_xgmii_rx_valid(phy_rx_valid), // Non standard XGMII - required for no CDC 142 | .o_term_loc(term_loc), 143 | 144 | .i_xver_tx_clk(i_xver_tx_clk), 145 | .i_xgmii_tx_data(xgmii_tx_data), 146 | .i_xgmii_tx_ctl(xgmii_tx_ctl), 147 | .o_xgmii_tx_ready(phy_tx_ready), // Non standard XGMII - required for no CDC 148 | 149 | // TX Interface out 150 | .o_xver_tx_data(o_xver_tx_data), 151 | .o_xver_tx_header(o_xver_tx_header), 152 | .o_xver_tx_gearbox_sequence(o_xver_tx_gearbox_sequence) 153 | ); 154 | 155 | endmodule 156 | -------------------------------------------------------------------------------- /src/tb/gearbox/gearbox_model.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class RxGearboxModel: 4 | def __init__(self, type='int'): 5 | if type == 'str': 6 | self.obuf = ['XXX' for _ in range(67)] # Extra bit for half slip 7 | else: 8 | self.obuf = [0 for _ in range(67)] 9 | self.cycle = 0 10 | self.half_slip = 0 11 | 12 | 13 | def next(self, idata, slip=False): 14 | 15 | self.slip = slip 16 | self.count = int(self.cycle % 33) 17 | 18 | 19 | if not self.half_slip: 20 | self.valid = self.count != 0 21 | self.frame_word = int(self.count % 2 == 0) 22 | self.output_header = int(self.count % 2 == 1) 23 | else: 24 | self.valid = self.count != 31 25 | self.frame_word = int(self.count % 2 == 0) and self.count != 32 26 | self.output_header = (int(self.count % 2 == 1) or self.count == 32) and self.valid 27 | 28 | data_idxs = [00,32,64,30,62,28,60,26,58,24,56,22,54,20,52,18,50,16,48,14,46,12,44,10,42,8,40,6,38,4,36,2,34] 29 | 30 | # if self.count % 2 == 0: 31 | # idata_idx = (66 - self.count) % 66 32 | # obuf[idata_idx:66] = idata[0 : self.count] 33 | # obuf[0:32-self.count] = idata[self.count:] 34 | 35 | # else: 36 | # idata_idx = ((66 - self.count - 32) - 1) % 66 37 | # obuf[idata_idx : self.idata_idx + 32] = idata 38 | 39 | # bit select method 40 | 41 | for bit in range(len(self.obuf)): 42 | 43 | self.idata_idx = data_idxs[self.count] 44 | 45 | if self.count % 2 == 0: 46 | if self.idata_idx != 0 and bit >= self.idata_idx and bit < 66: 47 | self.obuf[bit] = idata[bit-self.idata_idx] 48 | elif bit < 32 - self.count: 49 | self.obuf[bit] = idata[self.count + bit] 50 | else: 51 | if bit >= self.idata_idx and bit < self.idata_idx + 32: 52 | self.obuf[bit] = idata[bit - self.idata_idx] 53 | 54 | self.obuf[66] = self.obuf[0] 55 | 56 | if self.half_slip % 2 == 0: 57 | self.odata = self.obuf[2 : 34] if self.frame_word == 0 else self.obuf[34:66] 58 | self.oheader = self.obuf[0:2] 59 | else: 60 | self.odata = self.obuf[3 : 35] if self.frame_word == 0 else self.obuf[35:67] 61 | self.oheader = self.obuf[1:3] 62 | 63 | ret = { 64 | 'data' : self.odata, 65 | 'header' : self.oheader, 66 | 'data_valid' : self.valid, 67 | 'header_valid' : self.output_header, 68 | 'obuf' : self.obuf, 69 | 'cycle' : self.cycle, 70 | 'count' : self.count, 71 | 'frame_word' : self.frame_word 72 | } 73 | 74 | if not self.slip: 75 | self.cycle = self.cycle + 1 76 | 77 | if slip: 78 | self.half_slip = (self.half_slip + 1) % 2 79 | 80 | return ret 81 | 82 | 83 | 84 | 85 | def get_state(self): 86 | 87 | return f'{self.count:03d}\t{self.slip:b}\t{self.valid}\t{int(self.output_header)}\t{self.idata_idx:02d}\t{self.oheader}\t{self.odata}' 88 | 89 | 90 | class TxGearboxModel: 91 | def __init__(self, type='int'): 92 | if type == 'str': 93 | self.obuf = ['XXX' for _ in range(66)] 94 | else: 95 | self.obuf = [0 for _ in range(66)] 96 | self.cycle = 0 97 | self.half_slip = 0 98 | self.count = int(self.cycle % 33) 99 | self.pause = self.count == 32 100 | 101 | 102 | 103 | def next(self, iheader, idata): 104 | 105 | self.count = int(self.cycle % 33) 106 | self.pause = self.count == 32 107 | self.frame_word = int(self.count % 2 == 1) 108 | self.load_header = int(self.count % 2 == 0) 109 | self.header_idx = self.count if self.load_header else -1 110 | self.data_idx = self.count + 2 if self.load_header else self.count + 1 111 | 112 | 113 | 114 | # obuf[0:32] = obuf[32:64] 115 | # if not self.pause: 116 | # if self.load_header: 117 | # obuf[self.header_idx:self.header_idx+2] = header 118 | # obuf[self.data_idx:self.data_idx+32] = data[self.frame_word*32 : 32 + self.frame_word*32] 119 | 120 | # odata.append(obuf[0:32]) 121 | 122 | # bit select method 123 | for bit in range(len(self.obuf)): 124 | if bit < 32: 125 | self.obuf[bit] = self.obuf[bit+32] 126 | 127 | if not self.pause: 128 | if self.load_header: 129 | if bit >= self.header_idx and bit < self.header_idx + 2: 130 | self.obuf[bit] = iheader[bit-self.header_idx] 131 | 132 | if bit >= self.data_idx and bit < self.data_idx + 32: 133 | self.obuf[bit] = idata[bit - self.data_idx] 134 | 135 | self.odata = self.obuf[:32] 136 | 137 | self.cycle = self.cycle + 1 138 | 139 | return { 140 | 'data' : self.odata, 141 | 'pause' : self.pause, 142 | 'obuf' : self.obuf, 143 | 'cycle' : self.cycle 144 | } 145 | 146 | def get_frame_word(self): 147 | self.count = int(self.cycle % 33) 148 | self.frame_word = int(self.count % 2 == 1) 149 | return self.frame_word 150 | 151 | def get_pause(self): 152 | self.count = int(self.cycle % 33) 153 | self.pause = self.count == 32 154 | return self.pause 155 | 156 | def get_count(self): 157 | self.count = int(self.cycle % 33) 158 | return self.count 159 | 160 | def get_state(self): 161 | 162 | return f'{self.count:03d}\t{self.pause}\t{int(self.load_header)}\t{self.header_idx:02d}\t{self.data_idx:02d}\t{int(self.frame_word)}\t{self.odata}' 163 | 164 | def get_state_header(self): 165 | return f'count\tpause\theader?\theader_idx\tdata_idx\tframe_word\todata' -------------------------------------------------------------------------------- /src/tb/gearbox/gearbox_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "from gearbox_model import RxGearboxModel, TxGearboxModel\n", 11 | "import matplotlib.pyplot as plt" 12 | ] 13 | }, 14 | { 15 | "attachments": {}, 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "## Tx Gearbox" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "\n", 29 | "n_frames = 200\n", 30 | "header = [[f'{y:02d}-H0{x}' for x in range(2)] for y in range(n_frames)]\n", 31 | "data = [[f'{y:02d}-D{x:02d}' for x in range(64)] for y in range(n_frames)]\n", 32 | "\n", 33 | "\n", 34 | "txg = TxGearboxModel('str')\n", 35 | "txg_odata = []\n", 36 | "\n", 37 | "print(txg.get_state_header())\n", 38 | "\n", 39 | "for h, d in zip(header, data):\n", 40 | " for _ in range(2):\n", 41 | " idata = d[32:] if txg.get_frame_word() else d[:32]\n", 42 | " ret = txg.next(h, idata)\n", 43 | " txg_odata.append(ret['data'])\n", 44 | " print(txg.get_state())\n", 45 | "\n", 46 | " if (ret['pause']):\n", 47 | " ret = txg.next(h, idata)\n", 48 | " txg_odata.append(ret['data'])\n", 49 | " print(txg.get_state())" 50 | ] 51 | }, 52 | { 53 | "attachments": {}, 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "## Rx Gearbox - no slip" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "rxg = RxGearboxModel('str')\n", 67 | "\n", 68 | "rxg_oheader =[]\n", 69 | "rxg_odata = []\n", 70 | "\n", 71 | "for id in txg_odata:\n", 72 | " ret = rxg.next(id)\n", 73 | " print(rxg.get_state())\n", 74 | "\n", 75 | " if ret['header_valid']:\n", 76 | " rxg_oheader.append(ret['header'])\n", 77 | "\n", 78 | " if ret['data_valid']:\n", 79 | " rxg_odata.append(ret['data'])" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "# check the data\n", 89 | "\n", 90 | "# stitch the data back together in to frames\n", 91 | "\n", 92 | "\n", 93 | "\n", 94 | "oframes = [list(np.concatenate([x,y])) for x,y in zip(rxg_odata[::2], rxg_odata[1::2])]\n", 95 | "\n", 96 | "\n", 97 | "head_match = [ih == oh for ih, oh in zip(header, rxg_oheader)]\n", 98 | "data_match = [id == od for id, od in zip(data, oframes)]\n", 99 | "\n", 100 | "plt.plot(head_match)\n", 101 | "plt.plot(data_match)" 102 | ] 103 | }, 104 | { 105 | "attachments": {}, 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "## Rx Gearbox - half slip needed" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "# try with 1 slip out\n", 119 | "rx_idata = txg_odata.copy()\n", 120 | "rx_idata = [item for sublist in rx_idata for item in sublist]\n", 121 | "for _ in range(1): rx_idata.insert(0, 'XXX')\n", 122 | "\n", 123 | "def chunks(lst, n):\n", 124 | " \"\"\"Yield successive n-sized chunks from lst.\"\"\"\n", 125 | " for i in range(0, len(lst), n):\n", 126 | " yield lst[i:i + n]\n", 127 | "\n", 128 | "rx_idata = list(chunks(rx_idata, 32))[:-1]" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "rxg = RxGearboxModel('str')\n", 138 | "slip_next = False\n", 139 | "\n", 140 | "rxg_oheader = []\n", 141 | "rxg_odata = []\n", 142 | "aligned_idx = 0\n", 143 | "aligned = False\n", 144 | "\n", 145 | "for i, id in enumerate(rx_idata):\n", 146 | " ret = rxg.next(id, slip_next)\n", 147 | " print(rxg.get_state())\n", 148 | "\n", 149 | " if not aligned:\n", 150 | " aligned = ('H00' in ret['header'][0] and 'H01' in ret['header'][1]) and ret['header_valid'] \n", 151 | "\n", 152 | " slip_next = ret['header_valid'] and not aligned and not slip_next \n", 153 | "\n", 154 | " if ret['header_valid'] and aligned:\n", 155 | " rxg_oheader.append(ret['header'])\n", 156 | " rxg_odata.append([]) # append data placeholder\n", 157 | "\n", 158 | " if ret['data_valid'] and aligned:\n", 159 | " rxg_odata[-1] = list(np.concatenate([rxg_odata[-1], ret['data']]))\n" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "start_offset = 20 # if half slip, miss frame when first aligning\n", 169 | "\n", 170 | "# align the rx / test data\n", 171 | "\n", 172 | "start_idx = 0\n", 173 | "for i in range(len(header)):\n", 174 | " if header[i] == rxg_oheader[start_offset]:\n", 175 | " start_idx = i\n", 176 | " print(f'Aligned at {i}')\n", 177 | "\n", 178 | "head_match = [ih == oh for ih, oh in zip(header[start_idx:], rxg_oheader[start_offset:])]\n", 179 | "data_match = [id == od for id, od in zip(data[start_idx:], rxg_odata[start_offset:])]\n", 180 | "plt.plot(head_match)\n", 181 | "plt.plot(data_match)" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [] 190 | } 191 | ], 192 | "metadata": { 193 | "kernelspec": { 194 | "display_name": "Python 3", 195 | "language": "python", 196 | "name": "python3" 197 | }, 198 | "language_info": { 199 | "codemirror_mode": { 200 | "name": "ipython", 201 | "version": 3 202 | }, 203 | "file_extension": ".py", 204 | "mimetype": "text/x-python", 205 | "name": "python", 206 | "nbconvert_exporter": "python", 207 | "pygments_lexer": "ipython3", 208 | "version": "3.8.10" 209 | }, 210 | "orig_nbformat": 4 211 | }, 212 | "nbformat": 4, 213 | "nbformat_minor": 2 214 | } 215 | -------------------------------------------------------------------------------- /src/hdl/pcs/lock_state.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: lock_state 25 | * 26 | * Description: 64b66b lock detection as per IEEE 802.3-2008, Figure 49-14 27 | * 28 | */ 29 | 30 | `timescale 1ns/1ps 31 | `default_nettype none 32 | 33 | module lock_state( 34 | input wire i_clk, 35 | input wire i_reset, 36 | input wire [1:0] i_header, 37 | input wire i_valid, 38 | 39 | output wire o_slip 40 | ); 41 | 42 | typedef enum logic[3:0] { LOCK_INIT, RESET_CNT, TEST_SH, 43 | VALID_SH, INVALID_SH, GOOD_64, SLIP, X='x, Z='z} lock_state_t; 44 | 45 | lock_state_t state, next_state; 46 | // verilator lint_off UNUSED 47 | logic rx_block_lock, test_sh, slip_done; 48 | // verilator lint_on UNUSED 49 | logic [15:0] sh_cnt, sh_invalid_cnt; 50 | wire sh_valid; 51 | 52 | assign sh_valid = (i_header[1] ^ i_header[0]); 53 | assign o_slip = (state == SLIP); 54 | 55 | always_ff @(posedge i_clk) begin 56 | if (i_reset) begin 57 | state <= LOCK_INIT; 58 | end else begin 59 | state <= next_state; 60 | 61 | end 62 | end 63 | 64 | always_comb begin 65 | case (state) 66 | LOCK_INIT: begin 67 | next_state = RESET_CNT; 68 | end 69 | RESET_CNT: begin 70 | next_state = TEST_SH; 71 | end 72 | TEST_SH: begin 73 | // Must use if..else here to avoid unknown propagation into enum 74 | if (!i_valid) begin 75 | next_state = TEST_SH; 76 | end if (sh_valid) begin 77 | next_state = VALID_SH; 78 | end else begin 79 | next_state = INVALID_SH; 80 | end 81 | end 82 | VALID_SH: begin 83 | // Minor change here as going back to TEST_SH would miss data 84 | // next_state = sh_cnt == 64 && sh_invalid_cnt == 0 ? GOOD_64 : 85 | // sh_cnt == 64 && sh_invalid_cnt != 0 ? RESET_CNT : 86 | // sh_cnt < 64 && !sh_valid ? INVALID_SH : VALID_SH; 87 | 88 | if (!i_valid) begin 89 | next_state = VALID_SH; 90 | end if (sh_cnt == 64 && sh_invalid_cnt == 0) begin 91 | next_state = GOOD_64; 92 | end else if (sh_cnt == 64 && sh_invalid_cnt != 0) begin 93 | next_state = RESET_CNT; 94 | end else if (sh_cnt < 64 && !sh_valid) begin 95 | next_state = INVALID_SH; 96 | end else begin 97 | next_state = VALID_SH; 98 | end 99 | end 100 | INVALID_SH: begin 101 | // next_state = sh_cnt == 64 && sh_invalid_cnt < 16 ? RESET_CNT : 102 | // sh_invalid_cnt == 16 ? SLIP : 103 | // sh_cnt < 64 && !sh_valid ? INVALID_SH : VALID_SH; 104 | 105 | if (!i_valid) begin 106 | next_state = INVALID_SH; 107 | end if (sh_cnt == 64 && sh_invalid_cnt < 16) begin 108 | next_state = RESET_CNT; 109 | end else if (sh_invalid_cnt == 16) begin 110 | next_state = SLIP; 111 | end else if (sh_cnt < 64 && !sh_valid) begin 112 | next_state = INVALID_SH; 113 | end else begin 114 | next_state = VALID_SH; 115 | end 116 | end 117 | GOOD_64: begin 118 | next_state = RESET_CNT; 119 | end 120 | SLIP: begin 121 | next_state = RESET_CNT; 122 | end 123 | default: begin 124 | next_state = LOCK_INIT; 125 | end 126 | endcase 127 | 128 | end 129 | 130 | always_ff @(posedge i_clk) begin 131 | if (i_reset) begin 132 | rx_block_lock <= 1'b0; 133 | test_sh <= 1'b0; 134 | sh_cnt <= 0; 135 | sh_invalid_cnt <= 0; 136 | slip_done <= 1'b0; 137 | end else begin 138 | case (state) 139 | LOCK_INIT: begin 140 | rx_block_lock <= 1'b0; 141 | test_sh <= 1'b0; 142 | end 143 | RESET_CNT: begin 144 | sh_cnt <= 0; 145 | sh_invalid_cnt <= 0; 146 | slip_done <= 1'b0; 147 | end 148 | TEST_SH: begin 149 | test_sh <= 1'b0; 150 | end 151 | VALID_SH: begin 152 | sh_cnt <= (i_valid) ? sh_cnt + 1 : sh_cnt; 153 | end 154 | INVALID_SH: begin 155 | sh_cnt <= (i_valid) ? sh_cnt + 1 : sh_cnt; 156 | sh_invalid_cnt <= (i_valid) ? sh_invalid_cnt + 1 : sh_invalid_cnt; 157 | end 158 | GOOD_64: begin 159 | rx_block_lock <= 1'b0; 160 | end 161 | SLIP: begin 162 | rx_block_lock <= 1'b0; 163 | end 164 | default: begin 165 | rx_block_lock <= 1'b0; 166 | test_sh <= 1'b0; 167 | sh_cnt <= 0; 168 | sh_invalid_cnt <= 0; 169 | slip_done <= 1'b0; 170 | end 171 | endcase 172 | end 173 | end 174 | 175 | endmodule 176 | -------------------------------------------------------------------------------- /example/hdl/example_10g_eth.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: example_10g_eth 25 | * 26 | * Description: Example design for low-latency 10G Ethernet core. 27 | * This design generates test packets and measures latency when transceiver 28 | * is in loopback. 29 | * 30 | */ 31 | 32 | `timescale 1ns/1ps 33 | `default_nettype none 34 | 35 | module example_10g_eth #( 36 | parameter bit SCRAMBLER_BYPASS = 0, 37 | parameter bit EXTERNAL_GEARBOX = 0, 38 | parameter bit TX_XVER_BUFFER = 0, 39 | parameter real INIT_CLK_FREQ = 100.0 40 | ) ( 41 | 42 | input wire i_init_clk, 43 | 44 | // Differential reference clock inputs 45 | input wire i_mgtrefclk0_x0y3_p, 46 | input wire i_mgtrefclk0_x0y3_n, 47 | 48 | // Serial data ports for transceiver channel 0 49 | input wire i_ch0_gtyrxn_in, 50 | input wire i_ch0_gtyrxp_in, 51 | output wire o_ch0_gtytxn_out, 52 | output wire o_ch0_gtytxp_out 53 | ); 54 | 55 | /********* Internal Declarations ********/ 56 | wire packet_gen_reset; 57 | logic [1:0] reset_cdc; 58 | wire core_reset; 59 | 60 | // Packet gen 61 | wire [15:0] packet_length; 62 | logic [15:0] packet_length_cnt; 63 | logic [31:0] packet_vio_data; 64 | 65 | // Tx AXIS 66 | wire s00_axis_aclk; 67 | wire [31:0] s00_axis_tdata; 68 | wire [3:0] s00_axis_tkeep; 69 | wire s00_axis_tvalid; 70 | wire s00_axis_tready; 71 | wire s00_axis_tlast; 72 | 73 | // Rx AXIS 74 | wire m00_axis_aclk; 75 | wire [31:0] m00_axis_tdata; 76 | wire [3:0] m00_axis_tkeep; 77 | wire m00_axis_tvalid; 78 | wire m00_axis_tlast; 79 | wire m00_axis_tuser; 80 | 81 | // Resets 82 | wire mac_pcs_rx_reset, mac_pcs_tx_reset; 83 | 84 | // Performance meas 85 | wire [15:0] perf_latency; 86 | wire perf_complete; 87 | wire perf_tx_start, perf_rx_stop; 88 | 89 | /********* Transmit Packet Gen ********/ 90 | 91 | always_ff @(posedge s00_axis_aclk) 92 | if (packet_gen_reset) begin 93 | packet_length_cnt <= '0; 94 | end else if (s00_axis_tready) begin 95 | packet_length_cnt <= (packet_length_cnt == packet_length) ? '0 : packet_length_cnt + 1; 96 | end 97 | 98 | assign s00_axis_tdata = {packet_vio_data[31:16], packet_length_cnt}; // Set lower 16 bits to packet index counter 99 | assign s00_axis_tkeep = '1; 100 | assign s00_axis_tlast = packet_length_cnt == packet_length; 101 | assign s00_axis_tvalid = !packet_gen_reset; 102 | 103 | /********* Latency Measurement ********/ 104 | 105 | assign perf_tx_start = s00_axis_tvalid && s00_axis_tready && s00_axis_tdata[15:0] == 16'b0; 106 | assign perf_rx_stop = m00_axis_tvalid && m00_axis_tdata[15:0] == 16'b0; 107 | 108 | eth_perf u_eth_perf ( 109 | .i_tx_reset(mac_pcs_tx_reset), 110 | .i_tx_clk(s00_axis_aclk), 111 | .i_tx_start(perf_tx_start), 112 | .o_latency(perf_latency), 113 | .o_test_complete(perf_complete), 114 | 115 | .i_rx_stop(perf_rx_stop) 116 | ); 117 | 118 | /********* Debug Cores ********/ 119 | 120 | // Packet Gen VIO 121 | eth_core_control_vio u_packet_control_vio ( 122 | .clk(s00_axis_aclk), // input wire clk 123 | .probe_out0(packet_gen_reset), // output wire [0 : 0] probe_out0 124 | .probe_out1(packet_length), // output wire [15 : 0] probe_out1 125 | .probe_out2(packet_vio_data) // output wire [63 : 0] probe_out2 126 | ); 127 | 128 | eth_core_control_vio u_core_reset_vio ( 129 | .clk(i_init_clk), // input wire clk 130 | .probe_out0(core_reset), // output wire [0 : 0] probe_out0 131 | .probe_out1(), // output wire [15 : 0] probe_out1 132 | .probe_out2() // output wire [63 : 0] probe_out2 133 | ); 134 | 135 | // Data monitor ILAs 136 | example_packet_ila tx_packet_ila ( 137 | .clk(s00_axis_aclk), // input wire clk 138 | .probe0(s00_axis_tdata), // input wire [63:0] probe0 139 | .probe1(s00_axis_tkeep), // input wire [7:0] probe1 140 | .probe2(s00_axis_tready), // input wire [0:0] probe2 141 | .probe3(s00_axis_tvalid), // input wire [0:0] probe3 142 | .probe4(s00_axis_tlast), // input wire [0:0] probe4 143 | .probe5(perf_latency), 144 | .probe6(perf_complete) 145 | ); 146 | 147 | example_packet_ila rx_packet_ila ( 148 | .clk(m00_axis_aclk), // input wire clk 149 | .probe0(m00_axis_tdata), // input wire [63:0] probe0 150 | .probe1(m00_axis_tkeep), // input wire [7:0] probe1 151 | .probe2(m00_axis_tuser), // input wire [0:0] probe2 152 | .probe3(m00_axis_tvalid), // input wire [0:0] probe3 153 | .probe4(m00_axis_tlast), // input wire [0:0] probe4 154 | .probe5(16'h0), 155 | .probe6(perf_rx_stop) 156 | ); 157 | 158 | /********* Ethernet Core ********/ 159 | 160 | eth_10g #( 161 | .SCRAMBLER_BYPASS(SCRAMBLER_BYPASS), 162 | .EXTERNAL_GEARBOX(EXTERNAL_GEARBOX), 163 | .TX_XVER_BUFFER(TX_XVER_BUFFER), 164 | .INIT_CLK_FREQ(INIT_CLK_FREQ) 165 | ) u_eth_10g ( 166 | .i_reset(core_reset), 167 | .i_init_clk(i_init_clk), 168 | .i_mgtrefclk0_x0y3_p(i_mgtrefclk0_x0y3_p), 169 | .i_mgtrefclk0_x0y3_n(i_mgtrefclk0_x0y3_n), 170 | .s00_axis_aclk(s00_axis_aclk), 171 | .s00_axis_tdata(s00_axis_tdata), 172 | .s00_axis_tkeep(s00_axis_tkeep), 173 | .s00_axis_tvalid(s00_axis_tvalid), 174 | .s00_axis_tready(s00_axis_tready), 175 | .s00_axis_tlast(s00_axis_tlast), 176 | .m00_axis_aclk(m00_axis_aclk), 177 | .m00_axis_tdata(m00_axis_tdata), 178 | .m00_axis_tkeep(m00_axis_tkeep), 179 | .m00_axis_tvalid(m00_axis_tvalid), 180 | .m00_axis_tlast(m00_axis_tlast), 181 | .m00_axis_tuser(m00_axis_tuser), 182 | .i_ch0_gtyrxn(i_ch0_gtyrxn_in), 183 | .i_ch0_gtyrxp(i_ch0_gtyrxp_in), 184 | .o_ch0_gtytxn(o_ch0_gtytxn_out), 185 | .o_ch0_gtytxp(o_ch0_gtytxp_out), 186 | .o_mac_pcs_tx_reset(mac_pcs_tx_reset), 187 | .o_mac_pcs_rx_reset(mac_pcs_rx_reset) 188 | ); 189 | 190 | 191 | endmodule 192 | -------------------------------------------------------------------------------- /src/hdl/eth_10g.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: eth_10g 25 | * 26 | * Description: Top-level low-latency 10G Ethernet Core. Includes MAC, PCS and Xilinx 27 | * GTY instantiation. 28 | * 29 | * Note: 30 | * A non-standard implementation of TUSER is used for the AXIS master. 31 | * If TUSER is not asserted with TLAST, this indicates a packet was recieved with 32 | * incorrect CRC. However, TLAST/TUSER can be asserted when all TKEEP == 0, this is to 33 | * provide data for processing ASAP. This may cause TUSER to be dropped if routing 34 | * the AXIS interface through interconnect. 35 | * 36 | */ 37 | 38 | `timescale 1ns/1ps 39 | `default_nettype none 40 | 41 | module eth_10g #( 42 | parameter bit SCRAMBLER_BYPASS = 0, 43 | parameter bit EXTERNAL_GEARBOX = 0, 44 | parameter bit TX_XVER_BUFFER = 0, 45 | parameter real INIT_CLK_FREQ = 100.0 46 | ) ( 47 | // Reset + initiliaszation 48 | input wire i_reset, 49 | input wire i_init_clk, 50 | 51 | // Differential reference clock inputs 52 | input wire i_mgtrefclk0_x0y3_p, 53 | input wire i_mgtrefclk0_x0y3_n, 54 | 55 | /* svlint off prefix_input */ 56 | /* svlint off prefix_output */ 57 | // Tx AXIS 58 | output wire s00_axis_aclk, 59 | input wire [31:0] s00_axis_tdata, 60 | input wire [3:0] s00_axis_tkeep, 61 | input wire s00_axis_tvalid, 62 | output logic s00_axis_tready, 63 | input wire s00_axis_tlast, 64 | 65 | // Rx AXIS 66 | output wire m00_axis_aclk, 67 | output logic [31:0] m00_axis_tdata, 68 | output logic [3:0] m00_axis_tkeep, 69 | output logic m00_axis_tvalid, 70 | output logic m00_axis_tlast, 71 | output logic m00_axis_tuser, 72 | /* svlint on prefix_input */ 73 | /* svlint on prefix_output */ 74 | 75 | // Serial data ports for transceiver channel 0 76 | input wire i_ch0_gtyrxn, 77 | input wire i_ch0_gtyrxp, 78 | output wire o_ch0_gtytxn, 79 | output wire o_ch0_gtytxp, 80 | 81 | // Output tx/rx mac/pcs i_reset ports 82 | output wire o_mac_pcs_tx_reset, 83 | output wire o_mac_pcs_rx_reset 84 | ); 85 | 86 | // MAC/PCS i_reset 87 | wire gtwiz_tx_ready; 88 | wire gtwiz_rx_ready; 89 | 90 | assign o_mac_pcs_tx_reset = !gtwiz_tx_ready; 91 | assign o_mac_pcs_rx_reset = !gtwiz_rx_ready; 92 | 93 | // Datapath 94 | logic [31:0] pcs_xver_tx_data, xver_tx_data; 95 | logic [1:0] pcs_xver_tx_header, xver_tx_header; 96 | wire [31:0] pcs_xver_rx_data; 97 | wire [1:0] pcs_xver_rx_header; 98 | 99 | // Clock 100 | wire gtwiz_tx_usrclk2; 101 | wire gtwiz_rx_usrclk2; 102 | 103 | // Gearbox 104 | logic [5:0] pcs_xver_tx_gearbox_sequence, xver_tx_gearbox_sequence; 105 | wire pcs_xver_rx_data_valid; 106 | wire pcs_xver_rx_header_valid; 107 | wire pcs_xver_rx_gearbox_slip; 108 | 109 | assign m00_axis_aclk = gtwiz_rx_usrclk2; 110 | assign s00_axis_aclk = gtwiz_tx_usrclk2; 111 | 112 | mac_pcs #( 113 | .SCRAMBLER_BYPASS(SCRAMBLER_BYPASS), 114 | .EXTERNAL_GEARBOX(EXTERNAL_GEARBOX) 115 | ) u_mac_pcs ( 116 | .i_tx_reset(o_mac_pcs_tx_reset), 117 | .i_rx_reset(o_mac_pcs_rx_reset), 118 | .s00_axis_tdata(s00_axis_tdata), 119 | .s00_axis_tkeep(s00_axis_tkeep), 120 | .s00_axis_tvalid(s00_axis_tvalid), 121 | .s00_axis_tready(s00_axis_tready), 122 | .s00_axis_tlast(s00_axis_tlast), 123 | .m00_axis_tdata(m00_axis_tdata), 124 | .m00_axis_tkeep(m00_axis_tkeep), 125 | .m00_axis_tvalid(m00_axis_tvalid), 126 | .m00_axis_tlast(m00_axis_tlast), 127 | .m00_axis_tuser(m00_axis_tuser), 128 | .i_xver_rx_clk(gtwiz_rx_usrclk2), 129 | .i_xver_rx_data(pcs_xver_rx_data), 130 | .i_xver_rx_header(pcs_xver_rx_header), 131 | .i_xver_rx_data_valid(pcs_xver_rx_data_valid), 132 | .i_xver_rx_header_valid(pcs_xver_rx_header_valid), 133 | .o_xver_rx_gearbox_slip(pcs_xver_rx_gearbox_slip), 134 | .i_xver_tx_clk(gtwiz_tx_usrclk2), 135 | .o_xver_tx_data(pcs_xver_tx_data), 136 | .o_xver_tx_header(pcs_xver_tx_header), 137 | .o_xver_tx_gearbox_sequence(pcs_xver_tx_gearbox_sequence) 138 | ); 139 | 140 | 141 | generate if (TX_XVER_BUFFER == 1) begin: l_tx_xver_buffer 142 | always_ff @(posedge gtwiz_tx_usrclk2) 143 | if (o_mac_pcs_tx_reset) begin 144 | xver_tx_data <= '0; 145 | xver_tx_header <= '0; 146 | xver_tx_gearbox_sequence <= '0; 147 | end else begin 148 | xver_tx_data <= pcs_xver_tx_data; 149 | xver_tx_header <= pcs_xver_tx_header; 150 | xver_tx_gearbox_sequence <= pcs_xver_tx_gearbox_sequence; 151 | end 152 | end else begin: l_tx_xver_nobuf 153 | assign xver_tx_data = pcs_xver_tx_data; 154 | assign xver_tx_header = pcs_xver_tx_header; 155 | assign xver_tx_gearbox_sequence = pcs_xver_tx_gearbox_sequence; 156 | end 157 | endgenerate 158 | 159 | gtwizard_wrapper #( 160 | .INIT_CLK_FREQ(INIT_CLK_FREQ), 161 | .EXTERNAL_GEARBOX(EXTERNAL_GEARBOX) 162 | ) u_gtwizard_wrapper ( 163 | 164 | // Differential reference clock inputs 165 | .mgtrefclk0_x0y3_p(i_mgtrefclk0_x0y3_p), 166 | .mgtrefclk0_x0y3_n(i_mgtrefclk0_x0y3_n), 167 | 168 | // Serial data ports for transceiver channel 0 169 | .ch0_gtyrxn_in(i_ch0_gtyrxn), 170 | .ch0_gtyrxp_in(i_ch0_gtyrxp), 171 | .ch0_gtytxn_out(o_ch0_gtytxn), 172 | .ch0_gtytxp_out(o_ch0_gtytxp), 173 | 174 | // User-provided ports for i_reset helper block(s) 175 | .hb_gtwiz_reset_clk_freerun_in(i_init_clk), 176 | .hb_gtwiz_reset_all_in(i_reset), 177 | 178 | // User data ports 179 | .hb0_gtwiz_userdata_tx_int(xver_tx_data), 180 | .hb0_gtwiz_header_tx(xver_tx_header), 181 | .hb0_gtwiz_userdata_rx_int(pcs_xver_rx_data), 182 | .hb0_gtwiz_header_rx(pcs_xver_rx_header), 183 | 184 | .hb0_gtwiz_rx_gearbox_slip(pcs_xver_rx_gearbox_slip), 185 | .hb0_gtwiz_rx_data_valid(pcs_xver_rx_data_valid), 186 | .hb0_gtwiz_rx_header_valid(pcs_xver_rx_header_valid), 187 | .hb0_gtwiz_tx_gearbox_sequence(xver_tx_gearbox_sequence), 188 | 189 | // Transceiver user clock outputs 190 | .hb0_gtwiz_userclk_tx_usrclk2(gtwiz_tx_usrclk2), 191 | .hb0_gtwiz_userclk_rx_usrclk2(gtwiz_rx_usrclk2), 192 | 193 | // Transceiver ready/error outputs 194 | .tx_ready(gtwiz_tx_ready), 195 | .rx_ready(gtwiz_rx_ready) 196 | ); 197 | 198 | endmodule 199 | -------------------------------------------------------------------------------- /src/hdl/pcs/rx_gearbox.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: rx_gearbox 25 | * 26 | * Description: 64b66b receive synchronus gearbox with 32-bit interface. Takes 27 | * 2-3 32-bit words and outputs a 64-bit frame and two bit header over 28 | * two cycles. The header is valid when the lower 32-bits of the frame is output. 29 | * 30 | * The design was modelled in python which can be found in src/tb/gearbox. 31 | * 32 | * Input data is constructed in an output buffer depending on the input sequence 33 | * which counts from 0-32. No data is output when seq == 0. By default, obuf[1:0] is output 34 | * as the header, obuf[33:2] is output as the first (lower) 32-bits of the frame, and obuf[65:34] 35 | * is output as the second (higher) 32-bits of the frame. 36 | * 37 | * Frame alignment is performed with the i_slip input. This pauses the sequence counter 38 | * and slips the input data by two positions. Every other assertation of i_slip causes a 39 | * half slip, where the data output from the buffer is shifted up by one - i.e. the header 40 | * output is obuf[2:1] and data obuf[34:3] / obuf[66:35]. Bit 66 of obuf is mirrored to bit 0 to 41 | * support this. Additionaly, the cycle at which the data output is paused is modified when 42 | * half slipping. 43 | * 44 | */ 45 | 46 | `timescale 1ns/1ps 47 | `default_nettype none 48 | 49 | module rx_gearbox #( 50 | parameter bit REGISTER_OUTPUT = 1, 51 | localparam int DATA_WIDTH = 32, 52 | localparam int HEADER_WIDTH = 2, 53 | localparam int SEQUENCE_WIDTH = 6 54 | ) ( 55 | input wire i_clk, 56 | input wire i_reset, 57 | input wire [DATA_WIDTH-1:0] i_data, 58 | 59 | input wire i_slip, 60 | 61 | output logic [DATA_WIDTH-1:0] o_data, 62 | output logic [HEADER_WIDTH-1:0] o_header, 63 | output logic o_data_valid, 64 | output logic o_header_valid 65 | ); 66 | 67 | localparam int BUF_SIZE = 2*DATA_WIDTH + HEADER_WIDTH + 1; // Extra bit at top of buffer to allow for single bit slip 68 | 69 | /********* Sequence Counter ********/ 70 | // Re-use the gearbox sequnce counter method as used in gty - this time counting every clock 71 | 72 | // Create the sequence counter - 0 to 32 73 | logic [SEQUENCE_WIDTH-1:0] gearbox_seq; 74 | logic half_slip; 75 | 76 | always_ff @(posedge i_clk) 77 | if (i_reset) begin 78 | gearbox_seq <= '0; 79 | half_slip <= '0; 80 | end else begin 81 | if (!i_slip) begin 82 | gearbox_seq <= gearbox_seq < 32 ? gearbox_seq + 1 : '0; 83 | end 84 | 85 | if (i_slip) begin 86 | half_slip <= ~half_slip; 87 | end 88 | end 89 | 90 | /********* Buffer Construction ********/ 91 | 92 | logic [BUF_SIZE-1:0] obuf, next_obuf; 93 | wire frame_word; 94 | logic [6:0] data_idxs[33]; // For each counter value, the start buffer index to load the data 95 | logic [6:0] current_data_idx; 96 | 97 | assign frame_word = (half_slip) ? !gearbox_seq[0] && gearbox_seq != SEQUENCE_WIDTH'(32) : 98 | !gearbox_seq[0]; 99 | assign current_data_idx = data_idxs[gearbox_seq]; 100 | 101 | // Need to assign single bits as iverilog does not support variable width assignments 102 | generate for (genvar gi = 0; gi < BUF_SIZE; gi++) begin: l_assign_obuf 103 | 104 | always @(*) begin 105 | 106 | next_obuf[gi] = obuf[gi]; 107 | 108 | if (gi == BUF_SIZE - 1) begin 109 | next_obuf[gi] = next_obuf[0]; // Mirror top/bottom bits to allow for half sequence slip 110 | end else begin 111 | if (frame_word) begin 112 | if (gearbox_seq != 0 && gi >= current_data_idx) begin 113 | next_obuf[gi] = i_data[gi - current_data_idx]; 114 | end else if (gi < DATA_WIDTH - gearbox_seq) begin 115 | next_obuf[gi] = i_data[gi + gearbox_seq]; 116 | end 117 | 118 | end else begin 119 | if (gi >= current_data_idx && gi < current_data_idx + 32) begin 120 | next_obuf[gi] = i_data[gi - current_data_idx]; 121 | end 122 | end 123 | end 124 | 125 | end 126 | 127 | end endgenerate 128 | 129 | always_ff @(posedge i_clk) 130 | if (i_reset) begin 131 | obuf <= '0; 132 | end else begin 133 | obuf <= next_obuf; 134 | end 135 | 136 | /********* Output Data ********/ 137 | 138 | wire [DATA_WIDTH-1:0] odata_int; 139 | wire [HEADER_WIDTH-1:0] oheader_int; 140 | wire odata_valid_int; 141 | wire oheader_valid_int; 142 | 143 | assign odata_valid_int = half_slip ? gearbox_seq != 31 : gearbox_seq != 0; 144 | assign oheader_int = half_slip ? next_obuf[2:1] : next_obuf[1:0]; 145 | assign oheader_valid_int = !frame_word && odata_valid_int; 146 | assign odata_int = half_slip ? (!frame_word ? next_obuf[34:3] : next_obuf[66:35]) : 147 | (!frame_word ? next_obuf[33:2] : next_obuf[65:34]); 148 | 149 | generate if (REGISTER_OUTPUT) begin: l_register_output 150 | always_ff @(posedge i_clk) 151 | if (i_reset) begin 152 | o_data_valid <= '0; 153 | o_header <= '0; 154 | o_header_valid <= '0; 155 | o_data <= '0; 156 | end else begin 157 | o_data_valid <= odata_valid_int; 158 | o_header <= (oheader_valid_int) ? oheader_int : o_header; 159 | o_header_valid <= oheader_valid_int; 160 | o_data <= (odata_valid_int) ? odata_int : o_data; 161 | end 162 | end else begin: l_comb_output 163 | assign o_data_valid = odata_valid_int; 164 | assign o_header = oheader_int; 165 | assign o_header_valid = oheader_valid_int; 166 | assign o_data = odata_int; 167 | end endgenerate 168 | 169 | // Clearest way to sequence loading of data as modelled 170 | // iverilog doesn't support entire array assignment 171 | initial begin 172 | data_idxs[0] = 00; 173 | data_idxs[1] = 32; 174 | data_idxs[2] = 64; 175 | data_idxs[3] = 30; 176 | data_idxs[4] = 62; 177 | data_idxs[5] = 28; 178 | data_idxs[6] = 60; 179 | data_idxs[7] = 26; 180 | data_idxs[8] = 58; 181 | data_idxs[9] = 24; 182 | data_idxs[10] = 56; 183 | data_idxs[11] = 22; 184 | data_idxs[12] = 54; 185 | data_idxs[13] = 20; 186 | data_idxs[14] = 52; 187 | data_idxs[15] = 18; 188 | data_idxs[16] = 50; 189 | data_idxs[17] = 16; 190 | data_idxs[18] = 48; 191 | data_idxs[19] = 14; 192 | data_idxs[20] = 46; 193 | data_idxs[21] = 12; 194 | data_idxs[22] = 44; 195 | data_idxs[23] = 10; 196 | data_idxs[24] = 42; 197 | data_idxs[25] = 8; 198 | data_idxs[26] = 40; 199 | data_idxs[27] = 6; 200 | data_idxs[28] = 38; 201 | data_idxs[29] = 4; 202 | data_idxs[30] = 36; 203 | data_idxs[31] = 2; 204 | data_idxs[32] = 34; 205 | end 206 | 207 | endmodule 208 | -------------------------------------------------------------------------------- /src/tb/pcs/test_pcs.py: -------------------------------------------------------------------------------- 1 | from asyncore import loop 2 | import asyncio 3 | import cocotb 4 | from cocotb.triggers import Timer, RisingEdge, FallingEdge, Edge, NextTimeStep, First 5 | from cocotb.clock import Clock 6 | from cocotb.result import TestFailure 7 | from cocotb.queue import Queue 8 | 9 | from pcs_test_vector import PCSTestVector 10 | 11 | import debugpy 12 | 13 | class PCS_TB: 14 | def __init__(self, dut, loopback=False): 15 | self.dut = dut 16 | 17 | # debugpy.listen(5678) 18 | # debugpy.wait_for_client() 19 | # debugpy.breakpoint() 20 | 21 | self.data_width = len(self.dut.i_xgmii_tx_data) 22 | self.data_nbytes = self.data_width // 8 23 | self.clk_period = round(1 / (10.3125 / self.data_width), 2) # ps precision 24 | self.gearbox_pause_val = 31 if self.data_width == 32 else 32 25 | 26 | cocotb.start_soon(Clock(dut.i_xver_tx_clk, self.clk_period, units="ns").start()) 27 | cocotb.start_soon(Clock(dut.i_xver_rx_clk, self.clk_period, units="ns").start()) 28 | 29 | if loopback: cocotb.start_soon(self.loopback()) 30 | 31 | # default to idle frame 32 | self.dut.i_xgmii_tx_data.value = int(''.join(["07" for _ in range(self.data_nbytes)]), 16) 33 | self.dut.i_xgmii_tx_ctl.value = int(''.join(["1" for _ in range(self.data_nbytes)]), 2) 34 | self.dut.o_xgmii_rx_data.value = int("0x0", 16) 35 | 36 | async def change_reset(self, val): 37 | self.dut.i_tx_reset.value = val 38 | self.dut.i_rx_reset.value = val 39 | 40 | await RisingEdge(self.dut.i_xver_tx_clk) 41 | await RisingEdge(self.dut.i_xver_rx_clk) 42 | 43 | 44 | async def reset(self): 45 | await self.change_reset(0) 46 | await self.change_reset(1) 47 | self.dut.i_tx_reset.value = 0 48 | self.dut.i_rx_reset.value = 0 49 | 50 | async def loopback(self, delay=1): 51 | 52 | async def capture_outputs(self, q, delay): 53 | for _ in range(delay): await q.put([0, 0, 0, 0]) 54 | prev_tx_gearbox_seq = 0 55 | while True: 56 | await RisingEdge(self.dut.i_xver_tx_clk) 57 | 58 | o_xver_tx_data = self.dut.o_xver_tx_data.value 59 | o_xver_tx_header = self.dut.o_xver_tx_header.value 60 | xver_tx_data_valid = self.dut.o_xver_tx_gearbox_sequence.value != self.gearbox_pause_val 61 | 62 | xver_tx_header_valid = self.dut.o_xver_tx_gearbox_sequence.value != prev_tx_gearbox_seq and xver_tx_data_valid 63 | prev_tx_gearbox_seq = self.dut.o_xver_tx_gearbox_sequence.value 64 | 65 | await q.put([o_xver_tx_data, o_xver_tx_header, xver_tx_data_valid, xver_tx_header_valid]) 66 | 67 | async def apply_input(self, q): 68 | while True: 69 | await RisingEdge(self.dut.i_xver_rx_clk) 70 | [o_xver_tx_data, o_xver_tx_header, xver_tx_data_valid, xver_tx_header_valid] = await q.get() 71 | 72 | self.dut.i_xver_rx_data.value = o_xver_tx_data 73 | self.dut.i_xver_rx_header.value = o_xver_tx_header 74 | self.dut.i_xver_rx_data_valid.value = xver_tx_data_valid 75 | self.dut.i_xver_rx_header_valid.value = xver_tx_header_valid 76 | 77 | 78 | 79 | q = Queue() 80 | cocotb.start_soon(capture_outputs(self, q, delay)) 81 | cocotb.start_soon(apply_input(self, q)) 82 | 83 | 84 | 85 | 86 | # 87 | # Test transmit encoding and scrambling with sample test vector 88 | # 89 | @cocotb.test() 90 | async def encode_scramble_test(dut): 91 | 92 | tb = PCS_TB(dut) 93 | test_vector = PCSTestVector() 94 | 95 | await tb.reset() 96 | 97 | # Wait for one cycle before applying test vector to align the encoder - but 98 | # hold the scrambler data so the initial state lines up with the test vector 99 | await RisingEdge(tb.dut.i_xver_tx_clk) 100 | 101 | # set up tx output monitor 102 | # we need to probe internal signals to check data without having to undo the gearing 103 | async def monitor_tx_scrambled_data(): 104 | frame_index = 0 105 | timeout_index = 0 106 | while True: 107 | await RisingEdge(tb.dut.i_xver_tx_clk) 108 | timeout_index += 1 109 | if tb.dut.o_xgmii_tx_ready.value: 110 | 111 | if frame_index != 0: # ensure frames are correct sequentially 112 | assert (tb.dut.tx_header.value, tb.dut.tx_scrambled_data.value) == \ 113 | test_vector.eg_scrambled_data[tb.data_width][frame_index], \ 114 | f'tx frame index {frame_index} incorrect. ' + \ 115 | f'({tb.dut.tx_header.value},{tb.dut.tx_scrambled_data.value.integer:08x}) != ' + \ 116 | f'({test_vector.eg_scrambled_data[tb.data_width][frame_index][0]:02b}, {test_vector.eg_scrambled_data[tb.data_width][frame_index][1]:08x})' 117 | 118 | if (tb.dut.tx_header.value, tb.dut.tx_scrambled_data.value) == \ 119 | test_vector.eg_scrambled_data[tb.data_width][frame_index]: 120 | print(f'Found frame {frame_index}') 121 | frame_index += 1 122 | timeout_index = 0 123 | if(frame_index == len(test_vector.eg_scrambled_data[tb.data_width])): 124 | return 125 | 126 | if timeout_index >= 10: 127 | raise TestFailure('Waiting for eg tx frame timed out') 128 | 129 | tx_monitor = cocotb.start_soon(monitor_tx_scrambled_data()) 130 | 131 | # tx example frame 132 | for ctl, data in test_vector.eg_xgmii_data[tb.data_width][1:]: # skip first idle frame as this 133 | # throws scrambler off wrt example data 134 | await RisingEdge(tb.dut.i_xver_tx_clk) 135 | if tb.dut.o_xgmii_tx_ready.value: 136 | tb.dut.i_xgmii_tx_data.value = data 137 | tb.dut.i_xgmii_tx_ctl.value = ctl 138 | 139 | await tx_monitor 140 | 141 | 142 | # 143 | # Test tx -> rx chain in loopback with sample test vector 144 | # 145 | 146 | @cocotb.test() 147 | async def tx_rx_loopback_test(dut): 148 | 149 | tb = PCS_TB(dut, loopback=True) 150 | test_vector = PCSTestVector() 151 | 152 | await tb.reset() 153 | 154 | # set up tx output monitor 155 | async def monitor_rx_data(): 156 | start_frame = 1 if tb.data_width == 64 else 2 # wait on first non-idle frame 157 | frame_index = start_frame 158 | timeout_index = 0 159 | while True: 160 | await RisingEdge(tb.dut.i_xver_rx_clk) 161 | timeout_index += 1 162 | if tb.dut.o_xgmii_rx_valid.value: 163 | 164 | if frame_index != start_frame: # ensure frames are correct sequentially 165 | assert (tb.dut.o_xgmii_rx_ctl.value, tb.dut.o_xgmii_rx_data.value) == \ 166 | test_vector.eg_xgmii_data[tb.data_width][frame_index], \ 167 | f'rx frame index {frame_index} incorrect. ' + \ 168 | f'({tb.dut.o_xgmii_rx_ctl.value},{tb.dut.o_xgmii_rx_data.value.integer:08x}) != ' + \ 169 | f'({test_vector.eg_xgmii_data[tb.data_width][frame_index][0]:08b}, {test_vector.eg_xgmii_data[tb.data_width][frame_index][1]:08x})' 170 | 171 | if (tb.dut.o_xgmii_rx_ctl.value, tb.dut.o_xgmii_rx_data.value) == \ 172 | test_vector.eg_xgmii_data[tb.data_width][frame_index]: 173 | print(f'Found frame {frame_index}') 174 | frame_index += 1 175 | timeout_index = 0 176 | if(frame_index == len(test_vector.eg_xgmii_data[tb.data_width])): 177 | return 178 | 179 | if timeout_index >= 10: 180 | raise TestFailure('Waiting for rx frame timed out') 181 | 182 | 183 | 184 | # transmit idles to allow gearbox to sync 185 | for _ in range(200): 186 | await RisingEdge(tb.dut.i_xver_tx_clk) 187 | 188 | rx_monitor = cocotb.start_soon(monitor_rx_data()) 189 | 190 | # tx example frame 191 | for ctl, data in test_vector.eg_xgmii_data[tb.data_width]: 192 | await RisingEdge(tb.dut.i_xver_tx_clk) 193 | if tb.dut.o_xgmii_tx_ready.value: 194 | tb.dut.i_xgmii_tx_data.value = data 195 | tb.dut.i_xgmii_tx_ctl.value = ctl 196 | 197 | await rx_monitor 198 | -------------------------------------------------------------------------------- /src/hdl/pcs/encoder.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: encoder 25 | * 26 | * Description: XGMII RS to 64b66b encoder. 32-bit data I/O. 27 | * 28 | */ 29 | 30 | `timescale 1ns/1ps 31 | `default_nettype none 32 | `include "code_defs_pkg.svh" 33 | 34 | module encoder #( 35 | parameter bit OCODE_SUPPORT = 0, 36 | localparam int DATA_WIDTH = 32, 37 | localparam int DATA_NBYTES = DATA_WIDTH / 8 38 | ) ( 39 | 40 | input wire i_reset, 41 | input wire i_init_done, 42 | 43 | // TX Interface from MAC 44 | input wire i_txc, 45 | input wire[DATA_WIDTH-1:0] i_txd, 46 | input wire[DATA_NBYTES-1:0] i_txctl, 47 | 48 | // Input from gearbox 49 | input wire i_tx_pause, 50 | input wire i_frame_word, 51 | 52 | // TX Interface out 53 | output wire [DATA_WIDTH-1:0] o_txd, 54 | output wire [1:0] o_tx_header 55 | ); 56 | 57 | import code_defs_pkg::*; 58 | 59 | // 32-bit input to 64 bit internal 60 | 61 | logic [31:0] delayed_itxd; 62 | logic [3:0] delayed_itxctl; 63 | wire [63:0] internal_itxd; 64 | wire [7:0] internal_itxctl; 65 | 66 | wire [63:0] internal_otxd; 67 | wire [1:0] internal_oheader; 68 | // verilator lint_off UNUSED 69 | // While lower word unused, useful for debugging 70 | logic [63:0] delayed_internal_otxd; 71 | logic [1:0] delayed_internal_header; 72 | // verilator lint_on UNUSED 73 | 74 | always_ff @(posedge i_txc) begin 75 | if (i_reset) begin 76 | delayed_itxd <= '0; 77 | delayed_itxctl <= '0; 78 | delayed_internal_otxd <= '0; 79 | delayed_internal_header <= '0; 80 | end else begin 81 | if (!i_tx_pause) begin 82 | delayed_itxd <= i_txd; 83 | delayed_itxctl <= i_txctl; 84 | 85 | if (!i_frame_word) begin 86 | delayed_internal_otxd <= internal_otxd; 87 | delayed_internal_header <= internal_oheader; 88 | end 89 | end 90 | end 91 | end 92 | 93 | assign internal_itxd = {i_txd, delayed_itxd}; 94 | assign internal_itxctl = {i_txctl, delayed_itxctl}; 95 | 96 | assign o_tx_header = i_frame_word ? delayed_internal_header : internal_oheader; 97 | assign o_txd = i_frame_word ? delayed_internal_otxd[32 +: 32] : internal_otxd[0 +: 32]; 98 | 99 | 100 | // Tx encoding 101 | logic [63:0] enc_tx_data; 102 | assign internal_oheader = (internal_itxctl == '0) ? SYNC_DATA : SYNC_CTL; 103 | 104 | // Data is transmitted lsb first, first byte is in txd[7:0] 105 | function automatic logic [7:0] get_rs_code(input logic [63:0] idata, input logic [7:0] ictl, input int lane); 106 | assert (lane < 8); 107 | return ictl[lane] == 1'b1 ? idata[8*lane +: 8] : RS_ERROR; 108 | endfunction 109 | 110 | function automatic bit get_all_rs_code(input logic [63:0] idata, input logic [7:0] ictl, 111 | input bit [7:0] lanes, input logic[7:0] code); 112 | for (int i = 0; i < 8; i++) begin 113 | if (lanes[i] == 1 && get_rs_code(idata, ictl, i) != code) return 0; 114 | end 115 | return 1; 116 | endfunction 117 | 118 | function automatic bit is_rs_ocode(input logic[7:0] code); 119 | return code == RS_OSEQ || code == RS_OSIG; 120 | endfunction 121 | 122 | function automatic bit is_all_lanes_data(input logic [7:0] ictl, input bit [7:0] lanes); 123 | for (int i = 0; i < 8; i++) begin 124 | if (lanes[i] == 1 && ictl[i] == 1) return 0; 125 | end 126 | return 1; 127 | endfunction 128 | 129 | // Ref 802.3 49.2.4.4 130 | function automatic logic [63:0] encode_frame(input logic [63:0] idata, input logic [7:0] ictl); 131 | if (ictl == '0) begin 132 | return idata; 133 | end else begin 134 | // All Control (IDLE) = CCCCCCCC 135 | if (get_all_rs_code(idata, ictl, 8'hFF, RS_IDLE)) begin 136 | return {{8{CC_IDLE}}, BT_IDLE}; 137 | end 138 | // O4 = CCCCODDD 139 | else if (OCODE_SUPPORT && is_rs_ocode(get_rs_code(idata, ictl, 4)) && is_all_lanes_data(ictl, 8'h07)) begin 140 | return {idata[63:40], rs_to_cc_ocode(get_rs_code(idata, ictl, 4)), {4{CC_IDLE}}, BT_O4}; 141 | end 142 | // S4 = CCCCSDDD 143 | else if (get_all_rs_code(idata, ictl, 8'h0F, RS_IDLE) && (get_rs_code(idata, ictl, 4) == RS_START) && 144 | is_all_lanes_data(ictl, 8'hE0)) begin 145 | return {idata[63:40], 4'b0, {4{CC_IDLE}}, BT_S4}; 146 | end 147 | // O0S4 = ODDDSDDD 148 | else if (OCODE_SUPPORT && is_rs_ocode(get_rs_code(idata, ictl, 0)) && get_rs_code(idata, ictl, 4) == RS_START) begin 149 | return {idata[63:40], 4'b0, rs_to_cc_ocode(get_rs_code(idata, ictl, 0)), idata[23:0], BT_O0S4}; 150 | end 151 | // O0O4 = ODDDODDD 152 | else if (OCODE_SUPPORT && is_rs_ocode(get_rs_code(idata, ictl, 0)) && is_rs_ocode(get_rs_code(idata, ictl, 4))) begin 153 | return {idata[63:40], rs_to_cc_ocode(get_rs_code(idata, ictl, 4)), rs_to_cc_ocode(get_rs_code(idata, ictl, 0)), 154 | idata[23:0], BT_O0O4}; 155 | end 156 | // S0 = SDDDDDDD 157 | else if (get_rs_code(idata, ictl, 0) == RS_START) begin 158 | return {idata[63:8], BT_S0}; 159 | end 160 | // O0 = ODDDCCCC 161 | else if (OCODE_SUPPORT && is_rs_ocode(get_rs_code(idata, ictl, 4))) begin 162 | return {idata[63:36], rs_to_cc_ocode(get_rs_code(idata, ictl, 4)), idata[31:8], BT_O0}; 163 | end 164 | // T0 = TCCCCCCC 165 | else if (get_rs_code(idata, ictl, 0) == RS_TERM) begin 166 | return {56'd0, BT_T0}; 167 | end 168 | // T1 = DTCCCCCC 169 | else if (get_rs_code(idata, ictl, 1) == RS_TERM) begin 170 | return {48'd0, idata[7:0], BT_T1}; 171 | end 172 | // T2 = DDTCCCCC 173 | else if (get_rs_code(idata, ictl, 2) == RS_TERM) begin 174 | return {40'd0, idata[15:0], BT_T2}; 175 | end 176 | // T3 = DDDTCCCC 177 | else if (get_rs_code(idata, ictl, 3) == RS_TERM) begin 178 | return {32'd0, idata[23:0], BT_T3}; 179 | end 180 | // T4 = DDDDTCCC 181 | else if (get_rs_code(idata, ictl, 4) == RS_TERM) begin 182 | return {24'd0, idata[31:0], BT_T4}; 183 | end 184 | // T5 = DDDDDTCC 185 | else if (get_rs_code(idata, ictl, 5) == RS_TERM) begin 186 | return {16'd0, idata[39:0], BT_T5}; 187 | end 188 | // T6 = DDDDDDTC 189 | else if (get_rs_code(idata, ictl, 6) == RS_TERM) begin 190 | return {8'd0, idata[47:0], BT_T6}; 191 | end 192 | // T7 = DDDDDDDT 193 | else if (get_rs_code(idata, ictl, 7) == RS_TERM) begin 194 | return {idata[55:0], BT_T7}; 195 | end 196 | else begin 197 | return {{7{RS_ERROR}}, BT_IDLE}; 198 | end 199 | end 200 | endfunction 201 | 202 | assign enc_tx_data = encode_frame(internal_itxd, internal_itxctl); 203 | 204 | assign internal_otxd = (i_reset || !i_init_done) ? {{7{RS_ERROR}}, BT_IDLE} : enc_tx_data; 205 | 206 | endmodule 207 | -------------------------------------------------------------------------------- /src/hdl/pcs/pcs.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ps 2 | `default_nettype none 3 | `include "code_defs_pkg.svh" 4 | 5 | module pcs #( 6 | parameter bit SCRAMBLER_BYPASS = 0, 7 | parameter bit EXTERNAL_GEARBOX = 0, 8 | parameter bit ENCODER_OCODE_SUPPORT = 0, 9 | 10 | localparam int DATA_WIDTH = 32, 11 | localparam int DATA_NBYTES = DATA_WIDTH / 8 12 | ) ( 13 | 14 | // Clocks 15 | input wire i_xver_rx_clk, 16 | input wire i_xver_tx_clk, 17 | 18 | // Reset logic 19 | input wire i_tx_reset, 20 | input wire i_rx_reset, 21 | 22 | // Rx from tranceiver 23 | input wire [DATA_WIDTH-1:0] i_xver_rx_data, 24 | /* verilator lint_off UNUSED */ 25 | input wire [1:0] i_xver_rx_header, 26 | input wire i_xver_rx_data_valid, 27 | input wire i_xver_rx_header_valid, 28 | output wire o_xver_rx_gearbox_slip, 29 | /* verilator lint_on UNUSED */ 30 | 31 | // Rx XGMII 32 | output logic [DATA_WIDTH-1:0] o_xgmii_rx_data, 33 | output logic [DATA_NBYTES-1:0] o_xgmii_rx_ctl, 34 | output logic o_xgmii_rx_valid, // Non standard XGMII - required for no CDC 35 | output logic [DATA_NBYTES-1:0] o_term_loc, 36 | 37 | // Tx XGMII 38 | input wire[DATA_WIDTH-1:0] i_xgmii_tx_data, 39 | input wire[DATA_NBYTES-1:0] i_xgmii_tx_ctl, 40 | output wire o_xgmii_tx_ready, // Non standard XGMII - required for no CDC 41 | 42 | // TX Interface out 43 | output wire [DATA_WIDTH-1:0] o_xver_tx_data, 44 | output wire [1:0] o_xver_tx_header, 45 | output wire [5:0] o_xver_tx_gearbox_sequence 46 | ); 47 | 48 | import code_defs_pkg::*; 49 | 50 | // ************* TX DATAPATH ************* // 51 | wire [DATA_WIDTH-1:0] tx_encoded_data, tx_scrambled_data; 52 | wire [1:0] tx_header; 53 | wire tx_gearbox_pause; 54 | wire [DATA_WIDTH-1:0] rx_decoded_data; 55 | wire [DATA_NBYTES-1:0] rx_decoded_ctl; 56 | wire rx_data_valid, rx_header_valid; 57 | wire enc_frame_word; 58 | 59 | // Encoder 60 | encoder #( 61 | .OCODE_SUPPORT(ENCODER_OCODE_SUPPORT) 62 | ) u_encoder ( 63 | .i_reset(i_tx_reset), 64 | .i_init_done(!i_tx_reset), 65 | .i_txc(i_xver_tx_clk), 66 | .i_txd(i_xgmii_tx_data), 67 | .i_txctl(i_xgmii_tx_ctl), 68 | .i_tx_pause(tx_gearbox_pause), 69 | .i_frame_word(enc_frame_word), 70 | .o_txd(tx_encoded_data), 71 | .o_tx_header(tx_header) 72 | ); 73 | 74 | // Scrambler 75 | generate if (SCRAMBLER_BYPASS) begin: l_tx_scrambler_bypass 76 | assign tx_scrambled_data = tx_encoded_data; 77 | end else begin: l_tx_scrambler 78 | 79 | scrambler #( 80 | ) u_scrambler( 81 | .i_clk(i_xver_tx_clk), 82 | .i_reset(i_tx_reset), 83 | .i_init_done(!i_tx_reset), 84 | .i_pause(tx_gearbox_pause), 85 | .i_data(tx_encoded_data), 86 | .o_data(tx_scrambled_data) 87 | ); 88 | end 89 | endgenerate 90 | 91 | // Gearbox 92 | generate 93 | if (EXTERNAL_GEARBOX != 0) begin: l_tx_ext_gearbox 94 | 95 | logic [5:0] prev_tx_seq; 96 | 97 | assign o_xver_tx_data = tx_scrambled_data; 98 | assign o_xver_tx_header = tx_header; 99 | 100 | // This assumes gearbox counter counting every other TXUSRCLK when 101 | // TX_DATAWIDTH == TX_INT_DATAWIDTH == 32 ref UG578 v1.3.1 pg 120 on 102 | gearbox_seq #(.WIDTH(6), .MAX_VAL(32), .PAUSE_VAL(32), .HALF_STEP(1)) 103 | u_tx_gearbox_seq ( 104 | .i_clk(i_xver_tx_clk), 105 | .i_reset(i_tx_reset), 106 | .i_slip(1'b0), 107 | .o_count(o_xver_tx_gearbox_sequence), 108 | .o_pause(tx_gearbox_pause) 109 | ); 110 | 111 | // Encode top word on second cycle of sequence 112 | assign enc_frame_word = o_xver_tx_gearbox_sequence == prev_tx_seq; 113 | 114 | always_ff @(posedge i_xver_tx_clk) 115 | if (i_tx_reset) begin 116 | prev_tx_seq <= '0; 117 | end else begin 118 | prev_tx_seq <= o_xver_tx_gearbox_sequence; 119 | end 120 | 121 | end else begin: l_tx_int_gearbox 122 | 123 | wire [5:0] int_tx_gearbox_seq; 124 | 125 | gearbox_seq #(.WIDTH(6), .MAX_VAL(32), .PAUSE_VAL(32), .HALF_STEP(0)) 126 | u_tx_gearbox_seq ( 127 | .i_clk(i_xver_tx_clk), 128 | .i_reset(i_tx_reset), 129 | .i_slip(1'b0), 130 | .o_count(int_tx_gearbox_seq), 131 | .o_pause(tx_gearbox_pause) 132 | ); 133 | 134 | tx_gearbox u_tx_gearbox ( 135 | .i_clk(i_xver_tx_clk), 136 | .i_reset(i_tx_reset), 137 | .i_data(tx_scrambled_data), 138 | .i_header(tx_header), 139 | .i_gearbox_seq(int_tx_gearbox_seq), 140 | .i_pause(tx_gearbox_pause), 141 | .o_frame_word(enc_frame_word), 142 | .o_data(o_xver_tx_data) 143 | ); 144 | 145 | assign o_xver_tx_gearbox_sequence = '0; 146 | assign o_xver_tx_header = '0; 147 | 148 | end 149 | endgenerate 150 | 151 | assign o_xgmii_tx_ready = !tx_gearbox_pause; 152 | 153 | /// ************* RX DATAPATH ************* // 154 | wire [DATA_WIDTH-1:0] rx_gearbox_data_out, rx_descrambled_data; 155 | wire [1:0] rx_header; 156 | wire rx_gearbox_slip; 157 | 158 | generate 159 | if (EXTERNAL_GEARBOX != 0) begin: l_rx_ext_gearbox 160 | 161 | assign o_xver_rx_gearbox_slip = rx_gearbox_slip; 162 | assign rx_gearbox_data_out = i_xver_rx_data; 163 | assign rx_header = i_xver_rx_header; 164 | assign rx_data_valid = i_xver_rx_data_valid; 165 | assign rx_header_valid = i_xver_rx_header_valid; 166 | 167 | end else begin: l_rx_int_gearbox 168 | 169 | rx_gearbox #(.REGISTER_OUTPUT(1)) 170 | u_rx_gearbox ( 171 | .i_clk(i_xver_rx_clk), 172 | .i_reset(i_tx_reset), 173 | .i_data(i_xver_rx_data), 174 | .i_slip(rx_gearbox_slip), 175 | .o_data(rx_gearbox_data_out), 176 | .o_header(rx_header), 177 | .o_data_valid(rx_data_valid), 178 | .o_header_valid(rx_header_valid) 179 | ); 180 | 181 | assign o_xver_rx_gearbox_slip = 1'b0; 182 | 183 | end 184 | endgenerate 185 | 186 | // Lock state machine 187 | lock_state u_lock_state( 188 | .i_clk(i_xver_rx_clk), 189 | .i_reset(i_rx_reset), 190 | .i_header(rx_header), 191 | .i_valid(rx_header_valid), 192 | .o_slip(rx_gearbox_slip) 193 | ); 194 | 195 | // Descrambler 196 | generate 197 | if (SCRAMBLER_BYPASS) begin: l_rx_scrambler_bypass 198 | 199 | assign rx_descrambled_data = rx_gearbox_data_out; 200 | 201 | end else begin: l_rx_scrambler 202 | 203 | scrambler #( 204 | .DESCRAMBLE(1) 205 | ) u_descrambler( 206 | .i_clk(i_xver_rx_clk), 207 | .i_reset(i_rx_reset), 208 | .i_init_done(!i_rx_reset), 209 | .i_pause(!rx_data_valid), 210 | .i_data(rx_gearbox_data_out), 211 | .o_data(rx_descrambled_data) 212 | ); 213 | 214 | end 215 | endgenerate 216 | 217 | // Decoder 218 | decoder #( 219 | ) u_decoder( 220 | .i_reset(i_rx_reset), 221 | .i_rxc(i_xver_rx_clk), 222 | .i_rxd(rx_descrambled_data), 223 | .i_rx_header(rx_header), 224 | .i_rx_data_valid(rx_data_valid), 225 | .i_rx_header_valid(rx_header_valid), 226 | .o_rxd(rx_decoded_data), 227 | .o_rxctl(rx_decoded_ctl) 228 | ); 229 | 230 | // Calulate the term location here to help with timing in the MAC 231 | wire [DATA_NBYTES-1:0] early_term_loc; 232 | generate for (genvar gi = 0; gi < DATA_NBYTES; gi++) begin: l_early_term_loc 233 | assign early_term_loc[gi] = rx_data_valid && rx_decoded_data[gi*8 +: 8] == RS_TERM && rx_decoded_ctl[gi]; 234 | end endgenerate 235 | 236 | always_ff @(posedge i_xver_rx_clk) 237 | if (i_rx_reset) begin 238 | o_xgmii_rx_data <= '0; 239 | o_xgmii_rx_ctl <= '0; 240 | o_xgmii_rx_valid <= '0; 241 | o_term_loc <= '0; 242 | end else begin 243 | o_xgmii_rx_data <= rx_decoded_data; 244 | o_xgmii_rx_ctl <= rx_decoded_ctl; 245 | o_xgmii_rx_valid <= rx_data_valid; 246 | o_term_loc <= early_term_loc; 247 | end 248 | 249 | endmodule 250 | -------------------------------------------------------------------------------- /src/hdl/mac/rx_mac.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: rx_mac 25 | * 26 | * Description: 10G Ethernet MAC, receive channel. XGMII and valid from PCS, 27 | * AXIS user out. Note terminator location (i_term_loc) input from PCS, this 28 | * is to ease timing pressure here. 29 | * 30 | * Note: 31 | * A non-standard implementation of TUSER is used for the AXIS master. 32 | * If TUSER is not asserted with TLAST, this indicates a packet was recieved with 33 | * incorrect CRC. However, TLAST/TUSER can be asserted when all TKEEP == 0, this is to 34 | * provide data for processing ASAP. This may cause TUSER to be dropped if routing 35 | * the AXIS interface through interconnect. 36 | */ 37 | 38 | `timescale 1ns/1ps 39 | `default_nettype none 40 | `include "code_defs_pkg.svh" 41 | 42 | module rx_mac #( 43 | localparam int DATA_WIDTH = 32, 44 | localparam int DATA_NBYTES = DATA_WIDTH / 8 45 | ) ( 46 | input wire i_reset, 47 | input wire i_clk, 48 | 49 | // Rx PHY 50 | input wire [DATA_WIDTH-1:0] i_xgmii_rxd, 51 | input wire [DATA_NBYTES-1:0] i_xgmii_rxc, 52 | input wire i_phy_rx_valid, 53 | input wire [DATA_NBYTES-1:0] i_term_loc, 54 | 55 | /* svlint off prefix_input */ 56 | /* svlint off prefix_output */ 57 | // Rx AXIS 58 | output logic [DATA_WIDTH-1:0] m00_axis_tdata, 59 | output logic [DATA_NBYTES-1:0] m00_axis_tkeep, 60 | output logic m00_axis_tvalid, 61 | output logic m00_axis_tlast, 62 | output logic m00_axis_tuser 63 | /* svlint on prefix_input */ 64 | /* svlint on prefix_output */ 65 | ); 66 | import code_defs_pkg::*; 67 | 68 | /********* Declarations ********/ 69 | // Rx states 70 | typedef enum logic[1:0] {IDLE, PREAMBLE, DATA, TERM} rx_state_t; 71 | rx_state_t rx_state, rx_next_state; 72 | 73 | // Start detect 74 | wire sfd_found; 75 | logic [DATA_NBYTES-1:0] start_keep; 76 | logic sfd_found_del; 77 | wire start_valid; 78 | 79 | // Term detect 80 | wire term_found; 81 | wire [DATA_NBYTES-1:0] term_keep; 82 | 83 | wire ctl_found; 84 | wire data_invalid; 85 | 86 | // CRC 87 | wire rx_crc_reset; 88 | logic [DATA_WIDTH-1:0] rx_crc_input_del; 89 | logic [31:0] rx_calc_crc; 90 | logic [31:0] rx_captured_crc; 91 | logic [DATA_NBYTES-1:0] crc_input_valid; 92 | logic [DATA_NBYTES-1:0] rx_crc_input_valid_del; 93 | 94 | /********* Implementation ********/ 95 | 96 | // State 97 | always_ff @(posedge i_clk) 98 | if (i_reset) begin 99 | rx_state <= IDLE; 100 | end else begin 101 | rx_state <= rx_next_state; 102 | end 103 | 104 | always @(*) begin 105 | 106 | m00_axis_tdata = i_xgmii_rxd; 107 | 108 | case (rx_state) 109 | IDLE: begin 110 | rx_next_state = bit '(sfd_found) ? DATA : IDLE; 111 | 112 | m00_axis_tvalid = '0; 113 | m00_axis_tlast = '0; 114 | m00_axis_tkeep = '0; 115 | m00_axis_tuser = '0; 116 | end 117 | DATA: begin 118 | // Abandon packet on any ctl char 119 | if (i_phy_rx_valid && (term_found || ctl_found || data_invalid)) begin 120 | rx_next_state = IDLE; 121 | end else begin 122 | rx_next_state = DATA; 123 | end 124 | 125 | m00_axis_tvalid = i_phy_rx_valid && !data_invalid && (!sfd_found_del || (sfd_found_del && start_valid)); 126 | m00_axis_tlast = rx_next_state == IDLE; 127 | m00_axis_tkeep = sfd_found_del ? start_keep : 128 | term_found ? term_keep : 129 | '1; 130 | m00_axis_tuser = term_found && (rx_calc_crc == rx_captured_crc); 131 | end 132 | default: begin 133 | rx_next_state = IDLE; 134 | m00_axis_tvalid = '0; 135 | m00_axis_tlast = '0; 136 | m00_axis_tkeep = '0; 137 | m00_axis_tuser = '0; 138 | end 139 | 140 | endcase 141 | 142 | end 143 | 144 | // Any control character found 145 | assign ctl_found = |i_xgmii_rxc; 146 | 147 | // Detect xgmii error 148 | assign data_invalid = (i_xgmii_rxc[3] && i_xgmii_rxd[31:24] == RS_ERROR) || 149 | (i_xgmii_rxc[2] && i_xgmii_rxd[23:16] == RS_ERROR) || 150 | (i_xgmii_rxc[1] && i_xgmii_rxd[15: 8] == RS_ERROR) || 151 | (i_xgmii_rxc[0] && i_xgmii_rxd[ 7: 0] == RS_ERROR); 152 | 153 | // Start detect 154 | assign sfd_found = i_phy_rx_valid && (i_xgmii_rxd == {{3{8'h55}}, RS_START}) && (i_xgmii_rxc == 4'b1); 155 | 156 | always_ff @(posedge i_clk) // Record sfd loc for next cycle output 157 | if (i_reset) begin 158 | sfd_found_del <= '0; 159 | end else begin 160 | sfd_found_del <= (i_phy_rx_valid) ? sfd_found : sfd_found_del; 161 | end 162 | 163 | // Term detect - now done in PCS for better timing 164 | // genvar gi; 165 | // generate for (gi = 0; gi < DATA_NBYTES; gi++) begin 166 | // assign i_term_loc[gi] = i_phy_rx_valid && i_xgmii_rxd[gi*8 +: 8] == RS_TERM && i_xgmii_rxc[gi]; 167 | // end endgenerate 168 | assign term_found = |i_term_loc; 169 | 170 | 171 | // Keep 172 | assign start_keep = 4'b0000; 173 | assign start_valid = 1'b0; // If data width is 32, start is all preamble 174 | 175 | generate for (genvar gi = 0; gi < DATA_NBYTES; gi++) begin: l_term_keep 176 | assign term_keep[gi] = (1 << gi) < i_term_loc ? 1'b1 : 1'b0; 177 | end endgenerate 178 | 179 | // CRC 180 | always_ff @(posedge i_clk) 181 | if (i_reset) begin 182 | rx_crc_input_del <= '0; 183 | rx_crc_input_valid_del <= '0; 184 | end else begin 185 | rx_crc_input_del <= i_phy_rx_valid ? m00_axis_tdata : rx_crc_input_del; 186 | rx_crc_input_valid_del <= i_phy_rx_valid ? m00_axis_tkeep : rx_crc_input_valid_del; 187 | end 188 | 189 | always @(*) begin 190 | if (!i_phy_rx_valid) begin 191 | crc_input_valid = {DATA_NBYTES{1'b0}}; 192 | end else if (!term_found) begin 193 | crc_input_valid = rx_crc_input_valid_del; 194 | end else begin 195 | // We need to stop the CRC itself from being input 196 | case (i_term_loc) 197 | 1: crc_input_valid = 4'b0000; 198 | 2: crc_input_valid = 4'b0001; 199 | 4: crc_input_valid = 4'b0011; 200 | 8: crc_input_valid = 4'b0111; 201 | default: crc_input_valid = 4'b1111; 202 | endcase 203 | end 204 | 205 | case (i_term_loc) 206 | 1: rx_captured_crc = rx_crc_input_del; 207 | 2: rx_captured_crc = {i_xgmii_rxd[0+:8], rx_crc_input_del[8+:24]}; 208 | 4: rx_captured_crc = {i_xgmii_rxd[0+:16], rx_crc_input_del[16+:16]}; 209 | 8: rx_captured_crc = {i_xgmii_rxd[0+:24], rx_crc_input_del[24+:8]}; 210 | default: rx_captured_crc = i_xgmii_rxd; 211 | endcase 212 | end 213 | 214 | assign rx_crc_reset = rx_state == IDLE; 215 | 216 | slicing_crc #( 217 | .SLICE_LENGTH(DATA_NBYTES), 218 | .INITIAL_CRC(32'hFFFFFFFF), 219 | .INVERT_OUTPUT(1), 220 | .REGISTER_OUTPUT(0) 221 | ) u_rx_crc ( 222 | .i_clk(i_clk), 223 | .i_reset(rx_crc_reset), 224 | .i_data(rx_crc_input_del), 225 | .i_valid(crc_input_valid), 226 | .o_crc(rx_calc_crc) 227 | ); 228 | 229 | endmodule 230 | -------------------------------------------------------------------------------- /src/hdl/pcs/decoder.sv: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Tom Chisholm 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | /* 24 | * Module: decoder 25 | * 26 | * Description: 64b66b to XGMII RS decoder. 32-bit data I/O. 27 | * 28 | */ 29 | 30 | `timescale 1ns/1ps 31 | `include "code_defs_pkg.svh" 32 | `default_nettype none 33 | 34 | module decoder #( 35 | localparam int DATA_WIDTH = 32, 36 | localparam int DATA_NBYTES = DATA_WIDTH / 8 37 | ) ( 38 | 39 | input wire i_reset, 40 | input wire i_rxc, 41 | input wire [DATA_WIDTH-1:0] i_rxd, 42 | input wire [1:0] i_rx_header, 43 | input wire i_rx_data_valid, 44 | input wire i_rx_header_valid, 45 | 46 | //Rx interface out 47 | output wire [DATA_WIDTH-1:0] o_rxd, 48 | output wire [DATA_NBYTES-1:0] o_rxctl 49 | ); 50 | 51 | import code_defs_pkg::*; 52 | 53 | typedef struct packed { 54 | logic [63:0] odata; 55 | logic [7:0] octl; 56 | logic frame_valid; 57 | } xgmii_data_ctl_t; 58 | 59 | /**** 32-bit input to 64 bit internal ****/ 60 | // Input declarations 61 | logic [DATA_WIDTH-1:0] delayed_i_rxd; 62 | wire [63:0] internal_irxd; 63 | 64 | // Output declarations 65 | xgmii_data_ctl_t decoded_frame; 66 | wire output_decode_frame; 67 | 68 | // verilator lint_off UNUSED 69 | // While lower word unused, useful for debugging 70 | logic [63:0] internal_orxd; 71 | logic [7:0] internal_orxctl; 72 | wire frame_valid; 73 | // verilator lint_on UNUSED 74 | 75 | // I/O delays for width conversion 76 | always_ff @(posedge i_rxc) begin 77 | if (i_reset) begin 78 | delayed_i_rxd <= '0; 79 | internal_orxd <= '0; 80 | internal_orxctl <= '0; 81 | end else begin 82 | if (i_rx_data_valid) begin 83 | delayed_i_rxd <= i_rxd; 84 | if (output_decode_frame) begin // Header is invalid on second part of frame 85 | internal_orxd <= decoded_frame.odata; 86 | internal_orxctl <= decoded_frame.octl; 87 | end 88 | end 89 | end 90 | end 91 | 92 | // Internal assignments 93 | assign internal_irxd = {i_rxd, delayed_i_rxd}; 94 | assign output_decode_frame = !i_rx_header_valid && i_rx_data_valid; // When header invalid - we have second word 95 | assign frame_valid = decoded_frame.frame_valid; 96 | 97 | // Output assignments 98 | assign o_rxd = !output_decode_frame ? internal_orxd[32 +: 32] : decoded_frame.odata[0 +: 32]; 99 | assign o_rxctl = !output_decode_frame ? internal_orxctl[4 +: 4] : decoded_frame.octl[0 +: 4]; 100 | 101 | /**** Decoder functions ****/ 102 | /* 103 | * Function `control_code_to_rs_lane` 104 | * 105 | * A 64b66b control frame can contain up to 8 7-bit control codes and the 8-bit block type. 106 | * This function converts the 64b66b control codes into XGMII RS Codes and places into XGMII 107 | * frame. The location of the 64b66b control codes are determined by the bits set in the 'lanes' 108 | * input. 109 | */ 110 | function automatic logic [63:0] control_code_to_rs_lane(input logic [63:0] idata, input bit [7:0] lanes); 111 | control_code_to_rs_lane = {8{RS_ERROR}}; 112 | for (int i = 0; i < 8; i++) begin 113 | if (lanes[7-i] == 1) begin 114 | control_code_to_rs_lane[8*i +: 8] = control_to_rs_code(idata[8 + (7*i) +: 7]); 115 | end 116 | end 117 | endfunction 118 | 119 | /* 120 | * Function `decode_frame` 121 | * 122 | * Decodes 64b66b 64-bit input data and 2-bit header into XGMII 64-bit data and 8-bit control 123 | */ 124 | function automatic xgmii_data_ctl_t decode_frame(input logic [63:0] idata, input logic [1:0] iheader); 125 | 126 | decode_frame.odata = '0; 127 | decode_frame.octl = '0; 128 | decode_frame.frame_valid = 1; 129 | 130 | if (iheader == SYNC_DATA) begin 131 | decode_frame.odata = idata; 132 | decode_frame.octl = '0; 133 | end else begin 134 | case (idata[7:0]) 135 | 136 | BT_IDLE: begin 137 | decode_frame.odata = control_code_to_rs_lane(idata, 8'hFF); 138 | decode_frame.octl = '1; 139 | end 140 | BT_O4: begin 141 | decode_frame.odata = control_code_to_rs_lane(idata, 8'hF0); 142 | decode_frame.odata[63:40] = idata[63:40]; 143 | decode_frame.odata[39:32] = cc_to_rs_ocode(idata[39:36]); 144 | decode_frame.octl = 8'h1F; 145 | end 146 | BT_S4: begin 147 | decode_frame.odata = control_code_to_rs_lane(idata, 8'hF0); 148 | decode_frame.odata[63:40] = idata[63:40]; 149 | decode_frame.odata[39:32] = RS_START; 150 | decode_frame.octl = 8'h1F; 151 | end 152 | BT_O0S4: begin 153 | decode_frame.odata[63:40] = idata[63:40]; 154 | decode_frame.odata[39:32] = RS_START; 155 | decode_frame.odata[31: 8] = idata[31: 8]; 156 | decode_frame.odata[ 7: 0] = cc_to_rs_ocode(idata[35:32]); 157 | decode_frame.octl = 8'h11; 158 | end 159 | BT_O0O4: begin 160 | decode_frame.odata[63:40] = idata[63:40]; 161 | decode_frame.odata[39:32] = cc_to_rs_ocode(idata[35:32]); 162 | decode_frame.odata[31: 8] = idata[31: 8]; 163 | decode_frame.odata[ 7: 0] = cc_to_rs_ocode(idata[39:36]); 164 | decode_frame.octl = 8'h11; 165 | end 166 | BT_S0: begin 167 | decode_frame.odata[63: 8] = idata[63: 8]; 168 | decode_frame.odata[ 7: 0] = RS_START; 169 | decode_frame.octl = 8'h01; 170 | end 171 | BT_O0: begin 172 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h0F); 173 | decode_frame.odata[31: 8] = idata[31: 8]; 174 | decode_frame.odata[ 7: 0] = cc_to_rs_ocode(idata[35:32]); 175 | decode_frame.octl = 8'hF1; 176 | end 177 | BT_T0: begin 178 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h7F); 179 | decode_frame.odata[ 7: 0] = RS_TERM; 180 | decode_frame.octl = 8'hFF; 181 | end 182 | BT_T1: begin 183 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h3F); 184 | decode_frame.odata[ 7: 0] = idata[15: 8]; 185 | decode_frame.odata[15: 8] = RS_TERM; 186 | decode_frame.octl = 8'hFE; 187 | end 188 | BT_T2: begin 189 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h1F); 190 | decode_frame.odata[15: 0] = idata[23: 8]; 191 | decode_frame.odata[23:16] = RS_TERM; 192 | decode_frame.octl = 8'hFC; 193 | end 194 | BT_T3: begin 195 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h0F); 196 | decode_frame.odata[23: 0] = idata[31: 8]; 197 | decode_frame.odata[31:24] = RS_TERM; 198 | decode_frame.octl = 8'hF8; 199 | end 200 | BT_T4: begin 201 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h07); 202 | decode_frame.odata[31: 0] = idata[39: 8]; 203 | decode_frame.odata[39:32] = RS_TERM; 204 | decode_frame.octl = 8'hF0; 205 | end 206 | BT_T5: begin 207 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h03); 208 | decode_frame.odata[39: 0] = idata[47: 8]; 209 | decode_frame.odata[47:40] = RS_TERM; 210 | decode_frame.octl = 8'hE0; 211 | end 212 | BT_T6: begin 213 | decode_frame.odata = control_code_to_rs_lane(idata, 8'h01); 214 | decode_frame.odata[47: 0] = idata[55: 8]; 215 | decode_frame.odata[55:48] = RS_TERM; 216 | decode_frame.octl = 8'hC0; 217 | end 218 | BT_T7: begin 219 | decode_frame.odata[55: 0] = idata[63: 8]; 220 | decode_frame.odata[63:56] = RS_TERM; 221 | decode_frame.octl = 8'h80; 222 | end 223 | default: begin 224 | decode_frame.octl = 8'hFF; 225 | decode_frame.odata[63:0] = {8{RS_ERROR}}; 226 | decode_frame.frame_valid = 0; 227 | end 228 | endcase 229 | end 230 | 231 | endfunction 232 | 233 | assign decoded_frame = decode_frame(internal_irxd, i_rx_header); 234 | 235 | endmodule 236 | -------------------------------------------------------------------------------- /src/tb/mac_pcs/mac_pcs_bfm.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2023 Tom Chisholm 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | """ 24 | mac_pcs_bfm.py 25 | 26 | Pyuvm Bus Fuctional Model (BFM) for mac_pcs module. 27 | 28 | """ 29 | 30 | import logging 31 | import debugpy 32 | 33 | import cocotb 34 | from cocotb.triggers import RisingEdge, FallingEdge, Edge 35 | from cocotb.queue import QueueEmpty, Queue 36 | from cocotb.clock import Clock 37 | 38 | from cocotbext.axi import (AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor) 39 | 40 | from pyuvm import * 41 | 42 | class MacPcsBfm(metaclass=utility_classes.Singleton): 43 | def __init__(self): 44 | self.dut = cocotb.top 45 | self.config = ConfigDB().get(None, "", 'run_config') 46 | self.tx_driver_queue = Queue(maxsize=1) 47 | self.tx_monitor_queue = Queue(maxsize=0) 48 | self.rx_monitor_queue = Queue(maxsize=0) 49 | 50 | self.tx_axis_source = AxiStreamSource(AxiStreamBus.from_prefix(self.dut, "s00_axis"), 51 | self.dut.i_xver_tx_clk, self.dut.i_rx_reset) 52 | 53 | 54 | self.tx_axis_monitor = AxiStreamMonitor(AxiStreamBus.from_prefix(self.dut, "s00_axis"), 55 | self.dut.i_xver_tx_clk, self.dut.i_rx_reset) 56 | 57 | 58 | self.rx_axis_monitor = AxiStreamMonitor(AxiStreamBus.from_prefix(self.dut, "m00_axis"), 59 | self.dut.i_xver_rx_clk, self.dut.i_tx_reset) 60 | 61 | 62 | def set_axis_log(self, enable): 63 | self.tx_axis_source.log.propagate = enable 64 | self.tx_axis_monitor.log.propagate = enable 65 | self.rx_axis_monitor.log.propagate = enable 66 | 67 | async def loopback(self, cycle_delay, bit_delay): 68 | async def capture_outputs(self, q, cycle_delay, bit_delay): 69 | for _ in range(cycle_delay): await q.put([0, 0, 0, 0]) 70 | prev_tx_gearbox_seq = 0 71 | prev_data_bits = 0 72 | 73 | while True: 74 | await RisingEdge(self.dut.i_xver_tx_clk) 75 | 76 | o_xver_tx_data = self.dut.o_xver_tx_data.value 77 | o_xver_tx_header = self.dut.o_xver_tx_header.value 78 | xver_tx_data_valid = self.dut.o_xver_tx_gearbox_sequence.value != self.gearbox_pause_val 79 | 80 | xver_tx_header_valid = self.dut.o_xver_tx_gearbox_sequence.value != prev_tx_gearbox_seq and xver_tx_data_valid 81 | prev_tx_gearbox_seq = self.dut.o_xver_tx_gearbox_sequence.value 82 | 83 | # As we don't fully model external gearbox, only support bit slips for internal 84 | if bit_delay > 0 and self.dut.EXTERNAL_GEARBOX.value == 0: 85 | tx_data_noslip = o_xver_tx_data 86 | o_xver_tx_data = ((o_xver_tx_data << bit_delay) | prev_data_bits) & 0xFFFFFFFF 87 | prev_data_bits = (tx_data_noslip >> (32 - bit_delay)) & ((1 << bit_delay) - 1) 88 | 89 | await q.put([o_xver_tx_data, o_xver_tx_header, xver_tx_data_valid, xver_tx_header_valid]) 90 | 91 | async def apply_input(self, q): 92 | while True: 93 | await RisingEdge(self.dut.i_xver_rx_clk) 94 | [o_xver_tx_data, o_xver_tx_header, xver_tx_data_valid, xver_tx_header_valid] = await q.get() 95 | 96 | self.dut.i_xver_rx_data.value = o_xver_tx_data 97 | if self.dut.EXTERNAL_GEARBOX.value: 98 | self.dut.i_xver_rx_header.value = o_xver_tx_header 99 | self.dut.i_xver_rx_data_valid.value = xver_tx_data_valid 100 | self.dut.i_xver_rx_header_valid.value = xver_tx_header_valid 101 | else: 102 | self.dut.i_xver_rx_header.value = 0 103 | self.dut.i_xver_rx_data_valid.value = 0 104 | self.dut.i_xver_rx_header_valid.value = 0 105 | 106 | q = Queue() 107 | cocotb.start_soon(capture_outputs(self, q, cycle_delay, bit_delay)) 108 | cocotb.start_soon(apply_input(self, q)) 109 | 110 | async def send_tx_packet(self, packet): 111 | await self.tx_driver_queue.put(packet) 112 | 113 | async def reset(self): 114 | self.dut.i_tx_reset.value = 1 115 | self.dut.i_rx_reset.value = 1 116 | self.dut.i_xver_rx_clk.value = 0 117 | self.dut.i_xver_rx_data.value = 0 118 | self.dut.i_xver_tx_clk.value = 0 119 | await RisingEdge(self.dut.i_xver_rx_clk) 120 | await RisingEdge(self.dut.i_xver_tx_clk) 121 | await FallingEdge(self.dut.i_xver_rx_clk) 122 | await FallingEdge(self.dut.i_xver_tx_clk) 123 | self.dut.i_tx_reset.value = 0 124 | self.dut.i_rx_reset.value = 0 125 | await RisingEdge(self.dut.i_xver_rx_clk) 126 | await RisingEdge(self.dut.i_xver_tx_clk) 127 | 128 | async def pause(self, cycles): 129 | for _ in range(cycles): 130 | await RisingEdge(self.dut.i_xver_tx_clk) 131 | 132 | async def driver_bfm(self): 133 | while True: 134 | packet = await self.tx_driver_queue.get() 135 | await self.tx_axis_source.send(packet.tobytes()) 136 | await self.tx_axis_source.wait() 137 | 138 | 139 | async def tx_monitor_bfm(self): 140 | while True: 141 | packet = await self.tx_axis_monitor.recv() 142 | self.tx_monitor_queue.put_nowait(packet) 143 | 144 | async def rx_monitor_bfm(self): 145 | while True: 146 | packet = await self.rx_axis_monitor.recv(compact=False) 147 | packet = self.compact_axis_no_tuser(packet) 148 | self.rx_monitor_queue.put_nowait(packet) 149 | 150 | async def get_tx_frame(self): 151 | return await self.tx_monitor_queue.get() 152 | 153 | async def get_rx_frame(self): 154 | return await self.rx_monitor_queue.get() 155 | 156 | 157 | async def start_bfm(self): 158 | self.data_width = len(self.dut.xgmii_tx_data) 159 | self.data_nbytes = self.data_width // 8 160 | self.gearbox_pause_val = 32 161 | self.clk_period = round(1 / (10.3125 / self.data_width), 2) # ps precision 162 | 163 | cocotb.start_soon(Clock(self.dut.i_xver_tx_clk, self.clk_period, units="ns").start()) 164 | cocotb.start_soon(Clock(self.dut.i_xver_rx_clk, self.clk_period, units="ns").start()) 165 | 166 | 167 | await self.reset() 168 | cocotb.start_soon(self.loopback(self.config['loopback_cycle_slip'], self.config['loopback_bit_slip'])) 169 | 170 | # manual slip for idles - debugging w/out scrambler 171 | if self.config['dbg_manual_gearbox_slip']: 172 | print('Performing manual gearbox sync') 173 | conseq = 0 174 | for i in range(10000): 175 | 176 | if self.dut.xgmii_rx_data.value == int('0x07070707', 16) and \ 177 | self.dut.u_pcs.l_rx_int_gearbox.u_rx_gearbox.o_header.value != 0: 178 | conseq += 1 179 | else: 180 | conseq = 0 181 | 182 | if conseq > 40: 183 | print('aligned') 184 | break 185 | 186 | if i % 50 == 0: 187 | self.dut.u_pcs.rx_gearbox_slip.value = 1 188 | else: 189 | self.dut.u_pcs.rx_gearbox_slip.value = 0 190 | await RisingEdge(self.dut.i_xver_rx_clk) 191 | 192 | 193 | cocotb.start_soon(self.driver_bfm()) 194 | cocotb.start_soon(self.tx_monitor_bfm()) 195 | cocotb.start_soon(self.rx_monitor_bfm()) 196 | 197 | 198 | # A copy of AxiStreamFrame::compact but does not remvove tuser when tkeep = 0 199 | @staticmethod 200 | def compact_axis_no_tuser(frame): 201 | if len(frame.tkeep): 202 | # remove tkeep=0 bytes 203 | for k in range(len(frame.tdata)-1, -1, -1): 204 | if not frame.tkeep[k]: 205 | if k < len(frame.tdata): 206 | del frame.tdata[k] 207 | if k < len(frame.tkeep): 208 | del frame.tkeep[k] 209 | if k < len(frame.tid): 210 | del frame.tid[k] 211 | if k < len(frame.tdest): 212 | del frame.tdest[k] 213 | 214 | # remove tkeep 215 | frame.tkeep = None 216 | 217 | # clean up other sideband signals 218 | # either remove or consolidate if values are identical 219 | if len(frame.tid) == 0: 220 | frame.tid = None 221 | elif all(frame.tid[0] == i for i in frame.tid): 222 | frame.tid = frame.tid[0] 223 | 224 | if len(frame.tdest) == 0: 225 | frame.tdest = None 226 | elif all(frame.tdest[0] == i for i in frame.tdest): 227 | frame.tdest = frame.tdest[0] 228 | 229 | if len(frame.tuser) == 0: 230 | frame.tuser = None 231 | elif all(frame.tuser[0] == i for i in frame.tuser): 232 | frame.tuser = frame.tuser[0] 233 | 234 | return frame 235 | 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /src/tb/mac/test_mac.py: -------------------------------------------------------------------------------- 1 | from asyncore import loop 2 | import asyncio 3 | import enum 4 | import cocotb 5 | from cocotb.triggers import Timer, RisingEdge, FallingEdge, Edge, NextTimeStep 6 | from cocotb.clock import Clock 7 | from cocotb.result import TestFailure 8 | 9 | import debugpy 10 | 11 | 12 | class MAC_TB: 13 | def __init__(self, dut, loopback=False): 14 | self.dut = dut 15 | 16 | self.data_width = len(self.dut.o_xgmii_tx_data) 17 | self.data_nbytes = self.data_width // 8 18 | self.clk_period = round(1 / (10.3125 / self.data_width), 2) # ps precision 19 | 20 | cocotb.start_soon(Clock(dut.i_tx_clk, self.clk_period, units="ns").start()) 21 | cocotb.start_soon(Clock(dut.i_rx_clk, self.clk_period, units="ns").start()) 22 | 23 | if loopback: 24 | cocotb.start_soon(self.loopback('o_xgmii_tx_data', 'i_xgmii_rx_data')) 25 | cocotb.start_soon(self.loopback('o_xgmii_tx_ctl', 'i_xgmii_rx_ctl')) 26 | 27 | 28 | 29 | self.dut.s00_axis_tvalid.value = 0 30 | self.dut.s00_axis_tdata.value = 0 31 | self.dut.s00_axis_tkeep.value = 0 32 | self.dut.s00_axis_tlast.value = 0 33 | 34 | 35 | async def change_reset(self, val): 36 | self.dut.i_tx_reset.value = val 37 | self.dut.i_rx_reset.value = val 38 | 39 | await RisingEdge(self.dut.i_tx_clk) 40 | await RisingEdge(self.dut.i_rx_clk) 41 | 42 | 43 | async def reset(self): 44 | await self.change_reset(0) 45 | await self.change_reset(1) 46 | self.dut.i_tx_reset.value = 0 47 | self.dut.i_rx_reset.value = 0 48 | 49 | async def loopback(self, output, input): 50 | while True: 51 | await Edge(getattr(self.dut, output)) 52 | getattr(self.dut, input).value = getattr(self.dut, output).value 53 | 54 | 55 | 56 | @cocotb.test() 57 | async def tx_test(dut): 58 | tb = MAC_TB(dut, True) 59 | 60 | dut.i_phy_tx_ready.value = 1 61 | 62 | await tb.reset() 63 | 64 | dut.s00_axis_tkeep.value = int(2**tb.data_nbytes - 1) 65 | await RisingEdge(dut.i_tx_clk) 66 | 67 | test_vectors = [ 68 | [ 69 | int("0x00", 16), int("0x10", 16), int("0xA4", 16), int("0x7B", 16), int("0xEA", 16), 70 | ], 71 | [ 72 | int("0x00", 16), int("0x10", 16), int("0xA4", 16), int("0x7B", 16), int("0xEA", 16), int("0x80", 16), 73 | int("0x00", 16), int("0x12", 16), int("0x34", 16), int("0x56", 16), int("0x78", 16), int("0x90", 16), 74 | int("0x08", 16), int("0x00", 16), int("0x45", 16), int("0x00", 16), int("0x00", 16), int("0x2E", 16), 75 | int("0xB3", 16), int("0xFE", 16), int("0x00", 16), int("0x00", 16), int("0x80", 16), int("0x11", 16), 76 | int("0x05", 16), int("0x40", 16), int("0xC0", 16), int("0xA8", 16), int("0x00", 16), int("0x2C", 16), 77 | int("0xC0", 16), int("0xA8", 16), int("0x00", 16), int("0x04", 16), int("0x04", 16), int("0x00", 16), 78 | int("0x04", 16), int("0x00", 16), int("0x00", 16), int("0x1A", 16), int("0x2D", 16), int("0xE8", 16), 79 | int("0x00", 16), int("0x01", 16), int("0x02", 16), int("0x03", 16), int("0x04", 16), int("0x05", 16), 80 | int("0x06", 16), int("0x07", 16), int("0x08", 16), int("0x09", 16), int("0x0A", 16), int("0x0B", 16), 81 | int("0x0C", 16), int("0x0D", 16), int("0x0E", 16), int("0x0F", 16), int("0x10", 16), int("0x11", 16) 82 | ], 83 | [ int("0x08", 16), int("0x00", 16), int("0x20", 16), int("0x77", 16), int("0x05", 16), int("0x38", 16), int("0x0e", 16), int("0x8b", 16), 84 | int("0x00", 16), int("0x00", 16), int("0x00", 16), int("0x00", 16), int("0x08", 16), int("0x00", 16), int("0x45", 16), int("0x00", 16), 85 | int("0x00", 16), int("0x28", 16), int("0x1c", 16), int("0x66", 16), int("0x00", 16), int("0x00", 16), int("0x1b", 16), int("0x06", 16), 86 | int("0x9e", 16), int("0xd7", 16), int("0x00", 16), int("0x00", 16), int("0x59", 16), int("0x4d", 16), int("0x00", 16), int("0x00", 16), 87 | int("0x68", 16), int("0xd1", 16), int("0x39", 16), int("0x28", 16), int("0x4a", 16), int("0xeb", 16), int("0x00", 16), int("0x00", 16), 88 | int("0x30", 16), int("0x77", 16), int("0x00", 16), int("0x00", 16), int("0x7a", 16), int("0x0c", 16), int("0x50", 16), int("0x12", 16), 89 | int("0x1e", 16), int("0xd2", 16), int("0x62", 16), int("0x84", 16), int("0x00", 16), int("0x00", 16), int("0x00", 16), int("0x00", 16), 90 | int("0x00", 16), int("0x00", 16), int("0x00", 16), int("0x00", 16) 91 | ], 92 | [ int("0x08", 16), int("0xdd", 16), int("0x20", 16), int("0x77", 16), int("0x05", 16), int("0x38", 16), int("0x0e", 16), int("0x8b", 16), 93 | int("0xd3", 16), int("0xd4", 16), int("0xd5", 16), int("0xd6", 16), int("0x08", 16), int("0xdd", 16), int("0x45", 16), int("0xdd", 16), 94 | int("0xdd", 16), int("0x28", 16), int("0x1c", 16), int("0x66", 16), int("0xd8", 16), int("0xda", 16), int("0x1b", 16), int("0x06", 16), 95 | int("0x9e", 16), int("0xd7", 16), int("0x08", 16), int("0xdd", 16), int("0x20", 16), int("0x77", 16), int("0x05", 16), int("0x38", 16), int("0x0e", 16), int("0x8b", 16), 96 | int("0xd3", 16), int("0xd4", 16), int("0xd5", 16), int("0xd6", 16), int("0x08", 16), int("0xdd", 16), int("0x45", 16), int("0xdd", 16), 97 | int("0xdd", 16), int("0x28", 16), int("0x1c", 16), int("0x66", 16), int("0xd8", 16), int("0xda", 16), int("0x1b", 16), int("0x06", 16), 98 | int("0x9e", 16), int("0xd7", 16), int("0xd8", 16) 99 | ] 100 | ] 101 | 102 | # concatonate the test vector to match the input width 103 | # https://stackoverflow.com/questions/434287/how-to-iterate-over-a-list-in-chunks 104 | def chunker(seq, size): 105 | return (seq[pos:pos + size] for pos in range(0, len(seq), size)) 106 | 107 | # debugpy.listen(5678) 108 | # debugpy.wait_for_client() 109 | # debugpy.breakpoint() 110 | 111 | async def print_out(): 112 | while(True): 113 | await RisingEdge(dut.i_tx_clk) 114 | print(f'{int(tb.dut.o_xgmii_tx_data.value):08x}') 115 | 116 | cocotb.start_soon(print_out()) 117 | 118 | 119 | for tv in test_vectors: 120 | 121 | timeout = 0 122 | 123 | tvc = list(chunker(tv, tb.data_nbytes)) 124 | 125 | for i, ivalues in enumerate(tvc): 126 | 127 | while tb.dut.s00_axis_tready.value == 0: 128 | tb.dut.s00_axis_tvalid.value = 0 129 | timeout += 1 130 | await RisingEdge(dut.i_tx_clk) 131 | assert timeout < 40, 'Waiting for tx ready timed out' 132 | 133 | ivalue = 0 134 | ivalid = 0 135 | for k, v in enumerate(ivalues): 136 | ivalue = ivalue | (v << (k * 8)) 137 | ivalid = ivalid | (1 << k) 138 | 139 | tb.dut.s00_axis_tdata.value = ivalue 140 | tb.dut.s00_axis_tkeep.value = ivalid 141 | tb.dut.s00_axis_tvalid.value = 1 142 | tb.dut.s00_axis_tlast.value = int(i == len(tvc) - 1) 143 | 144 | await RisingEdge(dut.i_tx_clk) 145 | 146 | 147 | tb.dut.s00_axis_tvalid.value = 0 148 | tb.dut.s00_axis_tdata.value = 0 149 | tb.dut.s00_axis_tlast.value = 0 150 | 151 | await RisingEdge(dut.i_tx_clk) 152 | 153 | for _ in range(20): 154 | await RisingEdge(dut.i_tx_clk) 155 | 156 | @cocotb.test() 157 | async def rx_test(dut): 158 | tb = MAC_TB(dut) 159 | 160 | eg_xgmii_data = {64: [ 161 | (int("0b11111111", 2), int("0x0707070707070707", 16)), 162 | (int("0b00000001", 2), int("0xd5555555555555fb", 16)), 163 | (int("0b00000000", 2), int("0x8b0e380577200008", 16)), 164 | (int("0b00000000", 2), int("0x0045000800000000", 16)), 165 | (int("0b00000000", 2), int("0x061b0000661c2800", 16)), 166 | (int("0b00000000", 2), int("0x00004d590000d79e", 16)), 167 | (int("0b00000000", 2), int("0x0000eb4a2839d168", 16)), 168 | (int("0b00000000", 2), int("0x12500c7a00007730", 16)), 169 | (int("0b00000000", 2), int("0x000000008462d21e", 16)), 170 | (int("0b00000000", 2), int("0x79f7eb9300000000", 16)), 171 | (int("0b11111111", 2), int("0x07070707070707fd", 16)), 172 | (int("0b11111111", 2), int("0x0707070707070707", 16)) 173 | ], 174 | 32: [ 175 | (int("0b1111", 2), int("0x07070707", 16)), 176 | (int("0b1111", 2), int("0x07070707", 16)), 177 | (int("0b0001", 2), int("0x555555fb", 16)), 178 | (int("0b0000", 2), int("0xd5555555", 16)), 179 | (int("0b0000", 2), int("0x77200008", 16)), 180 | (int("0b0000", 2), int("0x8b0e3805", 16)), 181 | (int("0b0000", 2), int("0x00000000", 16)), 182 | (int("0b0000", 2), int("0x00450008", 16)), 183 | (int("0b0000", 2), int("0x661c2800", 16)), 184 | (int("0b0000", 2), int("0x061b0000", 16)), 185 | (int("0b0000", 2), int("0x0000d79e", 16)), 186 | (int("0b0000", 2), int("0x00004d59", 16)), 187 | (int("0b0000", 2), int("0x2839d168", 16)), 188 | (int("0b0000", 2), int("0x0000eb4a", 16)), 189 | (int("0b0000", 2), int("0x00007730", 16)), 190 | (int("0b0000", 2), int("0x12500c7a", 16)), 191 | (int("0b0000", 2), int("0x8462d21e", 16)), 192 | (int("0b0000", 2), int("0x00000000", 16)), 193 | (int("0b0000", 2), int("0x00000000", 16)), 194 | (int("0b0000", 2), int("0x79f7eb93", 16)), 195 | (int("0b1111", 2), int("0x070707fd", 16)), 196 | (int("0b1111", 2), int("0x07070707", 16)), 197 | (int("0b1111", 2), int("0x07070707", 16)), 198 | (int("0b1111", 2), int("0x07070707", 16)) 199 | ]} 200 | 201 | 202 | 203 | tb.dut.i_xgmii_rx_data.value = 0 204 | tb.dut.i_xgmii_rx_ctl.value = 0 205 | tb.dut.i_phy_rx_valid.value = 1 206 | 207 | await tb.reset() 208 | 209 | for (ctl, data) in eg_xgmii_data[tb.data_width]: 210 | tb.dut.i_xgmii_rx_data.value = data 211 | tb.dut.i_xgmii_rx_ctl.value = ctl 212 | 213 | await RisingEdge(dut.i_rx_clk) 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/tb/mac_pcs/test_mac_pcs.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2023 Tom Chisholm 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | """test_mac_pcs.py 24 | 25 | Pyuvm testbench for mac_pcs module. 26 | 27 | This testbench implements a single test with random packets, tested in loopback. 28 | Received packets are checked for correctness and for CRC match flag (TUSER) set. 29 | 30 | """ 31 | 32 | import random 33 | import debugpy 34 | import yaml 35 | import pytest 36 | import numpy as np 37 | import os 38 | import glob 39 | from shutil import copyfile 40 | 41 | from cocotb.triggers import RisingEdge, FallingEdge 42 | from cocotb.queue import QueueEmpty, Queue 43 | from cocotb.clock import Clock 44 | from cocotb_test.simulator import run 45 | 46 | from pyuvm import * 47 | import pyuvm 48 | 49 | from mac_pcs_bfm import MacPcsBfm 50 | 51 | class EthTxSeqItem(uvm_sequence_item): 52 | def __init__(self, name, packet_size): 53 | super().__init__(name) 54 | self.packet_size = packet_size 55 | self.packet = np.random.randint(0, 255, packet_size, dtype=np.uint8) 56 | 57 | def __eq__(self, other): 58 | return self.packet == other.packet 59 | 60 | def __str__(self): 61 | return f'{self.get_name()} : Size = {len(self.packet)}, Data = {self.packet}' 62 | 63 | class EthTxSeqRandom(uvm_sequence): 64 | 65 | def __init__(self, name, length): 66 | super().__init__(name) 67 | self.length = length 68 | 69 | async def body(self): 70 | for i in range(self.length): 71 | seq_item = EthTxSeqItem(f'p{i}', np.random.randint(16, 256, 1)) 72 | await self.start_item(seq_item) 73 | await self.finish_item(seq_item) 74 | 75 | class EthTxAllSeq(uvm_sequence): 76 | async def body(self): 77 | self.config = ConfigDB().get(None, "", 'run_config') 78 | seqr = ConfigDB().get(None, "", "SEQR") 79 | random = EthTxSeqRandom("random", self.config['tx_seq_length']) 80 | await random.start(seqr) 81 | 82 | class TxDriver(uvm_driver): 83 | def build_phase(self): 84 | self.config = ConfigDB().get(self, "", 'run_config') 85 | self.ap = uvm_analysis_port('ap', self) 86 | 87 | def start_of_simulation_phase(self): 88 | self.bfm = MacPcsBfm() 89 | 90 | async def launch_tb(self): 91 | await self.bfm.start_bfm() 92 | await self.bfm.pause(self.config['startup_pause']) 93 | 94 | async def run_phase(self): 95 | await self.launch_tb() 96 | while True: 97 | seq_item = await self.seq_item_port.get_next_item() 98 | await self.bfm.send_tx_packet(seq_item.packet) 99 | self.seq_item_port.item_done() 100 | 101 | class Monitor(uvm_component): 102 | def __init__(self, name, parent, method_name): 103 | super().__init__(name, parent) 104 | self.method_name = method_name 105 | 106 | def build_phase(self): 107 | self.ap = uvm_analysis_port("ap", self) 108 | self.bfm = MacPcsBfm() 109 | self.get_method = getattr(self.bfm, self.method_name) 110 | 111 | async def run_phase(self): 112 | while True: 113 | datum = await self.get_method() 114 | self.logger.debug(f"MONITORED {datum}") 115 | self.ap.write(datum) 116 | 117 | class Scoreboard(uvm_component): 118 | def build_phase(self): 119 | self.tx_frame_fifo = uvm_tlm_analysis_fifo("tx_frame_fifo", self) 120 | self.rx_frame_fifo = uvm_tlm_analysis_fifo("rx_frame_fifo", self) 121 | self.tx_frame_port = uvm_get_port("tx_frame_port", self) 122 | self.rx_frame_port = uvm_get_port("rx_frame_port", self) 123 | self.tx_frame_export = self.tx_frame_fifo.analysis_export 124 | self.rx_frame_export = self.rx_frame_fifo.analysis_export 125 | 126 | def connect_phase(self): 127 | self.tx_frame_port.connect(self.tx_frame_fifo.get_export) 128 | self.rx_frame_port.connect(self.rx_frame_fifo.get_export) 129 | 130 | def check_phase(self): 131 | 132 | had_frame = False 133 | 134 | while self.rx_frame_port.can_get(): 135 | _, rx_frame = self.rx_frame_port.try_get() 136 | tx_success, tx_frame = self.tx_frame_port.try_get() 137 | 138 | had_frame = True 139 | 140 | if not tx_success: 141 | self.logger.critical(f'tx_frame {tx_frame} error') 142 | assert tx_success 143 | else: 144 | 145 | if len(tx_frame.tdata) < 64: 146 | data_eq = rx_frame.tdata[0:len(tx_frame.tdata)] == tx_frame.tdata and \ 147 | all([x == 0 for x in rx_frame.tdata[len(tx_frame.tdata):-4]]) 148 | else: 149 | data_eq = rx_frame.tdata[:-4] == tx_frame.tdata 150 | 151 | try: 152 | iter(rx_frame.tuser) 153 | rx_crc_valid = rx_frame.tuser[-1] == 1 154 | except TypeError: 155 | rx_crc_valid = False 156 | 157 | 158 | if not data_eq: 159 | self.logger.critical(f"FAILED (Data Not Equal): {rx_frame}, {tx_frame}") 160 | for i, (tx,rx) in enumerate(zip(tx_frame.tdata, rx_frame.tdata)): 161 | if tx != rx: 162 | print(f'Index {i}, tx = 0x{tx:02x}, rx = 0x{rx:02x}') 163 | 164 | elif not rx_crc_valid: 165 | self.logger.critical(f"FAILED (CRC Valid Flag Not Set): {rx_frame}, {tx_frame}") 166 | else: 167 | self.logger.info(f"PASSED: {rx_frame}, {tx_frame}") 168 | 169 | assert data_eq and rx_crc_valid 170 | 171 | if not had_frame: self.logger.critical(f"Didn't recieve any frames") 172 | assert had_frame 173 | 174 | 175 | 176 | class EthEnv(uvm_env): 177 | def build_phase(self): 178 | self.config = ConfigDB().get(self, "", 'run_config') 179 | 180 | self.seqr = uvm_sequencer('seqr', self) 181 | ConfigDB().set(None, '*', 'SEQR', self.seqr) 182 | 183 | self.bfm = MacPcsBfm() 184 | self.bfm.set_axis_log(self.config['print_axis']) 185 | 186 | self.driver = TxDriver.create('driver', self) 187 | self.tx_mon = Monitor("tx_mon", self, "get_tx_frame") 188 | self.rx_mon = Monitor("rx_mon", self, "get_rx_frame") 189 | self.scoreboard = Scoreboard("scoreboard", self) 190 | 191 | def connect_phase(self): 192 | self.driver.seq_item_port.connect(self.seqr.seq_item_export) 193 | self.tx_mon.ap.connect(self.scoreboard.tx_frame_export) 194 | self.rx_mon.ap.connect(self.scoreboard.rx_frame_export) 195 | 196 | 197 | class MacPcsTest(uvm_test): 198 | def build_phase(self): 199 | 200 | with open('mac_pcs_config.yaml', 'r') as f: 201 | self.config = yaml.safe_load(f) 202 | 203 | ConfigDB().set(None, '*', 'run_config', self.config) 204 | 205 | np.random.seed(self.config['seed']) 206 | 207 | if self.config['debug']: 208 | debugpy.listen(5678) 209 | debugpy.wait_for_client() 210 | debugpy.breakpoint() 211 | 212 | self.env = EthEnv("env", self) 213 | 214 | def end_of_elaboration_phase(self): 215 | self.test_random = EthTxAllSeq.create("test_random") 216 | 217 | async def run_phase(self): 218 | self.raise_objection() 219 | await self.test_random.start() 220 | self.drop_objection() 221 | 222 | 223 | 224 | @cocotb.test() 225 | async def run_MacPcsTest(pytestconfig): 226 | # 227 | await uvm_root().run_test(MacPcsTest) 228 | 229 | @pytest.mark.parametrize( 230 | "parameters,config", [ 231 | ({"EXTERNAL_GEARBOX": "0", "SCRAMBLER_BYPASS": "0"}, {"loopback_bit_slip": 0}), 232 | ({"EXTERNAL_GEARBOX": "0", "SCRAMBLER_BYPASS": "0"}, {"loopback_bit_slip": 1}), 233 | ({"EXTERNAL_GEARBOX": "0", "SCRAMBLER_BYPASS": "0"}, {"loopback_bit_slip": 2}), 234 | ({"EXTERNAL_GEARBOX": "0", "SCRAMBLER_BYPASS": "0"}, {"loopback_bit_slip": 3}), 235 | ({"EXTERNAL_GEARBOX": "1", "SCRAMBLER_BYPASS": "0"}, {"loopback_bit_slip": 0}), 236 | ]) 237 | def test_mac_pcs(parameters, config): 238 | 239 | test_variables = {**parameters, **config} 240 | 241 | sim_build = "./sim_build/" + ",".join((f"{key}={str(value)}" for key, value in test_variables.items())) 242 | 243 | os.makedirs(sim_build, exist_ok=True) 244 | 245 | with open('mac_pcs_config.yaml', 'r') as f: 246 | base_config = yaml.safe_load(f) 247 | 248 | base_config.update(config) 249 | 250 | with open(os.path.join(sim_build, "mac_pcs_config.yaml"), 'w') as f: 251 | yaml.dump(base_config, f) 252 | 253 | copyfile("../../lib/slicing_crc/hdl/crc_tables.mem", os.path.join(sim_build, "crc_tables.mem")) 254 | 255 | source_tree = [ 256 | glob.glob('../../hdl/mac_pcs.sv'), 257 | glob.glob('../../hdl/mac/*.sv'), 258 | glob.glob('../../hdl/pcs/*.sv'), 259 | glob.glob('../../lib/slicing_crc/hdl/*.sv') 260 | ] 261 | 262 | sources = [item for sublist in source_tree for item in sublist] 263 | 264 | run( 265 | verilog_sources=sources, 266 | toplevel="mac_pcs", 267 | 268 | module="test_mac_pcs", 269 | simulator="icarus", 270 | verilog_compile_args=["-g2012"], 271 | includes=["../../hdl/include/"], 272 | parameters=parameters, 273 | extra_env=parameters, 274 | sim_build=sim_build 275 | ) -------------------------------------------------------------------------------- /src/hdl/serdes/gtwizard_ultrascale_0_example_init.v: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // (c) Copyright 2013-2018 Xilinx, Inc. All rights reserved. 3 | // 4 | // This file contains confidential and proprietary information 5 | // of Xilinx, Inc. and is protected under U.S. and 6 | // international copyright and other intellectual property 7 | // laws. 8 | // 9 | // DISCLAIMER 10 | // This disclaimer is not a license and does not grant any 11 | // rights to the materials distributed herewith. Except as 12 | // otherwise provided in a valid license issued to you by 13 | // Xilinx, and to the maximum extent permitted by applicable 14 | // law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND 15 | // WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES 16 | // AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 17 | // BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- 18 | // INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and 19 | // (2) Xilinx shall not be liable (whether in contract or tort, 20 | // including negligence, or under any other theory of 21 | // liability) for any loss or damage of any kind or nature 22 | // related to, arising under or in connection with these 23 | // materials, including for any direct, or any indirect, 24 | // special, incidental, or consequential loss or damage 25 | // (including loss of data, profits, goodwill, or any type of 26 | // loss or damage suffered as a result of any action brought 27 | // by a third party) even if such damage or loss was 28 | // reasonably foreseeable or Xilinx had been advised of the 29 | // possibility of the same. 30 | // 31 | // CRITICAL APPLICATIONS 32 | // Xilinx products are not designed or intended to be fail- 33 | // safe, or for use in any application requiring fail-safe 34 | // performance, such as life-support or safety devices or 35 | // systems, Class III medical devices, nuclear facilities, 36 | // applications related to the deployment of airbags, or any 37 | // other applications that could lead to death, personal 38 | // injury, or severe property or environmental damage 39 | // (individually and collectively, "Critical 40 | // Applications"). Customer assumes the sole risk and 41 | // liability of any use of Xilinx products in Critical 42 | // Applications, subject only to applicable laws and 43 | // regulations governing limitations on product liability. 44 | // 45 | // THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS 46 | // PART OF THIS FILE AT ALL TIMES. 47 | //------------------------------------------------------------------------------ 48 | 49 | 50 | `timescale 1ps/1ps 51 | 52 | // ===================================================================================================================== 53 | // This example design initialization module provides a demonstration of how initialization logic can be constructed to 54 | // interact with and enhance the reset controller helper block in order to assist with successful system bring-up. This 55 | // example initialization logic monitors for timely reset completion, retrying resets as necessary to mitigate problems 56 | // with system bring-up such as clock or data connection readiness. This is an example and can be modified as necessary. 57 | // ===================================================================================================================== 58 | 59 | module gtwizard_ultrascale_0_example_init # ( 60 | 61 | parameter real P_FREERUN_FREQUENCY = 100, 62 | parameter real P_TX_TIMER_DURATION_US = 30000, 63 | parameter real P_RX_TIMER_DURATION_US = 130000 64 | 65 | )( 66 | 67 | input wire clk_freerun_in, 68 | input wire reset_all_in, 69 | input wire tx_init_done_in, 70 | input wire rx_init_done_in, 71 | input wire rx_data_good_in, 72 | output reg reset_all_out = 1'b0, 73 | output reg reset_rx_out = 1'b0, 74 | output reg init_done_out = 1'b0, 75 | output reg [3:0] retry_ctr_out = 4'd0 76 | 77 | ); 78 | 79 | 80 | // ------------------------------------------------------------------------------------------------------------------- 81 | // Synchronizers 82 | // ------------------------------------------------------------------------------------------------------------------- 83 | 84 | // Synchronize the "reset all" input signal into the free-running clock domain 85 | // The reset_all_in input should be driven by the master "reset all" example design input 86 | wire reset_all_sync; 87 | (* DONT_TOUCH = "TRUE" *) 88 | gtwizard_ultrascale_0_example_reset_synchronizer reset_synchronizer_reset_all_inst ( 89 | .clk_in (clk_freerun_in), 90 | .rst_in (reset_all_in), 91 | .rst_out (reset_all_sync) 92 | ); 93 | 94 | // Synchronize the TX initialization done indicator into the free-running clock domain 95 | // The tx_init_done_in input should be driven by the signal or logical combination of signals that represents a 96 | // completed TX initialization process; for example, the reset helper block gtwiz_reset_tx_done_out signal, or the 97 | // logical AND of gtwiz_reset_tx_done_out with gtwiz_buffbypass_tx_done_out if the TX buffer is bypassed. 98 | wire tx_init_done_sync; 99 | (* DONT_TOUCH = "TRUE" *) 100 | gtwizard_ultrascale_0_example_bit_synchronizer bit_synchronizer_tx_init_done_inst ( 101 | .clk_in (clk_freerun_in), 102 | .i_in (tx_init_done_in), 103 | .o_out (tx_init_done_sync) 104 | ); 105 | 106 | // Synchronize the RX initialization done indicator into the free-running clock domain 107 | // The rx_init_done_in input should be driven by the signal or logical combination of signals that represents a 108 | // completed RX initialization process; for example, the reset helper block gtwiz_reset_rx_done_out signal, or the 109 | // logical AND of gtwiz_reset_rx_done_out with gtwiz_buffbypass_rx_done_out if the RX elastic buffer is bypassed. 110 | wire rx_init_done_sync; 111 | (* DONT_TOUCH = "TRUE" *) 112 | gtwizard_ultrascale_0_example_bit_synchronizer bit_synchronizer_rx_init_done_inst ( 113 | .clk_in (clk_freerun_in), 114 | .i_in (rx_init_done_in), 115 | .o_out (rx_init_done_sync) 116 | ); 117 | 118 | // Synchronize the RX data good indicator into the free-running clock domain 119 | // The rx_data_good_in input should be driven the user application's indication of continual good data reception. 120 | // The example design drives rx_data_good_in high when no PRBS checker errors are seen in the 8 most recent 121 | // consecutive clock cycles of data reception. 122 | wire rx_data_good_sync; 123 | (* DONT_TOUCH = "TRUE" *) 124 | gtwizard_ultrascale_0_example_bit_synchronizer bit_synchronizer_rx_data_good_inst ( 125 | .clk_in (clk_freerun_in), 126 | .i_in (rx_data_good_in), 127 | .o_out (rx_data_good_sync) 128 | ); 129 | 130 | 131 | // ------------------------------------------------------------------------------------------------------------------- 132 | // Timer 133 | // ------------------------------------------------------------------------------------------------------------------- 134 | 135 | // Declare registers and local parameters used for the shared TX and RX initialization timer 136 | // The free-running clock frequency is specified by the P_FREERUN_FREQUENCY parameter. The TX initialization timer 137 | // duration is specified by the P_TX_TIMER_DURATION_US parameter (default 30,000us), and the resulting terminal count 138 | // is assigned to p_tx_timer_term_cyc_int. The RX initialization timer duration is specified by the 139 | // P_RX_TIMER_DURATION_US parameter (default 130,000us), and the resulting terminal count is assigned to 140 | // p_rx_timer_term_cyc_int. 141 | reg timer_clr = 1'b1; 142 | reg [24:0] timer_ctr = 25'd0; 143 | reg tx_timer_sat = 1'b0; 144 | reg rx_timer_sat = 1'b0; 145 | wire [24:0] p_tx_timer_term_cyc_int = P_TX_TIMER_DURATION_US * P_FREERUN_FREQUENCY; 146 | wire [24:0] p_rx_timer_term_cyc_int = P_RX_TIMER_DURATION_US * P_FREERUN_FREQUENCY; 147 | 148 | // When the timer is enabled by the initialization state machine, increment the timer_ctr counter until its value 149 | // reaches p_rx_timer_term_cyc_int RX terminal count and rx_timer_sat is asserted. Assert tx_timer_sat when the 150 | // counter value reaches the p_tx_timer_term_cyc_int TX terminal count. Clear the timer and remove assertions when the 151 | // timer is disabled by the initialization state machine. 152 | always @(posedge clk_freerun_in) begin 153 | if (timer_clr) begin 154 | timer_ctr <= 25'd0; 155 | tx_timer_sat <= 1'b0; 156 | rx_timer_sat <= 1'b0; 157 | end 158 | else begin 159 | if (timer_ctr == p_tx_timer_term_cyc_int) 160 | tx_timer_sat <= 1'b1; 161 | 162 | if (timer_ctr == p_rx_timer_term_cyc_int) 163 | rx_timer_sat <= 1'b1; 164 | else 165 | timer_ctr <= timer_ctr + 25'd1; 166 | end 167 | end 168 | 169 | 170 | // ------------------------------------------------------------------------------------------------------------------- 171 | // Retry counter 172 | // ------------------------------------------------------------------------------------------------------------------- 173 | 174 | // Increment the retry_ctr_out register for each TX or RX reset asserted by the initialization state machine until the 175 | // register saturates at 4'd15. This value, which is initialized on device programming and is never reset, could be 176 | // useful for debugging purposes. The initialization state machine will continue to retry as needed beyond the retry 177 | // register saturation point indicated, so 4'd15 should be interpreted as "15 or more attempts since programming." 178 | reg retry_ctr_incr = 1'b0; 179 | 180 | always @(posedge clk_freerun_in) begin 181 | if ((retry_ctr_incr == 1'b1) && (retry_ctr_out != 4'd15)) 182 | retry_ctr_out <= retry_ctr_out + 4'd1; 183 | end 184 | 185 | 186 | // ------------------------------------------------------------------------------------------------------------------- 187 | // Initialization state machine 188 | // ------------------------------------------------------------------------------------------------------------------- 189 | 190 | // Declare local parameters and state register for the initialization state machine 191 | localparam [1:0] ST_START = 2'd0; 192 | localparam [1:0] ST_TX_WAIT = 2'd1; 193 | localparam [1:0] ST_RX_WAIT = 2'd2; 194 | localparam [1:0] ST_MONITOR = 2'd3; 195 | reg [1:0] sm_init = ST_START; 196 | reg sm_init_active = 1'b0; 197 | 198 | // Implement the initialization state machine control and its outputs as a single sequential process. The state 199 | // machine is reset by the synchronized reset_all_in input, and does not begin operating until its first use. Note 200 | // that this state machine is designed to interact with and enhance the reset controller helper block. 201 | always @(posedge clk_freerun_in) begin 202 | if (reset_all_sync) begin 203 | timer_clr <= 1'b1; 204 | reset_all_out <= 1'b0; 205 | reset_rx_out <= 1'b0; 206 | retry_ctr_incr <= 1'b0; 207 | init_done_out <= 1'b0; 208 | sm_init_active <= 1'b1; 209 | sm_init <= ST_START; 210 | end 211 | else begin 212 | case (sm_init) 213 | 214 | // When starting the initialization procedure, clear the timer and remove reset outputs, then proceed to wait 215 | // for completion of TX initialization 216 | ST_START: begin 217 | if (sm_init_active) begin 218 | timer_clr <= 1'b1; 219 | reset_all_out <= 1'b0; 220 | reset_rx_out <= 1'b0; 221 | retry_ctr_incr <= 1'b0; 222 | sm_init <= ST_TX_WAIT; 223 | end 224 | end 225 | 226 | // Enable the timer. If TX initialization completes before the counter's TX terminal count, clear the timer and 227 | // proceed to wait for RX initialization. If the TX terminal count is reached, clear the timer, assert the 228 | // reset_all_out output (which in this example causes a master reset_all assertion), and increment the retry 229 | // counter. Completion conditions for TX initialization are described above. 230 | ST_TX_WAIT: begin 231 | if (tx_init_done_sync) begin 232 | timer_clr <= 1'b1; 233 | sm_init <= ST_RX_WAIT; 234 | end 235 | else begin 236 | if (tx_timer_sat) begin 237 | timer_clr <= 1'b1; 238 | reset_all_out <= 1'b1; 239 | retry_ctr_incr <= 1'b1; 240 | sm_init <= ST_START; 241 | end 242 | else begin 243 | timer_clr <= 1'b0; 244 | end 245 | end 246 | end 247 | 248 | // Enable the timer. When the RX terminal count is reached, check whether RX initialization has completed and 249 | // whether the data good indicator is high. If both conditions are met, transition to the MONITOR state. If 250 | // either condition is not met, then clear the timer, assert the reset_rx_out output (which in this example 251 | // either drives gtwiz_reset_rx_pll_and_datapath_in or gtwiz_reset_rx_datapath_in, depending on PLL sharing), 252 | // and increnent the retry counter. 253 | ST_RX_WAIT: begin 254 | if (rx_timer_sat) begin 255 | if (rx_init_done_sync && rx_data_good_sync) begin 256 | init_done_out <= 1'b1; 257 | sm_init <= ST_MONITOR; 258 | end 259 | else begin 260 | timer_clr <= 1'b1; 261 | reset_rx_out <= 1'b1; 262 | retry_ctr_incr <= 1'b1; 263 | sm_init <= ST_START; 264 | end 265 | end 266 | else begin 267 | timer_clr <= 1'b0; 268 | end 269 | end 270 | 271 | // In this MONITOR state, assert the init_done_out output for use as desired. If RX initialization or the data 272 | // good indicator is lost while in this state, reset the RX components as described in the ST_RX_WAIT state. 273 | ST_MONITOR: begin 274 | if (~rx_init_done_sync || ~rx_data_good_sync) begin 275 | init_done_out <= 1'b0; 276 | timer_clr <= 1'b1; 277 | reset_rx_out <= 1'b1; 278 | retry_ctr_incr <= 1'b1; 279 | sm_init <= ST_START; 280 | end 281 | end 282 | 283 | endcase 284 | end 285 | end 286 | 287 | 288 | endmodule 289 | -------------------------------------------------------------------------------- /src/hdl/serdes/gtwizard_ultrascale_0_example_wrapper.v: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // (c) Copyright 2013-2018 Xilinx, Inc. All rights reserved. 3 | // 4 | // This file contains confidential and proprietary information 5 | // of Xilinx, Inc. and is protected under U.S. and 6 | // international copyright and other intellectual property 7 | // laws. 8 | // 9 | // DISCLAIMER 10 | // This disclaimer is not a license and does not grant any 11 | // rights to the materials distributed herewith. Except as 12 | // otherwise provided in a valid license issued to you by 13 | // Xilinx, and to the maximum extent permitted by applicable 14 | // law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND 15 | // WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES 16 | // AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING 17 | // BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- 18 | // INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and 19 | // (2) Xilinx shall not be liable (whether in contract or tort, 20 | // including negligence, or under any other theory of 21 | // liability) for any loss or damage of any kind or nature 22 | // related to, arising under or in connection with these 23 | // materials, including for any direct, or any indirect, 24 | // special, incidental, or consequential loss or damage 25 | // (including loss of data, profits, goodwill, or any type of 26 | // loss or damage suffered as a result of any action brought 27 | // by a third party) even if such damage or loss was 28 | // reasonably foreseeable or Xilinx had been advised of the 29 | // possibility of the same. 30 | // 31 | // CRITICAL APPLICATIONS 32 | // Xilinx products are not designed or intended to be fail- 33 | // safe, or for use in any application requiring fail-safe 34 | // performance, such as life-support or safety devices or 35 | // systems, Class III medical devices, nuclear facilities, 36 | // applications related to the deployment of airbags, or any 37 | // other applications that could lead to death, personal 38 | // injury, or severe property or environmental damage 39 | // (individually and collectively, "Critical 40 | // Applications"). Customer assumes the sole risk and 41 | // liability of any use of Xilinx products in Critical 42 | // Applications, subject only to applicable laws and 43 | // regulations governing limitations on product liability. 44 | // 45 | // THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS 46 | // PART OF THIS FILE AT ALL TIMES. 47 | //------------------------------------------------------------------------------ 48 | 49 | 50 | `timescale 1ps/1ps 51 | 52 | // ===================================================================================================================== 53 | // This example design wrapper module instantiates the core and any helper blocks which the user chose to exclude from 54 | // the core, connects them as appropriate, and maps enabled ports 55 | // ===================================================================================================================== 56 | 57 | module gtwizard_ultrascale_0_example_wrapper #( 58 | parameter EXTERNAL_GEARBOX = 1 59 | ) ( 60 | input wire [0:0] gtyrxn_in 61 | ,input wire [0:0] gtyrxp_in 62 | ,output wire [0:0] gtytxn_out 63 | ,output wire [0:0] gtytxp_out 64 | ,input wire [0:0] gtwiz_userclk_tx_reset_in 65 | ,output wire [0:0] gtwiz_userclk_tx_srcclk_out 66 | ,output wire [0:0] gtwiz_userclk_tx_usrclk_out 67 | ,output wire [0:0] gtwiz_userclk_tx_usrclk2_out 68 | ,output wire [0:0] gtwiz_userclk_tx_active_out 69 | ,input wire [0:0] gtwiz_userclk_rx_reset_in 70 | ,output wire [0:0] gtwiz_userclk_rx_srcclk_out 71 | ,output wire [0:0] gtwiz_userclk_rx_usrclk_out 72 | ,output wire [0:0] gtwiz_userclk_rx_usrclk2_out 73 | ,output wire [0:0] gtwiz_userclk_rx_active_out 74 | ,input wire [0:0] gtwiz_buffbypass_tx_reset_in 75 | ,input wire [0:0] gtwiz_buffbypass_tx_start_user_in 76 | ,output wire [0:0] gtwiz_buffbypass_tx_done_out 77 | ,output wire [0:0] gtwiz_buffbypass_tx_error_out 78 | ,input wire [0:0] gtwiz_buffbypass_rx_reset_in 79 | ,input wire [0:0] gtwiz_buffbypass_rx_start_user_in 80 | ,output wire [0:0] gtwiz_buffbypass_rx_done_out 81 | ,output wire [0:0] gtwiz_buffbypass_rx_error_out 82 | ,input wire [0:0] gtwiz_reset_clk_freerun_in 83 | ,input wire [0:0] gtwiz_reset_all_in 84 | ,input wire [0:0] gtwiz_reset_tx_pll_and_datapath_in 85 | ,input wire [0:0] gtwiz_reset_tx_datapath_in 86 | ,input wire [0:0] gtwiz_reset_rx_pll_and_datapath_in 87 | ,input wire [0:0] gtwiz_reset_rx_datapath_in 88 | ,output wire [0:0] gtwiz_reset_rx_cdr_stable_out 89 | ,output wire [0:0] gtwiz_reset_tx_done_out 90 | ,output wire [0:0] gtwiz_reset_rx_done_out 91 | ,input wire [31:0] gtwiz_userdata_tx_in 92 | ,output wire [31:0] gtwiz_userdata_rx_out 93 | ,input wire [0:0] gtrefclk00_in 94 | ,output wire [0:0] qpll0outclk_out 95 | ,output wire [0:0] qpll0outrefclk_out 96 | ,input wire [2:0] loopback_in 97 | ,input wire [0:0] rxgearboxslip_in 98 | ,input wire [5:0] txheader_in 99 | ,input wire [6:0] txsequence_in 100 | ,output wire [0:0] gtpowergood_out 101 | ,output wire [1:0] rxdatavalid_out 102 | ,output wire [5:0] rxheader_out 103 | ,output wire [1:0] rxheadervalid_out 104 | ,output wire [0:0] rxpmaresetdone_out 105 | ,output wire [1:0] rxstartofseq_out 106 | ,output wire [0:0] txpmaresetdone_out 107 | ,output wire [0:0] txprgdivresetdone_out 108 | ); 109 | 110 | 111 | // =================================================================================================================== 112 | // PARAMETERS AND FUNCTIONS 113 | // =================================================================================================================== 114 | 115 | // Declare and initialize local parameters and functions used for HDL generation 116 | localparam [191:0] P_CHANNEL_ENABLE = 192'b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000; 117 | // `include "gtwizard_ultrascale_0_example_wrapper_functions.v" 118 | // localparam integer P_TX_MASTER_CH_PACKED_IDX = f_calc_pk_mc_idx(12); 119 | // localparam integer P_RX_MASTER_CH_PACKED_IDX = f_calc_pk_mc_idx(12); 120 | 121 | 122 | // =================================================================================================================== 123 | // HELPER BLOCKS 124 | // =================================================================================================================== 125 | 126 | // Any helper blocks which the user chose to exclude from the core will appear below. In addition, some signal 127 | // assignments related to optionally-enabled ports may appear below. 128 | wire [0:0] gtpowergood_int; 129 | 130 | // Required assignment to expose the GTPOWERGOOD port per user request 131 | assign gtpowergood_out = gtpowergood_int; 132 | 133 | // =================================================================================================================== 134 | // CORE INSTANCE 135 | // =================================================================================================================== 136 | 137 | // Instantiate the core, mapping its enabled ports to example design ports and helper blocks as appropriate 138 | 139 | generate if (EXTERNAL_GEARBOX) begin 140 | gtwizard_ultrascale_inc_gearbox gtwizard_ultrascale_0_inst ( 141 | .gtyrxn_in (gtyrxn_in) 142 | ,.gtyrxp_in (gtyrxp_in) 143 | ,.gtytxn_out (gtytxn_out) 144 | ,.gtytxp_out (gtytxp_out) 145 | ,.gtwiz_userclk_tx_reset_in (gtwiz_userclk_tx_reset_in) 146 | ,.gtwiz_userclk_tx_srcclk_out (gtwiz_userclk_tx_srcclk_out) 147 | ,.gtwiz_userclk_tx_usrclk_out (gtwiz_userclk_tx_usrclk_out) 148 | ,.gtwiz_userclk_tx_usrclk2_out (gtwiz_userclk_tx_usrclk2_out) 149 | ,.gtwiz_userclk_tx_active_out (gtwiz_userclk_tx_active_out) 150 | ,.gtwiz_userclk_rx_reset_in (gtwiz_userclk_rx_reset_in) 151 | ,.gtwiz_userclk_rx_srcclk_out (gtwiz_userclk_rx_srcclk_out) 152 | ,.gtwiz_userclk_rx_usrclk_out (gtwiz_userclk_rx_usrclk_out) 153 | ,.gtwiz_userclk_rx_usrclk2_out (gtwiz_userclk_rx_usrclk2_out) 154 | ,.gtwiz_userclk_rx_active_out (gtwiz_userclk_rx_active_out) 155 | ,.gtwiz_buffbypass_tx_reset_in (gtwiz_buffbypass_tx_reset_in) 156 | ,.gtwiz_buffbypass_tx_start_user_in (gtwiz_buffbypass_tx_start_user_in) 157 | ,.gtwiz_buffbypass_tx_done_out (gtwiz_buffbypass_tx_done_out) 158 | ,.gtwiz_buffbypass_tx_error_out (gtwiz_buffbypass_tx_error_out) 159 | ,.gtwiz_buffbypass_rx_reset_in (gtwiz_buffbypass_rx_reset_in) 160 | ,.gtwiz_buffbypass_rx_start_user_in (gtwiz_buffbypass_rx_start_user_in) 161 | ,.gtwiz_buffbypass_rx_done_out (gtwiz_buffbypass_rx_done_out) 162 | ,.gtwiz_buffbypass_rx_error_out (gtwiz_buffbypass_rx_error_out) 163 | ,.gtwiz_reset_clk_freerun_in (gtwiz_reset_clk_freerun_in) 164 | ,.gtwiz_reset_all_in (gtwiz_reset_all_in) 165 | ,.gtwiz_reset_tx_pll_and_datapath_in (gtwiz_reset_tx_pll_and_datapath_in) 166 | ,.gtwiz_reset_tx_datapath_in (gtwiz_reset_tx_datapath_in) 167 | ,.gtwiz_reset_rx_pll_and_datapath_in (gtwiz_reset_rx_pll_and_datapath_in) 168 | ,.gtwiz_reset_rx_datapath_in (gtwiz_reset_rx_datapath_in) 169 | ,.gtwiz_reset_rx_cdr_stable_out (gtwiz_reset_rx_cdr_stable_out) 170 | ,.gtwiz_reset_tx_done_out (gtwiz_reset_tx_done_out) 171 | ,.gtwiz_reset_rx_done_out (gtwiz_reset_rx_done_out) 172 | ,.gtwiz_userdata_tx_in (gtwiz_userdata_tx_in) 173 | ,.gtwiz_userdata_rx_out (gtwiz_userdata_rx_out) 174 | ,.gtrefclk00_in (gtrefclk00_in) 175 | ,.qpll0outclk_out (qpll0outclk_out) 176 | ,.qpll0outrefclk_out (qpll0outrefclk_out) 177 | ,.rxgearboxslip_in (rxgearboxslip_in) 178 | ,.txheader_in (txheader_in) 179 | ,.txsequence_in (txsequence_in) 180 | ,.gtpowergood_out (gtpowergood_int) 181 | ,.rxdatavalid_out (rxdatavalid_out) 182 | ,.rxheader_out (rxheader_out) 183 | ,.rxheadervalid_out (rxheadervalid_out) 184 | ,.rxpmaresetdone_out (rxpmaresetdone_out) 185 | ,.rxstartofseq_out (rxstartofseq_out) 186 | ,.txpmaresetdone_out (txpmaresetdone_out) 187 | ,.txprgdivresetdone_out (txprgdivresetdone_out) 188 | ,.loopback_in (loopback_in) 189 | ); 190 | end else begin 191 | gtwizard_ultrascale_raw gtwizard_ultrascale_0_inst ( 192 | .gtyrxn_in (gtyrxn_in) 193 | ,.gtyrxp_in (gtyrxp_in) 194 | ,.gtytxn_out (gtytxn_out) 195 | ,.gtytxp_out (gtytxp_out) 196 | ,.gtwiz_userclk_tx_reset_in (gtwiz_userclk_tx_reset_in) 197 | ,.gtwiz_userclk_tx_srcclk_out (gtwiz_userclk_tx_srcclk_out) 198 | ,.gtwiz_userclk_tx_usrclk_out (gtwiz_userclk_tx_usrclk_out) 199 | ,.gtwiz_userclk_tx_usrclk2_out (gtwiz_userclk_tx_usrclk2_out) 200 | ,.gtwiz_userclk_tx_active_out (gtwiz_userclk_tx_active_out) 201 | ,.gtwiz_userclk_rx_reset_in (gtwiz_userclk_rx_reset_in) 202 | ,.gtwiz_userclk_rx_srcclk_out (gtwiz_userclk_rx_srcclk_out) 203 | ,.gtwiz_userclk_rx_usrclk_out (gtwiz_userclk_rx_usrclk_out) 204 | ,.gtwiz_userclk_rx_usrclk2_out (gtwiz_userclk_rx_usrclk2_out) 205 | ,.gtwiz_userclk_rx_active_out (gtwiz_userclk_rx_active_out) 206 | ,.gtwiz_buffbypass_tx_reset_in (gtwiz_buffbypass_tx_reset_in) 207 | ,.gtwiz_buffbypass_tx_start_user_in (gtwiz_buffbypass_tx_start_user_in) 208 | ,.gtwiz_buffbypass_tx_done_out (gtwiz_buffbypass_tx_done_out) 209 | ,.gtwiz_buffbypass_tx_error_out (gtwiz_buffbypass_tx_error_out) 210 | ,.gtwiz_buffbypass_rx_reset_in (gtwiz_buffbypass_rx_reset_in) 211 | ,.gtwiz_buffbypass_rx_start_user_in (gtwiz_buffbypass_rx_start_user_in) 212 | ,.gtwiz_buffbypass_rx_done_out (gtwiz_buffbypass_rx_done_out) 213 | ,.gtwiz_buffbypass_rx_error_out (gtwiz_buffbypass_rx_error_out) 214 | ,.gtwiz_reset_clk_freerun_in (gtwiz_reset_clk_freerun_in) 215 | ,.gtwiz_reset_all_in (gtwiz_reset_all_in) 216 | ,.gtwiz_reset_tx_pll_and_datapath_in (gtwiz_reset_tx_pll_and_datapath_in) 217 | ,.gtwiz_reset_tx_datapath_in (gtwiz_reset_tx_datapath_in) 218 | ,.gtwiz_reset_rx_pll_and_datapath_in (gtwiz_reset_rx_pll_and_datapath_in) 219 | ,.gtwiz_reset_rx_datapath_in (gtwiz_reset_rx_datapath_in) 220 | ,.gtwiz_reset_rx_cdr_stable_out (gtwiz_reset_rx_cdr_stable_out) 221 | ,.gtwiz_reset_tx_done_out (gtwiz_reset_tx_done_out) 222 | ,.gtwiz_reset_rx_done_out (gtwiz_reset_rx_done_out) 223 | ,.gtwiz_userdata_tx_in (gtwiz_userdata_tx_in) 224 | ,.gtwiz_userdata_rx_out (gtwiz_userdata_rx_out) 225 | ,.gtrefclk00_in (gtrefclk00_in) 226 | ,.qpll0outclk_out (qpll0outclk_out) 227 | ,.qpll0outrefclk_out (qpll0outrefclk_out) 228 | ,.gtpowergood_out (gtpowergood_int) 229 | ,.rxpmaresetdone_out (rxpmaresetdone_out) 230 | ,.txpmaresetdone_out (txpmaresetdone_out) 231 | ,.txprgdivresetdone_out (txprgdivresetdone_out) 232 | ,.loopback_in (loopback_in) 233 | ); 234 | 235 | assign rxdatavalid_out = gtwiz_reset_tx_done_out ? 1'b1 : 1'b0; 236 | assign rxheader_out = 2'b0; 237 | assign rxheadervalid_out = 1'b0; 238 | assign rxstartofseq_out = 2'b0; 239 | 240 | end endgenerate 241 | 242 | 243 | endmodule 244 | --------------------------------------------------------------------------------