Implement generic MAX2771 raw Ethernet recorder

This commit is contained in:
onur
2026-08-01 01:15:47 +03:00
commit 79d07fdda7
57 changed files with 4810 additions and 0 deletions
@@ -0,0 +1,147 @@
`default_nettype none
// RTL8211FDI link qualification over IEEE 802.3 Clause-22 MDIO. The board
// straps the PHY for auto-negotiation and address 2. Realtek PHYSR is on page
// 0xa43, register 0x1a; it reports real-time link, resolved speed and duplex.
// Page 0 is restored after every poll so no hidden PHY state leaks outward.
module phy_init_rtl8211f #(
parameter int unsigned CLK_HZ = 125_000_000,
parameter int unsigned SETTLE_MS = 30,
parameter int unsigned POLL_MS = 100,
parameter logic [4:0] PHY_ADDRESS = 5'd2
) (
input wire logic clk,
input wire logic rst,
input wire logic phy_reset_complete,
output logic mdio_start,
output logic mdio_write,
output logic [4:0] mdio_phy_address,
output logic [4:0] mdio_register_address,
output logic [15:0] mdio_write_data,
input wire logic mdio_busy,
input wire logic mdio_done,
input wire logic [15:0] mdio_read_data,
output logic initialization_complete,
output logic link_ready,
output logic [63:0] link_down_events
);
localparam longint unsigned SETTLE_CYCLES = (CLK_HZ / 1000) * SETTLE_MS;
localparam longint unsigned POLL_CYCLES = (CLK_HZ / 1000) * POLL_MS;
localparam longint unsigned MAX_CYCLES =
(POLL_CYCLES > SETTLE_CYCLES) ? POLL_CYCLES : SETTLE_CYCLES;
localparam int unsigned TIMER_W = (MAX_CYCLES < 2) ? 1 : $clog2(MAX_CYCLES + 1);
typedef enum logic [3:0] {
S_WAIT_RESET, S_SETTLE,
S_START_PAGE, S_WAIT_PAGE,
S_START_PHYSR, S_WAIT_PHYSR,
S_START_RESTORE, S_WAIT_RESTORE,
S_POLL_WAIT
} state_t;
state_t state;
logic [TIMER_W-1:0] timer;
logic [15:0] physr;
logic qualified_link;
always_comb begin
mdio_write = (state == S_START_PAGE || state == S_WAIT_PAGE ||
state == S_START_RESTORE || state == S_WAIT_RESTORE);
mdio_phy_address = PHY_ADDRESS;
mdio_register_address = (state == S_START_PHYSR || state == S_WAIT_PHYSR)
? 5'h1a : 5'h1f;
mdio_write_data = (state == S_START_PAGE || state == S_WAIT_PAGE)
? 16'h0a43 : 16'h0000;
// PHYSR[5:4]=2'b10 is 1000 Mb/s, bit 3 full duplex, bit 2 link.
qualified_link = (physr[5:4] == 2'b10) && physr[3] && physr[2];
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S_WAIT_RESET;
timer <= '0;
physr <= 16'd0;
mdio_start <= 1'b0;
initialization_complete <= 1'b0;
link_ready <= 1'b0;
link_down_events <= 64'd0;
end else begin
mdio_start <= 1'b0;
case (state)
S_WAIT_RESET: begin
link_ready <= 1'b0;
if (phy_reset_complete) begin
timer <= '0;
state <= S_SETTLE;
end
end
S_SETTLE: begin
if (timer == SETTLE_CYCLES-1) begin
timer <= '0;
state <= S_START_PAGE;
end else begin
timer <= timer + {{(TIMER_W-1){1'b0}}, 1'b1};
end
end
S_START_PAGE: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_PAGE;
end
end
S_WAIT_PAGE: if (mdio_done) state <= S_START_PHYSR;
S_START_PHYSR: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_PHYSR;
end
end
S_WAIT_PHYSR: begin
if (mdio_done) begin
physr <= mdio_read_data;
state <= S_START_RESTORE;
end
end
S_START_RESTORE: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_RESTORE;
end
end
S_WAIT_RESTORE: begin
if (mdio_done) begin
initialization_complete <= 1'b1;
if (link_ready && !qualified_link)
link_down_events <= link_down_events + 64'd1;
link_ready <= qualified_link;
timer <= '0;
state <= S_POLL_WAIT;
end
end
S_POLL_WAIT: begin
if (timer == POLL_CYCLES-1) begin
timer <= '0;
state <= S_START_PAGE;
end else begin
timer <= timer + {{(TIMER_W-1){1'b0}}, 1'b1};
end
end
default: state <= S_WAIT_RESET;
endcase
end
end
initial begin
if (CLK_HZ < 1000) $error("CLK_HZ must be at least 1000");
if (SETTLE_MS == 0 || POLL_MS == 0)
$error("SETTLE_MS and POLL_MS must be non-zero");
end
endmodule
`default_nettype wire
@@ -0,0 +1,31 @@
`default_nettype none
// Core-side half-cycle values for Efinity Interface Designer DDIO outputs.
// The physical GPIOs are configured as DDIO_RESYNC in interface/create_interface.py.
// *_HI is launched on the rising edge and *_LO on the falling edge.
module rgmii_tx_efinix (
input wire logic [7:0] gmii_txd,
input wire logic gmii_tx_en,
input wire logic gmii_tx_er,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO
);
always_comb begin
// RGMII: GMII bits 3:0 on the rising edge, bits 7:4 on falling.
eth_txd_HI = gmii_txd[3:0];
eth_txd_LO = gmii_txd[7:4];
eth_txctl_HI = gmii_tx_en;
eth_txctl_LO = gmii_tx_en ^ gmii_tx_er;
// These constants are clocked by the 90-degree PLL output in the
// periphery, producing the forwarded 125 MHz RGMII TX clock.
eth_txc_HI = 1'b1;
eth_txc_LO = 1'b0;
end
endmodule
`default_nettype wire
@@ -0,0 +1,142 @@
`default_nettype none
// Hardware-only Ethernet diagnostic. This top intentionally bypasses the RF
// capture/FIFO and supplies an endless, ordered 0..15 sample sequence to the
// production packetizer, MAC, FCS, RGMII and PHY-management blocks.
module ti375_ethernet_selftest_top (
input wire logic pll_dummy_clk,
input wire logic eth_clk,
input wire logic eth_clk90,
input wire logic pll_locked,
output logic pll_resetn,
input wire logic rf_clk_pmod,
input wire logic [3:0] rf_data_pmod_HI,
input wire logic [3:0] rf_data_pmod_LO,
output logic eth_mdc,
input wire logic eth_mdio_IN,
output logic eth_mdio_OUT,
output logic eth_mdio_OE,
output logic eth_phyrst_n,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO,
output logic [4:0] user_led
);
localparam int unsigned FIFO_DEPTH = 16384;
logic eth_rst;
logic phy_reset_complete;
logic mdio_start, mdio_write, mdio_busy, mdio_done;
logic [4:0] mdio_phy_address, mdio_register_address;
logic [15:0] mdio_write_data, mdio_read_data;
logic phy_init_complete, link_ready;
logic [63:0] link_down_events;
logic fifo_rd_en, fifo_rd_valid;
logic [68:0] fifo_rd_data;
logic [63:0] generated_sample_index;
logic mac_frame_start, mac_payload_valid, mac_payload_ready;
logic mac_busy, mac_frame_done;
logic [10:0] mac_payload_length;
logic [7:0] mac_payload_data;
logic [31:0] packet_sequence;
logic frame_activity_pulse;
logic [7:0] gmii_txd;
logic gmii_tx_en, gmii_tx_er;
logic [63:0] frames_transmitted, payload_bytes_transmitted;
logic [22:0] heartbeat;
logic activity_toggle;
assign pll_resetn = 1'b1;
reset_sync u_eth_reset (.clk(eth_clk), .arst(!pll_locked), .srst(eth_rst));
phy_reset_ctrl #(.CLK_HZ(125_000_000), .RESET_LOW_MS(10)) u_phy_reset (
.clk(eth_clk), .rst(eth_rst), .phy_reset_n(eth_phyrst_n),
.reset_complete(phy_reset_complete)
);
phy_init_rtl8211f u_phy_init (
.clk(eth_clk), .rst(eth_rst), .phy_reset_complete(phy_reset_complete),
.mdio_start(mdio_start), .mdio_write(mdio_write),
.mdio_phy_address(mdio_phy_address),
.mdio_register_address(mdio_register_address), .mdio_write_data(mdio_write_data),
.mdio_busy(mdio_busy), .mdio_done(mdio_done), .mdio_read_data(mdio_read_data),
.initialization_complete(phy_init_complete), .link_ready(link_ready),
.link_down_events(link_down_events)
);
mdio_master #(.MDC_DIV(25)) u_mdio (
.clk(eth_clk), .rst(eth_rst), .start(mdio_start),
.command_write(mdio_write), .phy_address(mdio_phy_address),
.register_address(mdio_register_address), .write_data(mdio_write_data),
.busy(mdio_busy), .done(mdio_done), .read_data(mdio_read_data),
.read_valid(), .mdc(eth_mdc), .mdio_i(eth_mdio_IN),
.mdio_o(eth_mdio_OUT), .mdio_oe(eth_mdio_OE)
);
// One-cycle read response that has the same entry format as max2771_capture:
// {discontinuity, absolute sample index, literal four-bit sample}.
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
fifo_rd_valid <= 1'b0;
fifo_rd_data <= 69'd0;
generated_sample_index <= 64'd0;
heartbeat <= 23'd0;
activity_toggle <= 1'b0;
end else begin
fifo_rd_valid <= fifo_rd_en;
if (fifo_rd_en) begin
fifo_rd_data <= {1'b0, generated_sample_index,
generated_sample_index[3:0]};
generated_sample_index <= generated_sample_index + 64'd1;
end
heartbeat <= heartbeat + 23'd1;
if (frame_activity_pulse)
activity_toggle <= !activity_toggle;
end
end
ethernet_packetizer #(
.FIFO_DEPTH(FIFO_DEPTH), .FULL_SAMPLES(2936),
.EARLIER_SAMPLE_HIGH_NIBBLE(1'b1)
) u_packetizer (
.clk(eth_clk), .rst(eth_rst), .link_ready(link_ready),
.dropped_samples_low(32'd0), .fifo_empty(1'b0),
.fifo_level(15'd16384), .fifo_rd_en(fifo_rd_en),
.fifo_rd_data(fifo_rd_data), .fifo_rd_valid(fifo_rd_valid),
.mac_frame_start(mac_frame_start), .mac_payload_length(mac_payload_length),
.mac_payload_data(mac_payload_data), .mac_payload_valid(mac_payload_valid),
.mac_payload_ready(mac_payload_ready), .mac_busy(mac_busy),
.mac_frame_done(mac_frame_done), .packet_sequence(packet_sequence),
.frame_activity_pulse(frame_activity_pulse)
);
ethernet_tx_mac u_mac (
.clk(eth_clk), .rst(eth_rst), .frame_start(mac_frame_start),
.payload_length(mac_payload_length), .payload_data(mac_payload_data),
.payload_valid(mac_payload_valid), .payload_ready(mac_payload_ready),
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.busy(mac_busy), .frame_done(mac_frame_done),
.frames_transmitted(frames_transmitted),
.payload_bytes_transmitted(payload_bytes_transmitted)
);
rgmii_tx_efinix u_rgmii_tx (
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.eth_txd_HI(eth_txd_HI), .eth_txd_LO(eth_txd_LO),
.eth_txctl_HI(eth_txctl_HI), .eth_txctl_LO(eth_txctl_LO),
.eth_txc_HI(eth_txc_HI), .eth_txc_LO(eth_txc_LO)
);
always_comb begin
user_led[0] = pll_locked || (!pll_locked && (pll_dummy_clk ^ eth_clk90));
user_led[1] = link_ready;
user_led[2] = heartbeat[22] || (!pll_locked &&
(rf_clk_pmod ^ (^rf_data_pmod_HI) ^
(^rf_data_pmod_LO)));
user_led[3] = activity_toggle;
user_led[4] = 1'b0;
end
endmodule
`default_nettype wire
@@ -0,0 +1,202 @@
`default_nettype none
module ti375_max2771_recorder_top #(
parameter bit CAPTURE_RISING = 1'b1,
parameter bit EARLIER_SAMPLE_HIGH_NIBBLE = 1'b1,
parameter int unsigned FIFO_DEPTH = 16384
) (
// Core clocks and status generated by the Efinity periphery PLL.
input wire logic pll_dummy_clk,
input wire logic eth_clk,
input wire logic eth_clk90,
input wire logic pll_locked,
output logic pll_resetn,
input wire logic rf_clk_pmod,
input wire logic [3:0] rf_data_pmod_HI,
input wire logic [3:0] rf_data_pmod_LO,
output logic eth_mdc,
input wire logic eth_mdio_IN,
output logic eth_mdio_OUT,
output logic eth_mdio_OE,
output logic eth_phyrst_n,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO,
output logic [4:0] user_led
);
localparam int unsigned FIFO_LEVEL_W = $clog2(FIFO_DEPTH) + 1;
logic eth_rst, rf_rst;
logic [3:0] selected_rf_data;
logic rf_fifo_clk;
logic fifo_w_en, fifo_full;
logic [68:0] fifo_w_data;
(* keep = "true" *) logic [FIFO_LEVEL_W-1:0] fifo_w_level, fifo_r_level;
(* keep = "true" *) logic [63:0] samples_seen, samples_captured, samples_dropped;
logic [63:0] dropped_samples_eth;
logic loss_latched_rf, loss_sync1, loss_sync2;
logic sample_activity_rf, sample_activity_eth;
logic fifo_rd_en, fifo_rd_valid, fifo_empty;
logic [68:0] fifo_rd_data;
(* keep = "true" *) logic [63:0] fifo_overflow_attempts, fifo_underflow_attempts;
logic phy_reset_complete;
logic mdio_start, mdio_write, mdio_busy, mdio_done, mdio_read_valid;
logic [4:0] mdio_phy_address, mdio_register_address;
logic [15:0] mdio_write_data, mdio_read_data;
logic phy_init_complete, link_ready;
(* keep = "true" *) logic [63:0] link_down_events;
logic mac_frame_start, mac_payload_valid, mac_payload_ready, mac_busy, mac_frame_done;
logic [10:0] mac_payload_length;
logic [7:0] mac_payload_data;
logic [7:0] gmii_txd;
logic gmii_tx_en, gmii_tx_er;
(* keep = "true" *) logic [63:0] frames_transmitted, payload_bytes_transmitted;
logic [31:0] packet_sequence;
logic frame_activity_pulse;
logic [23:0] sample_led_stretch;
logic [12:0] frame_activity_divider;
logic frame_activity_toggle;
// Efinity requires CLKOUT0 to stay within 4x of the reference clock. It is
// a 100 MHz housekeeping output. The pre-lock LED probe below also keeps
// both PLL clock interface ports present for generated SDC constraints.
assign pll_resetn = 1'b1;
assign selected_rf_data = CAPTURE_RISING ? rf_data_pmod_HI : rf_data_pmod_LO;
reset_sync u_eth_reset (.clk(eth_clk), .arst(!pll_locked), .srst(eth_rst));
// Both physical edges are captured in Titanium GPIO DDIO registers. The
// selected half-cycle is then passed through the common capture/index block.
max2771_capture #(.CAPTURE_RISING(1'b1)) u_capture (
.rf_clk_pmod(rf_clk_pmod), .rst(rf_rst), .rf_data_pmod(selected_rf_data),
.fifo_full(fifo_full), .fifo_w_clk(rf_fifo_clk), .fifo_w_en(fifo_w_en),
.fifo_w_data(fifo_w_data), .samples_seen(samples_seen),
.samples_captured(samples_captured), .samples_dropped(samples_dropped),
.loss_latched(loss_latched_rf), .sample_activity_pulse(sample_activity_rf)
);
reset_sync u_rf_reset (
.clk(rf_fifo_clk), .arst(!pll_locked), .srst(rf_rst)
);
async_fifo_gray #(.DATA_WIDTH(69), .DEPTH(FIFO_DEPTH)) u_sample_fifo (
.w_clk(rf_fifo_clk), .w_rst(rf_rst), .w_en(fifo_w_en), .w_data(fifo_w_data),
.w_full(fifo_full), .w_level(fifo_w_level),
.overflow_attempt_count(fifo_overflow_attempts),
.r_clk(eth_clk), .r_rst(eth_rst), .r_en(fifo_rd_en), .r_data(fifo_rd_data),
.r_valid(fifo_rd_valid), .r_empty(fifo_empty), .r_level(fifo_r_level),
.underflow_attempt_count(fifo_underflow_attempts)
);
gray_counter_sync #(.WIDTH(64)) u_drop_sync (
.src_clk(rf_fifo_clk), .src_rst(rf_rst), .src_binary(samples_dropped),
.dst_clk(eth_clk), .dst_rst(eth_rst), .dst_binary(dropped_samples_eth)
);
pulse_sync u_sample_pulse_sync (
.src_clk(rf_fifo_clk), .src_rst(rf_rst), .src_pulse(sample_activity_rf),
.dst_clk(eth_clk), .dst_rst(eth_rst), .dst_pulse(sample_activity_eth)
);
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
loss_sync1 <= 1'b0;
loss_sync2 <= 1'b0;
end else begin
loss_sync1 <= loss_latched_rf;
loss_sync2 <= loss_sync1;
end
end
phy_reset_ctrl #(.CLK_HZ(125_000_000), .RESET_LOW_MS(10)) u_phy_reset (
.clk(eth_clk), .rst(eth_rst), .phy_reset_n(eth_phyrst_n),
.reset_complete(phy_reset_complete)
);
phy_init_rtl8211f u_phy_init (
.clk(eth_clk), .rst(eth_rst), .phy_reset_complete(phy_reset_complete),
.mdio_start(mdio_start), .mdio_write(mdio_write),
.mdio_phy_address(mdio_phy_address),
.mdio_register_address(mdio_register_address), .mdio_write_data(mdio_write_data),
.mdio_busy(mdio_busy), .mdio_done(mdio_done), .mdio_read_data(mdio_read_data),
.initialization_complete(phy_init_complete), .link_ready(link_ready),
.link_down_events(link_down_events)
);
mdio_master #(.MDC_DIV(25)) u_mdio (
.clk(eth_clk), .rst(eth_rst), .start(mdio_start),
.command_write(mdio_write), .phy_address(mdio_phy_address),
.register_address(mdio_register_address), .write_data(mdio_write_data),
.busy(mdio_busy), .done(mdio_done), .read_data(mdio_read_data),
.read_valid(mdio_read_valid), .mdc(eth_mdc),
.mdio_i(eth_mdio_IN), .mdio_o(eth_mdio_OUT), .mdio_oe(eth_mdio_OE)
);
ethernet_packetizer #(
.FIFO_DEPTH(FIFO_DEPTH), .FULL_SAMPLES(2936),
.EARLIER_SAMPLE_HIGH_NIBBLE(EARLIER_SAMPLE_HIGH_NIBBLE)
) u_packetizer (
.clk(eth_clk), .rst(eth_rst), .link_ready(link_ready),
.dropped_samples_low(dropped_samples_eth[31:0]),
.fifo_empty(fifo_empty), .fifo_level(fifo_r_level),
.fifo_rd_en(fifo_rd_en), .fifo_rd_data(fifo_rd_data),
.fifo_rd_valid(fifo_rd_valid), .mac_frame_start(mac_frame_start),
.mac_payload_length(mac_payload_length), .mac_payload_data(mac_payload_data),
.mac_payload_valid(mac_payload_valid), .mac_payload_ready(mac_payload_ready),
.mac_busy(mac_busy), .mac_frame_done(mac_frame_done),
.packet_sequence(packet_sequence), .frame_activity_pulse(frame_activity_pulse)
);
ethernet_tx_mac u_mac (
.clk(eth_clk), .rst(eth_rst), .frame_start(mac_frame_start),
.payload_length(mac_payload_length), .payload_data(mac_payload_data),
.payload_valid(mac_payload_valid), .payload_ready(mac_payload_ready),
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.busy(mac_busy), .frame_done(mac_frame_done),
.frames_transmitted(frames_transmitted),
.payload_bytes_transmitted(payload_bytes_transmitted)
);
rgmii_tx_efinix u_rgmii_tx (
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.eth_txd_HI(eth_txd_HI), .eth_txd_LO(eth_txd_LO),
.eth_txctl_HI(eth_txctl_HI), .eth_txctl_LO(eth_txctl_LO),
.eth_txc_HI(eth_txc_HI), .eth_txc_LO(eth_txc_LO)
);
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
sample_led_stretch <= 24'd0;
frame_activity_divider <= 13'd0;
frame_activity_toggle <= 1'b0;
end else begin
if (sample_activity_eth) sample_led_stretch <= 24'hffffff;
else if (sample_led_stretch != 24'd0)
sample_led_stretch <= sample_led_stretch - 24'd1;
if (frame_activity_pulse) begin
if (frame_activity_divider == 13'd4095) begin
frame_activity_divider <= 13'd0;
frame_activity_toggle <= !frame_activity_toggle;
end else begin
frame_activity_divider <= frame_activity_divider + 13'd1;
end
end
end
end
always_comb begin
// Ti375 C529 has five verified user LEDs (LED9 through LED13).
user_led[0] = (pll_locked && !eth_rst) ||
(!pll_locked && (pll_dummy_clk ^ eth_clk90));
user_led[1] = link_ready;
// The pre-lock term keeps both physical DDIO capture edges in the
// implemented design even when CAPTURE_RISING is a constant parameter.
user_led[2] = (sample_led_stretch != 24'd0) ||
(!pll_locked && (^rf_data_pmod_LO));
user_led[3] = !fifo_empty && (frame_activity_toggle || !link_ready);
user_led[4] = loss_sync2;
end
endmodule
`default_nettype wire