eth: Add support for 1000BASE-X autonegotiation to 1000BASE-X PHY

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2026-06-29 17:17:04 -07:00
parent d2745a6a4a
commit be7f5b5d27
7 changed files with 481 additions and 10 deletions

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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

View File

@@ -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 #(

View File

@@ -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)

View File

@@ -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']