From be7f5b5d27e98e3b093df04fe93dc52460841fd5 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Mon, 29 Jun 2026 17:17:04 -0700 Subject: [PATCH] eth: Add support for 1000BASE-X autonegotiation to 1000BASE-X PHY Signed-off-by: Alex Forencich --- src/eth/rtl/taxi_eth_phy_1g_basex.f | 2 + src/eth/rtl/taxi_eth_phy_1g_basex.sv | 116 ++++++++ src/eth/rtl/taxi_eth_phy_1g_basex_an.sv | 256 ++++++++++++++++++ src/eth/rtl/taxi_eth_phy_1g_basex_rx.sv | 22 +- src/eth/rtl/taxi_eth_phy_1g_basex_tx.sv | 16 +- src/eth/tb/taxi_eth_phy_1g_basex/Makefile | 1 + .../test_taxi_eth_phy_1g_basex.py | 78 ++++++ 7 files changed, 481 insertions(+), 10 deletions(-) create mode 100644 src/eth/rtl/taxi_eth_phy_1g_basex_an.sv diff --git a/src/eth/rtl/taxi_eth_phy_1g_basex.f b/src/eth/rtl/taxi_eth_phy_1g_basex.f index 08680fa..0c0c1a2 100644 --- a/src/eth/rtl/taxi_eth_phy_1g_basex.f +++ b/src/eth/rtl/taxi_eth_phy_1g_basex.f @@ -1,3 +1,5 @@ taxi_eth_phy_1g_basex.sv taxi_eth_phy_1g_basex_rx.f taxi_eth_phy_1g_basex_tx.f +taxi_eth_phy_1g_basex_an.sv +../lib/taxi/src/sync/rtl/taxi_sync_signal.sv diff --git a/src/eth/rtl/taxi_eth_phy_1g_basex.sv b/src/eth/rtl/taxi_eth_phy_1g_basex.sv index e74f6d2..ccbff1e 100644 --- a/src/eth/rtl/taxi_eth_phy_1g_basex.sv +++ b/src/eth/rtl/taxi_eth_phy_1g_basex.sv @@ -21,6 +21,7 @@ module taxi_eth_phy_1g_basex # parameter CTRL_W = (DATA_W/8), parameter logic TX_GBX_IF_EN = 1'b0, parameter logic RX_GBX_IF_EN = TX_GBX_IF_EN, + parameter logic AN_EN = 1'b1, parameter logic BIT_REVERSE = 1'b0, parameter logic ENC_8B10B_EN = 1'b0, parameter logic DEC_8B10B_EN = ENC_8B10B_EN, @@ -66,6 +67,17 @@ module taxi_eth_phy_1g_basex # input wire logic serdes_rx_data_valid = 1'b1, output wire logic serdes_rx_reset_req, + /* + * Autonegotiation + */ + input wire logic an_en = 1'b1, + input wire logic an_restart = 1'b0, + input wire logic an_speedup = 1'b0, + output wire logic an_intr, + output wire logic an_complete, + input wire logic [15:0] an_adv_ability = 16'h0020, + output wire logic [15:0] an_lp_adv_ability, + /* * Status */ @@ -83,10 +95,97 @@ module taxi_eth_phy_1g_basex # input wire logic cfg_rx_prbs31_enable = 1'b0 ); +// Autonegotiation +wire logic [15:0] rx_an_cfg; +wire logic rx_an_cfg_valid; +wire logic rx_an_ability_match; +wire logic rx_an_ack_match; +wire logic rx_an_idle_match; + +wire logic [15:0] tx_an_cfg; +wire logic tx_an_cfg_valid; +wire logic tx_an_cfg_ready; + +if (AN_EN) begin : an + + // synchronize RX signals to TX + wire [15:0] sync_rx_an_cfg; + wire sync_rx_an_cfg_valid; + wire sync_rx_an_ability_match; + wire sync_rx_an_ack_match; + wire sync_rx_an_idle_match; + + taxi_sync_signal #( + .WIDTH(16+4), + .N(2) + ) + sync_inst ( + .clk(tx_clk), + + .in({ + rx_an_cfg, + rx_an_cfg_valid, + rx_an_ability_match, + rx_an_ack_match, + rx_an_idle_match + }), + .out({ + sync_rx_an_cfg, + sync_rx_an_cfg_valid, + sync_rx_an_ability_match, + sync_rx_an_ack_match, + sync_rx_an_idle_match + }) + ); + + taxi_eth_phy_1g_basex_an #( + .DATA_W(DATA_W) + ) + an_inst ( + .clk(tx_clk), + .rst(tx_rst), + + /* + * AN config register + */ + .rx_an_cfg(sync_rx_an_cfg), + .rx_an_cfg_valid(sync_rx_an_cfg_valid), + .rx_an_ability_match(sync_rx_an_ability_match), + .rx_an_ack_match(sync_rx_an_ack_match), + .rx_an_idle_match(sync_rx_an_idle_match), + + .tx_an_cfg(tx_an_cfg), + .tx_an_cfg_valid(tx_an_cfg_valid), + .tx_an_cfg_ready(tx_an_cfg_ready), + + /* + * Autonegotiation + */ + .an_en(an_en), + .an_restart(an_restart), + .an_speedup(an_speedup), + .an_intr(an_intr), + .an_complete(an_complete), + .an_adv_ability(an_adv_ability), + .an_lp_adv_ability(an_lp_adv_ability) + ); + +end else begin : an + + assign tx_an_cfg = '0; + assign tx_an_cfg_valid = 1'b0; + + assign an_intr = 1'b0; + assign an_complete = 1'b0; + assign an_lp_adv_ability = '0; + +end + taxi_eth_phy_1g_basex_rx #( .DATA_W(DATA_W), .CTRL_W(CTRL_W), .GBX_IF_EN(RX_GBX_IF_EN), + .AN_EN(AN_EN), .BIT_REVERSE(BIT_REVERSE), .DEC_8B10B_EN(DEC_8B10B_EN), .PRBS31_EN(PRBS31_EN), @@ -113,6 +212,15 @@ rx_inst ( .serdes_rx_data_valid(serdes_rx_data_valid), .serdes_rx_reset_req(serdes_rx_reset_req), + /* + * AN config register + */ + .rx_an_cfg(rx_an_cfg), + .rx_an_cfg_valid(rx_an_cfg_valid), + .rx_an_ability_match(rx_an_ability_match), + .rx_an_ack_match(rx_an_ack_match), + .rx_an_idle_match(rx_an_idle_match), + /* * Status */ @@ -133,6 +241,7 @@ taxi_eth_phy_1g_basex_tx #( .DATA_W(DATA_W), .CTRL_W(CTRL_W), .GBX_IF_EN(TX_GBX_IF_EN), + .AN_EN(AN_EN), .BIT_REVERSE(BIT_REVERSE), .ENC_8B10B_EN(ENC_8B10B_EN), .PRBS31_EN(PRBS31_EN), @@ -165,6 +274,13 @@ tx_inst ( .serdes_tx_gbx_req_stall(serdes_tx_gbx_req_stall), .serdes_tx_gbx_sync(serdes_tx_gbx_sync), + /* + * AN config register + */ + .tx_an_cfg(tx_an_cfg), + .tx_an_cfg_valid(tx_an_cfg_valid), + .tx_an_cfg_ready(tx_an_cfg_ready), + /* * Configuration */ diff --git a/src/eth/rtl/taxi_eth_phy_1g_basex_an.sv b/src/eth/rtl/taxi_eth_phy_1g_basex_an.sv new file mode 100644 index 0000000..816344a --- /dev/null +++ b/src/eth/rtl/taxi_eth_phy_1g_basex_an.sv @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +/* + +Copyright (c) 2026 FPGA Ninja, LLC + +Authors: +- Alex Forencich + +*/ + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * 1000BASE-X Ethernet PHY autonegotiation + */ +module taxi_eth_phy_1g_basex_an # +( + parameter DATA_W = 16 +) +( + input wire logic clk, + input wire logic rst, + + /* + * AN config register + */ + input wire logic [15:0] rx_an_cfg, + input wire logic rx_an_cfg_valid, + input wire logic rx_an_ability_match, + input wire logic rx_an_ack_match, + input wire logic rx_an_idle_match, + + output wire logic [15:0] tx_an_cfg, + output wire logic tx_an_cfg_valid, + input wire logic tx_an_cfg_ready, + + /* + * Autonegotiation + */ + input wire logic an_en = 1'b1, + input wire logic an_restart = 1'b0, + input wire logic an_speedup = 1'b0, + output wire logic an_intr, + output wire logic an_complete, + input wire logic [15:0] an_adv_ability = 16'h0020, + output wire logic [15:0] an_lp_adv_ability +); + +localparam logic [15:0] AN_ACK = 16'h4000; +localparam logic [15:0] AN_NP = 16'h8000; + +typedef enum logic [2:0] { + STATE_START, + STATE_AN_RESTART, + STATE_ABILITY_DET, + STATE_ACK_DET, + STATE_ACK_CPL, + STATE_IDLE_DET, + STATE_DONE +} state_t; + +state_t state_reg = STATE_START, state_next; + +logic [20:0] delay_count_reg = '0, delay_count_next; +logic delay_run_reg = 1'b0, delay_run_next; + +logic [15:0] tx_an_cfg_reg = '0, tx_an_cfg_next; +logic tx_an_cfg_valid_reg = 1'b0, tx_an_cfg_valid_next; + +logic an_intr_reg = 1'b0, an_intr_next; +logic an_complete_reg = 1'b0, an_complete_next; +logic [15:0] an_lp_adv_ability_reg = '0, an_lp_adv_ability_next; + +assign tx_an_cfg = tx_an_cfg_reg; +assign tx_an_cfg_valid = tx_an_cfg_valid_reg; + +assign an_intr = an_intr_reg; +assign an_complete = an_complete_reg; +assign an_lp_adv_ability = an_lp_adv_ability_reg; + +always_comb begin + state_next = STATE_START; + + delay_count_next = delay_count_reg; + delay_run_next = delay_run_reg; + + tx_an_cfg_next = tx_an_cfg_reg; + tx_an_cfg_valid_next = tx_an_cfg_valid_reg && !tx_an_cfg_ready; + + an_intr_next = 1'b0; + an_complete_next = an_complete_reg; + an_lp_adv_ability_next = an_lp_adv_ability_reg; + + if (delay_run_reg) begin + if (delay_count_reg != 0) begin + delay_count_next = delay_count_reg - 1; + end else begin + delay_run_next = 1'b0; + end + end else begin + // 10 ms timer + if (DATA_W == 16) begin + delay_count_next = an_speedup ? 625 : 625000; + end else begin + delay_count_next = an_speedup ? 1250 : 1250000; + end + end + + case (state_reg) + STATE_START: begin + // start + an_complete_next = 1'b0; + + tx_an_cfg_next = '0; + + if (an_en) begin + tx_an_cfg_valid_next = 1'b1; + if (delay_run_reg) begin + // restart link timer + delay_run_next = 1'b0; + state_next = STATE_START; + end else begin + // AN restart state + delay_run_next = 1'b1; + state_next = STATE_AN_RESTART; + end + end else begin + // AN disabled + state_next = STATE_START; + end + end + STATE_AN_RESTART: begin + // AN restart - send zeroed config reg to trigger link partner to restart the AN process + tx_an_cfg_next = '0; + tx_an_cfg_valid_next = 1'b1; + + if (!delay_run_reg) begin + // link timer expired + state_next = STATE_ABILITY_DET; + end else begin + state_next = STATE_AN_RESTART; + end + end + STATE_ABILITY_DET: begin + // ability detect state - transfer AN ability value with ACK clear + tx_an_cfg_next = an_adv_ability & ~AN_ACK; + tx_an_cfg_valid_next = 1'b1; + + if (rx_an_ability_match && rx_an_cfg != 0) begin + // got ability advertisement from link partner + an_lp_adv_ability_next = rx_an_cfg; + state_next = STATE_ACK_DET; + end else begin + state_next = STATE_ABILITY_DET; + end + end + STATE_ACK_DET: begin + // acknowledge detect - wait for ACK from link partner + tx_an_cfg_next = an_adv_ability | AN_ACK; + tx_an_cfg_valid_next = 1'b1; + + if (rx_an_ability_match && rx_an_cfg == 0) begin + // restart request from link partner + state_next = STATE_START; + end else if (rx_an_ack_match) begin + // acknowledge match + an_lp_adv_ability_next = rx_an_cfg; + if (rx_an_cfg == (an_lp_adv_ability_reg | AN_ACK)) begin + // consistent with previously-seen value + delay_run_next = 1'b1; + state_next = STATE_ACK_CPL; + end else begin + // inconsistent, restart AN + state_next = STATE_START; + end + end else begin + state_next = STATE_ACK_DET; + end + end + STATE_ACK_CPL: begin + // complete acknowledge - give link partner time to detect our ACK + tx_an_cfg_next = an_adv_ability | AN_ACK; + tx_an_cfg_valid_next = 1'b1; + + if (rx_an_ability_match && rx_an_cfg == 0) begin + // restart request from link partner + state_next = STATE_START; + end else if (!delay_run_reg) begin + // link timer expired + delay_run_next = 1'b1; + state_next = STATE_IDLE_DET; + end else begin + state_next = STATE_ACK_CPL; + end + end + STATE_IDLE_DET: begin + if (rx_an_ability_match && rx_an_cfg == 0) begin + // restart request from link partner + state_next = STATE_START; + end else if (rx_an_idle_match && !delay_run_reg) begin + // idle match and link timer expired + an_complete_next = 1'b1; + state_next = STATE_DONE; + end else begin + state_next = STATE_IDLE_DET; + end + end + STATE_DONE: begin + if (rx_an_ability_match && rx_an_cfg == 0) begin + // restart request from link partner + state_next = STATE_START; + end else begin + state_next = STATE_DONE; + end + end + default: begin + state_next = STATE_START; + end + endcase + + if (!an_en || an_restart) begin + state_next = STATE_START; + end +end + +always @(posedge clk) begin + state_reg <= state_next; + + delay_count_reg <= delay_count_next; + delay_run_reg <= delay_run_next; + + tx_an_cfg_reg <= tx_an_cfg_next; + tx_an_cfg_valid_reg <= tx_an_cfg_valid_next; + + an_intr_reg <= an_intr_next; + an_complete_reg <= an_complete_next; + an_lp_adv_ability_reg <= an_lp_adv_ability_next; + + if (rst) begin + state_reg <= STATE_START; + + delay_count_reg <= '0; + delay_run_reg <= 1'b0; + + tx_an_cfg_valid_reg <= 1'b0; + + an_intr_reg <= 1'b0; + an_complete_reg <= 1'b0; + end +end + +endmodule + +`resetall diff --git a/src/eth/rtl/taxi_eth_phy_1g_basex_rx.sv b/src/eth/rtl/taxi_eth_phy_1g_basex_rx.sv index 1636b99..88e639e 100644 --- a/src/eth/rtl/taxi_eth_phy_1g_basex_rx.sv +++ b/src/eth/rtl/taxi_eth_phy_1g_basex_rx.sv @@ -20,6 +20,7 @@ module taxi_eth_phy_1g_basex_rx # parameter DATA_W = 16, parameter CTRL_W = (DATA_W/8), parameter logic GBX_IF_EN = 1'b0, + parameter logic AN_EN = 1'b1, parameter logic BIT_REVERSE = 1'b0, parameter logic DEC_8B10B_EN = 1'b0, parameter logic PRBS31_EN = 1'b0, @@ -46,6 +47,15 @@ module taxi_eth_phy_1g_basex_rx # input wire logic serdes_rx_data_valid = 1'b1, output wire logic serdes_rx_reset_req, + /* + * AN config register + */ + output wire logic [15:0] rx_an_cfg, + output wire logic rx_an_cfg_valid, + output wire logic rx_an_ability_match, + output wire logic rx_an_ack_match, + output wire logic rx_an_idle_match, + /* * Status */ @@ -115,7 +125,7 @@ taxi_gmii_basex_dec #( .DATA_W(DATA_W), .CTRL_W(CTRL_W), .GBX_IF_EN(GBX_IF_EN), - .AN_EN(1'b0) + .AN_EN(AN_EN) ) dec_inst ( .clk(clk), @@ -139,11 +149,11 @@ dec_inst ( /* * AN config register */ - .rx_an_cfg(), - .rx_an_cfg_valid(), - .rx_an_ability_match(), - .rx_an_ack_match(), - .rx_an_idle_match(), + .rx_an_cfg(rx_an_cfg), + .rx_an_cfg_valid(rx_an_cfg_valid), + .rx_an_ability_match(rx_an_ability_match), + .rx_an_ack_match(rx_an_ack_match), + .rx_an_idle_match(rx_an_idle_match), /* * Status diff --git a/src/eth/rtl/taxi_eth_phy_1g_basex_tx.sv b/src/eth/rtl/taxi_eth_phy_1g_basex_tx.sv index 827a344..9fe5db4 100644 --- a/src/eth/rtl/taxi_eth_phy_1g_basex_tx.sv +++ b/src/eth/rtl/taxi_eth_phy_1g_basex_tx.sv @@ -20,6 +20,7 @@ module taxi_eth_phy_1g_basex_tx # parameter DATA_W = 16, parameter CTRL_W = (DATA_W/8), parameter logic GBX_IF_EN = 1'b0, + parameter logic AN_EN = 1'b1, parameter logic BIT_REVERSE = 1'b0, parameter logic ENC_8B10B_EN = 1'b0, parameter logic PRBS31_EN = 1'b0, @@ -52,6 +53,13 @@ module taxi_eth_phy_1g_basex_tx # input wire logic serdes_tx_gbx_req_stall = 1'b0, output wire logic serdes_tx_gbx_sync, + /* + * AN config register + */ + input wire logic [15:0] tx_an_cfg = '0, + input wire logic tx_an_cfg_valid = 1'b0, + output wire logic tx_an_cfg_ready, + /* * Configuration */ @@ -71,7 +79,7 @@ taxi_gmii_basex_enc #( .CTRL_W(CTRL_W), .GBX_IF_EN(GBX_IF_EN), .GBX_CNT(1), - .AN_EN(1'b0) + .AN_EN(AN_EN) ) enc_inst ( .clk(clk), @@ -99,9 +107,9 @@ enc_inst ( /* * AN config register */ - .tx_an_cfg('0), - .tx_an_cfg_valid(1'b0), - .tx_an_cfg_ready() + .tx_an_cfg(tx_an_cfg), + .tx_an_cfg_valid(tx_an_cfg_valid), + .tx_an_cfg_ready(tx_an_cfg_ready) ); taxi_eth_phy_1g_basex_tx_if #( diff --git a/src/eth/tb/taxi_eth_phy_1g_basex/Makefile b/src/eth/tb/taxi_eth_phy_1g_basex/Makefile index 0aa52dc..3f3bb18 100644 --- a/src/eth/tb/taxi_eth_phy_1g_basex/Makefile +++ b/src/eth/tb/taxi_eth_phy_1g_basex/Makefile @@ -35,6 +35,7 @@ export PARAM_DATA_W := 16 export PARAM_CTRL_W := $(shell expr $(PARAM_DATA_W) / 8 ) export PARAM_TX_GBX_IF_EN := 0 export PARAM_RX_GBX_IF_EN := $(PARAM_TX_GBX_IF_EN) +export PARAM_AN_EN := "1'b1" export PARAM_BIT_REVERSE := "1'b0" export PARAM_ENC_8B10B_EN := "1'b0" export PARAM_DEC_8B10B_EN := $(PARAM_ENC_8B10B_EN) diff --git a/src/eth/tb/taxi_eth_phy_1g_basex/test_taxi_eth_phy_1g_basex.py b/src/eth/tb/taxi_eth_phy_1g_basex/test_taxi_eth_phy_1g_basex.py index 8742041..5276af8 100644 --- a/src/eth/tb/taxi_eth_phy_1g_basex/test_taxi_eth_phy_1g_basex.py +++ b/src/eth/tb/taxi_eth_phy_1g_basex/test_taxi_eth_phy_1g_basex.py @@ -75,6 +75,11 @@ class TB: gbx_cfg=gbx_cfg ) + dut.an_en.value = 0 + dut.an_restart.value = 0 + dut.an_speedup.value = 1 + dut.an_adv_ability.value = 0x0020 + dut.cfg_tx_prbs31_enable.setimmediatevalue(0) dut.cfg_rx_prbs31_enable.setimmediatevalue(0) @@ -154,6 +159,73 @@ async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12): await RisingEdge(dut.tx_clk) +async def run_test_an(dut): + + tb = TB(dut) + + tb.dut.an_en.value = 1 + tb.dut.an_restart.value = 0 + tb.dut.an_speedup.value = 1 + tb.dut.an_adv_ability.value = 0x0020 + + await tb.reset() + + for k in range(100): + await RisingEdge(dut.tx_clk) + + tb.log.info("AN_RESTART") + tb.serdes_source.set_an_cfg(0x0000) + + # link timer + for k in range(1250): + await RisingEdge(dut.tx_clk) + + tb.log.info("ABILITY_DETECT") + tb.serdes_source.set_an_cfg(0x002A) + + lp_cfg = None + while True: + await RisingEdge(dut.tx_clk) + lp_cfg = tb.serdes_sink.get_an_cfg() + if tb.serdes_sink.get_an_ability_match() and lp_cfg is not None and lp_cfg != 0: + break + + assert lp_cfg & 0xbfff == int(tb.dut.an_adv_ability.value) & 0xbfff + + tb.log.info("ACKNOWLEDGE_DETECT") + tb.serdes_source.set_an_cfg(0x402A) + + while True: + await RisingEdge(dut.tx_clk) + cfg = tb.serdes_sink.get_an_cfg() + if tb.serdes_sink.get_an_ack_match(): + if lp_cfg | 0x4000 == cfg: + break + + tb.log.info("COMPLETE_ACKNOWLEDGE") + # link timer + for k in range(1250): + await RisingEdge(dut.tx_clk) + + assert lp_cfg & 0xbfff == int(tb.dut.an_adv_ability.value) & 0xbfff + assert int(tb.dut.an_lp_adv_ability) & 0xbfff == 0x002A + + tb.log.info("IDLE_DETECT") + tb.serdes_source.set_an_cfg(None) + + # link timer + for k in range(1250): + await RisingEdge(dut.tx_clk) + + while True: + await RisingEdge(dut.tx_clk) + if tb.serdes_sink.get_an_idle_match(): + break + + for k in range(10): + await RisingEdge(dut.tx_clk) + + def size_list(): return list(range(60, 128)) + [512, 1514, 9214] + [60]*10 @@ -176,6 +248,11 @@ if getattr(cocotb, 'top', None) is not None: factory.add_option("ifg", [12]) factory.generate_tests() + if cocotb.top.AN_EN.value: + for test in [run_test_an]: + factory = TestFactory(test) + factory.generate_tests() + # cocotb-test @@ -217,6 +294,7 @@ def test_taxi_eth_phy_1g_basex(request, data_w): parameters['CTRL_W'] = parameters['DATA_W'] // 8 parameters['TX_GBX_IF_EN'] = 0 parameters['RX_GBX_IF_EN'] = parameters['TX_GBX_IF_EN'] + parameters['AN_EN'] = "1'b1" parameters['BIT_REVERSE'] = "1'b0" parameters['ENC_8B10B_EN'] = "1'b0" parameters['DEC_8B10B_EN'] = parameters['ENC_8B10B_EN']