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
+122
View File
@@ -0,0 +1,122 @@
`default_nettype none
// Handwritten dual-clock FIFO. Only Gray pointers cross clock domains. Memory
// writes occur in w_clk and the read data is registered in r_clk, allowing BRAM
// inference without a combinational CDC path.
module async_fifo_gray #(
parameter int unsigned DATA_WIDTH = 69,
parameter int unsigned DEPTH = 16384
) (
input wire logic w_clk,
input wire logic w_rst,
input wire logic w_en,
input wire logic [DATA_WIDTH-1:0] w_data,
output logic w_full,
output logic [$clog2(DEPTH):0] w_level,
output logic [63:0] overflow_attempt_count,
input wire logic r_clk,
input wire logic r_rst,
input wire logic r_en,
output logic [DATA_WIDTH-1:0] r_data,
output logic r_valid,
output logic r_empty,
output logic [$clog2(DEPTH):0] r_level,
output logic [63:0] underflow_attempt_count
);
localparam int unsigned ADDR_WIDTH = $clog2(DEPTH);
localparam int unsigned PTR_WIDTH = ADDR_WIDTH + 1;
(* ram_style = "block" *) logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];
logic [PTR_WIDTH-1:0] wbin, wgray, rbin, rgray;
logic [PTR_WIDTH-1:0] wbin_next, wgray_next;
logic [PTR_WIDTH-1:0] rbin_next, rgray_next;
(* ASYNC_REG = "TRUE" *) logic [PTR_WIDTH-1:0] rgray_wsync1, rgray_wsync2;
(* ASYNC_REG = "TRUE" *) logic [PTR_WIDTH-1:0] wgray_rsync1, wgray_rsync2;
logic [PTR_WIDTH-1:0] rbin_wsync, wbin_rsync;
logic [DATA_WIDTH-1:0] r_mem_data;
logic w_push, r_pop;
integer i;
initial begin
if (DEPTH < 4 || (DEPTH & (DEPTH-1)) != 0)
$error("async_fifo_gray DEPTH must be a power of two >= 4");
if (DATA_WIDTH == 0) $error("async_fifo_gray DATA_WIDTH must be nonzero");
end
assign w_push = w_en && !w_full;
assign r_pop = r_en && !r_empty;
assign r_data = r_valid ? r_mem_data : {DATA_WIDTH{1'b0}};
assign wbin_next = wbin + {{(PTR_WIDTH-1){1'b0}}, w_push};
assign rbin_next = rbin + {{(PTR_WIDTH-1){1'b0}}, r_pop};
assign wgray_next = (wbin_next >> 1) ^ wbin_next;
assign rgray_next = (rbin_next >> 1) ^ rbin_next;
always_comb begin
rbin_wsync[PTR_WIDTH-1] = rgray_wsync2[PTR_WIDTH-1];
wbin_rsync[PTR_WIDTH-1] = wgray_rsync2[PTR_WIDTH-1];
for (i = PTR_WIDTH-2; i >= 0; i = i - 1) begin
rbin_wsync[i] = rbin_wsync[i+1] ^ rgray_wsync2[i];
wbin_rsync[i] = wbin_rsync[i+1] ^ wgray_rsync2[i];
end
end
always_comb begin
w_level = wbin - rbin_wsync;
r_level = wbin_rsync - rbin;
end
always_ff @(posedge w_clk or posedge w_rst) begin
if (w_rst) begin
wbin <= '0;
wgray <= '0;
w_full <= 1'b0;
rgray_wsync1 <= '0;
rgray_wsync2 <= '0;
overflow_attempt_count <= 64'd0;
end else begin
rgray_wsync1 <= rgray;
rgray_wsync2 <= rgray_wsync1;
if (w_en && w_full)
overflow_attempt_count <= overflow_attempt_count + 64'd1;
wbin <= wbin_next;
wgray <= wgray_next;
// Invert the two most-significant synchronized read-pointer bits.
w_full <= (wgray_next == {~rgray_wsync2[PTR_WIDTH-1:PTR_WIDTH-2],
rgray_wsync2[PTR_WIDTH-3:0]});
end
end
always_ff @(posedge r_clk or posedge r_rst) begin
if (r_rst) begin
rbin <= '0;
rgray <= '0;
r_valid <= 1'b0;
r_empty <= 1'b1;
wgray_rsync1 <= '0;
wgray_rsync2 <= '0;
underflow_attempt_count <= 64'd0;
end else begin
wgray_rsync1 <= wgray;
wgray_rsync2 <= wgray_rsync1;
r_valid <= r_pop;
if (r_en && r_empty)
underflow_attempt_count <= underflow_attempt_count + 64'd1;
rbin <= rbin_next;
rgray <= rgray_next;
r_empty <= (rgray_next == wgray_rsync2);
end
end
// Keep memory access out of the asynchronously-reset pointer processes.
// This is the supported true-dual-clock block-RAM inference template.
always_ff @(posedge w_clk) begin
if (w_push) mem[wbin[ADDR_WIDTH-1:0]] <= w_data;
end
always_ff @(posedge r_clk) begin
if (r_pop) r_mem_data <= mem[rbin[ADDR_WIDTH-1:0]];
end
endmodule
`default_nettype wire
+23
View File
@@ -0,0 +1,23 @@
`default_nettype none
// One reflected Ethernet CRC-32 byte update. Initialize crc_in to 0xffffffff,
// update over destination MAC through padding, then transmit ~crc in low-byte
// first order. Polynomial x^32+x^26+x^23+...+1 is 0xedb88320 reflected.
module ethernet_crc32 (
input wire logic [31:0] crc_in,
input wire logic [7:0] data_in,
output logic [31:0] crc_out
);
integer i;
logic [31:0] c;
always_comb begin
c = crc_in;
for (i = 0; i < 8; i = i + 1) begin
if (c[0] ^ data_in[i]) c = (c >> 1) ^ 32'hedb88320;
else c = (c >> 1);
end
crc_out = c;
end
endmodule
`default_nettype wire
+233
View File
@@ -0,0 +1,233 @@
`default_nettype none
module ethernet_packetizer #(
parameter int unsigned FIFO_DEPTH = 16384,
parameter int unsigned FULL_SAMPLES = 2936,
parameter bit EARLIER_SAMPLE_HIGH_NIBBLE = 1'b1
) (
input wire logic clk,
input wire logic rst,
input wire logic link_ready,
input wire logic [31:0] dropped_samples_low,
input wire logic fifo_empty,
input wire logic [$clog2(FIFO_DEPTH):0] fifo_level,
output logic fifo_rd_en,
input wire logic [68:0] fifo_rd_data,
input wire logic fifo_rd_valid,
output logic mac_frame_start,
output logic [10:0] mac_payload_length,
output logic [7:0] mac_payload_data,
output logic mac_payload_valid,
input wire logic mac_payload_ready,
input wire logic mac_busy,
input wire logic mac_frame_done,
output logic [31:0] packet_sequence,
output logic frame_activity_pulse
);
localparam int unsigned MAX_DATA_BYTES = (FULL_SAMPLES + 1) / 2;
localparam int unsigned SAMPLE_W = $clog2(FULL_SAMPLES + 1);
localparam logic [SAMPLE_W-1:0] FULL_SAMPLES_W = FULL_SAMPLES;
typedef enum logic [2:0] {P_IDLE, P_LOAD, P_READY, P_STREAM, P_WAIT_DONE} pstate_t;
pstate_t state;
(* ram_style = "distributed" *) logic [7:0] sample_bytes [0:MAX_DATA_BYTES-1];
logic [SAMPLE_W-1:0] load_sample_count;
logic [15:0] frame_sample_count;
logic [63:0] frame_first_index;
logic [63:0] expected_index;
logic [31:0] frame_drop_snapshot;
logic [31:0] last_reported_drop;
logic [7:0] frame_flags;
logic [10:0] stream_position;
logic waiting_read;
logic hold_valid;
logic [68:0] hold_entry;
logic recovery_in_frame;
logic link_d;
logic link_packet_pending;
wire logic current_valid = hold_valid || fifo_rd_valid;
wire logic [68:0] current_entry = hold_valid ? hold_entry : fifo_rd_data;
wire logic current_discontinuity = current_entry[68];
wire logic [63:0] current_index = current_entry[67:4];
wire logic [3:0] current_sample = current_entry[3:0];
wire logic [SAMPLE_W-1:0] samples_with_current = load_sample_count + {{(SAMPLE_W-1){1'b0}},1'b1};
initial begin
if (FULL_SAMPLES < 2 || FULL_SAMPLES > 2936)
$error("FULL_SAMPLES must be in the range 2..2936");
if ((FULL_SAMPLES & 1) != 0)
$error("Normal FULL_SAMPLES must be even to preserve byte alignment");
end
function automatic logic [7:0] header_byte(input logic [5:0] offset);
begin
case (offset)
6'd0: header_byte = 8'h4d; // M
6'd1: header_byte = 8'h58; // X
6'd2: header_byte = 8'h32; // 2
6'd3: header_byte = 8'h37; // 7
6'd4: header_byte = 8'd1;
6'd5: header_byte = frame_flags;
6'd6: header_byte = 8'h00;
6'd7: header_byte = 8'h20;
6'd8: header_byte = packet_sequence[31:24];
6'd9: header_byte = packet_sequence[23:16];
6'd10: header_byte = packet_sequence[15:8];
6'd11: header_byte = packet_sequence[7:0];
6'd12: header_byte = frame_first_index[63:56];
6'd13: header_byte = frame_first_index[55:48];
6'd14: header_byte = frame_first_index[47:40];
6'd15: header_byte = frame_first_index[39:32];
6'd16: header_byte = frame_first_index[31:24];
6'd17: header_byte = frame_first_index[23:16];
6'd18: header_byte = frame_first_index[15:8];
6'd19: header_byte = frame_first_index[7:0];
6'd20: header_byte = frame_sample_count[15:8];
6'd21: header_byte = frame_sample_count[7:0];
6'd22: header_byte = 8'd4;
6'd23: header_byte = EARLIER_SAMPLE_HIGH_NIBBLE ? 8'd1 : 8'd2;
6'd24: header_byte = frame_drop_snapshot[31:24];
6'd25: header_byte = frame_drop_snapshot[23:16];
6'd26: header_byte = frame_drop_snapshot[15:8];
6'd27: header_byte = frame_drop_snapshot[7:0];
default: header_byte = 8'h00;
endcase
end
endfunction
always_comb begin
mac_payload_valid = (state == P_STREAM);
if (stream_position < 11'd32)
mac_payload_data = header_byte(stream_position[5:0]);
else
mac_payload_data = sample_bytes[stream_position - 11'd32];
end
always_ff @(posedge clk) begin
if (rst) begin
state <= P_IDLE;
fifo_rd_en <= 1'b0;
mac_frame_start <= 1'b0;
mac_payload_length <= 11'd0;
packet_sequence <= 32'd0;
frame_activity_pulse <= 1'b0;
load_sample_count <= '0;
frame_sample_count <= '0;
frame_first_index <= 64'd0;
expected_index <= 64'd0;
frame_drop_snapshot <= 32'd0;
last_reported_drop <= 32'd0;
frame_flags <= 8'd0;
stream_position <= 11'd0;
waiting_read <= 1'b0;
hold_valid <= 1'b0;
hold_entry <= 69'd0;
recovery_in_frame <= 1'b0;
link_d <= 1'b0;
link_packet_pending <= 1'b1;
end else begin
fifo_rd_en <= 1'b0;
mac_frame_start <= 1'b0;
frame_activity_pulse <= 1'b0;
link_d <= link_ready;
if (link_ready && !link_d) link_packet_pending <= 1'b1;
case (state)
P_IDLE: begin
waiting_read <= 1'b0;
load_sample_count <= '0;
recovery_in_frame <= 1'b0;
// A held boundary sample counts toward the next full frame.
if (link_ready && ((hold_valid && fifo_level >= FULL_SAMPLES-1) ||
(!hold_valid && fifo_level >= FULL_SAMPLES))) begin
state <= P_LOAD;
end
end
P_LOAD: begin
if (current_valid) begin
waiting_read <= 1'b0;
if (hold_valid) hold_valid <= 1'b0;
if ((load_sample_count != 0) &&
(current_discontinuity || current_index != expected_index)) begin
// Preserve the first post-gap sample for the next frame.
hold_entry <= {1'b0, current_entry[67:0]};
hold_valid <= 1'b1;
frame_sample_count <= {{(16-SAMPLE_W){1'b0}}, load_sample_count};
mac_payload_length <= 11'd32 +
((load_sample_count + {{(SAMPLE_W-1){1'b0}},1'b1}) >> 1);
recovery_in_frame <= 1'b1;
state <= P_READY;
end else begin
if (load_sample_count == 0) begin
frame_first_index <= current_index;
recovery_in_frame <= current_discontinuity;
end
expected_index <= current_index + 64'd1;
if (!load_sample_count[0]) begin
if (EARLIER_SAMPLE_HIGH_NIBBLE)
sample_bytes[load_sample_count >> 1] <= {current_sample, 4'h0};
else
sample_bytes[load_sample_count >> 1] <= {4'h0, current_sample};
end else begin
if (EARLIER_SAMPLE_HIGH_NIBBLE)
sample_bytes[load_sample_count >> 1][3:0] <= current_sample;
else
sample_bytes[load_sample_count >> 1][7:4] <= current_sample;
end
load_sample_count <= samples_with_current;
if (samples_with_current == FULL_SAMPLES_W) begin
frame_sample_count <= {{(16-SAMPLE_W){1'b0}}, samples_with_current};
mac_payload_length <= 11'd32 + (FULL_SAMPLES / 2);
state <= P_READY;
end
end
end else if (!waiting_read && !fifo_empty) begin
fifo_rd_en <= 1'b1;
waiting_read <= 1'b1;
end
end
P_READY: begin
if (link_ready && !mac_busy) begin
frame_drop_snapshot <= dropped_samples_low;
frame_flags <= {5'd0,
link_packet_pending,
(packet_sequence == 32'd0),
(recovery_in_frame || dropped_samples_low != last_reported_drop)};
last_reported_drop <= dropped_samples_low;
link_packet_pending <= 1'b0;
stream_position <= 11'd0;
mac_frame_start <= 1'b1;
frame_activity_pulse <= 1'b1;
state <= P_STREAM;
end
end
P_STREAM: begin
if (mac_payload_ready && mac_payload_valid) begin
if (stream_position + 11'd1 == mac_payload_length)
state <= P_WAIT_DONE;
else
stream_position <= stream_position + 11'd1;
end
end
P_WAIT_DONE: begin
if (mac_frame_done) begin
packet_sequence <= packet_sequence + 32'd1;
state <= P_IDLE;
end
end
default: state <= P_IDLE;
endcase
end
end
endmodule
`default_nettype wire
+199
View File
@@ -0,0 +1,199 @@
`default_nettype none
module ethernet_tx_mac #(
parameter logic [47:0] DEST_MAC = 48'hffffffffffff,
parameter logic [47:0] SOURCE_MAC = 48'h022771000001,
parameter logic [15:0] ETHERTYPE = 16'h88b5
) (
input wire logic clk,
input wire logic rst,
input wire logic frame_start,
input wire logic [10:0] payload_length,
input wire logic [7:0] payload_data,
input wire logic payload_valid,
output logic payload_ready,
output logic [7:0] gmii_txd,
output logic gmii_tx_en,
output logic gmii_tx_er,
output logic busy,
output logic frame_done,
output logic [63:0] frames_transmitted,
output logic [63:0] payload_bytes_transmitted
);
typedef enum logic [3:0] {
S_IDLE, S_PREAMBLE, S_SFD, S_DEST, S_SOURCE, S_TYPE,
S_PAYLOAD, S_PAD, S_FCS, S_IFG
} state_t;
state_t state;
logic [3:0] byte_count;
logic [10:0] payload_len_q;
logic [10:0] payload_count;
logic [5:0] pad_remaining;
logic [3:0] ifg_count;
logic [31:0] crc_q;
logic [31:0] crc_next;
logic [31:0] fcs_q;
logic [15:0] requested_body_length;
ethernet_crc32 u_crc_next (
.crc_in(crc_q), .data_in(gmii_txd), .crc_out(crc_next)
);
always_comb begin
gmii_txd = 8'h00;
gmii_tx_en = 1'b0;
gmii_tx_er = 1'b0;
payload_ready = 1'b0;
busy = (state != S_IDLE);
case (state)
S_PREAMBLE: begin gmii_tx_en = 1'b1; gmii_txd = 8'h55; end
S_SFD: begin gmii_tx_en = 1'b1; gmii_txd = 8'hd5; end
S_DEST: begin
gmii_tx_en = 1'b1;
case (byte_count)
4'd0: gmii_txd = DEST_MAC[47:40];
4'd1: gmii_txd = DEST_MAC[39:32];
4'd2: gmii_txd = DEST_MAC[31:24];
4'd3: gmii_txd = DEST_MAC[23:16];
4'd4: gmii_txd = DEST_MAC[15:8];
default: gmii_txd = DEST_MAC[7:0];
endcase
end
S_SOURCE: begin
gmii_tx_en = 1'b1;
case (byte_count)
4'd0: gmii_txd = SOURCE_MAC[47:40];
4'd1: gmii_txd = SOURCE_MAC[39:32];
4'd2: gmii_txd = SOURCE_MAC[31:24];
4'd3: gmii_txd = SOURCE_MAC[23:16];
4'd4: gmii_txd = SOURCE_MAC[15:8];
default: gmii_txd = SOURCE_MAC[7:0];
endcase
end
S_TYPE: begin
gmii_tx_en = 1'b1;
gmii_txd = (byte_count == 4'd0) ? ETHERTYPE[15:8] : ETHERTYPE[7:0];
end
S_PAYLOAD: begin
gmii_tx_en = 1'b1;
payload_ready = 1'b1;
gmii_txd = payload_data;
end
S_PAD: begin gmii_tx_en = 1'b1; gmii_txd = 8'h00; end
S_FCS: begin
gmii_tx_en = 1'b1;
case (byte_count)
4'd0: gmii_txd = fcs_q[7:0];
4'd1: gmii_txd = fcs_q[15:8];
4'd2: gmii_txd = fcs_q[23:16];
default: gmii_txd = fcs_q[31:24];
endcase
end
default: begin end
endcase
end
always_comb requested_body_length = 16'd14 + {5'd0, payload_length};
always_ff @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
byte_count <= 4'd0;
payload_len_q <= 11'd0;
payload_count <= 11'd0;
pad_remaining <= 6'd0;
ifg_count <= 4'd0;
crc_q <= 32'hffffffff;
fcs_q <= 32'd0;
frame_done <= 1'b0;
frames_transmitted <= 64'd0;
payload_bytes_transmitted <= 64'd0;
end else begin
frame_done <= 1'b0;
case (state)
S_IDLE: begin
if (frame_start && payload_length <= 11'd1500) begin
payload_len_q <= payload_length;
payload_count <= 11'd0;
byte_count <= 4'd0;
crc_q <= 32'hffffffff;
if (requested_body_length < 16'd60)
pad_remaining <= 16'd60 - requested_body_length;
else
pad_remaining <= 6'd0;
state <= S_PREAMBLE;
end
end
S_PREAMBLE: begin
if (byte_count == 4'd6) begin byte_count <= 4'd0; state <= S_SFD; end
else byte_count <= byte_count + 4'd1;
end
S_SFD: begin byte_count <= 4'd0; state <= S_DEST; end
S_DEST: begin
crc_q <= crc_next;
if (byte_count == 4'd5) begin byte_count <= 4'd0; state <= S_SOURCE; end
else byte_count <= byte_count + 4'd1;
end
S_SOURCE: begin
crc_q <= crc_next;
if (byte_count == 4'd5) begin byte_count <= 4'd0; state <= S_TYPE; end
else byte_count <= byte_count + 4'd1;
end
S_TYPE: begin
crc_q <= crc_next;
if (byte_count == 4'd1) begin
byte_count <= 4'd0;
if (payload_len_q != 11'd0) state <= S_PAYLOAD;
else if (pad_remaining != 6'd0) state <= S_PAD;
else begin fcs_q <= ~crc_next; state <= S_FCS; end
end else byte_count <= 4'd1;
end
S_PAYLOAD: begin
if (payload_valid) begin
crc_q <= crc_next;
payload_count <= payload_count + 11'd1;
if (payload_count + 11'd1 == payload_len_q) begin
if (pad_remaining != 6'd0) state <= S_PAD;
else begin fcs_q <= ~crc_next; byte_count <= 4'd0; state <= S_FCS; end
end
end
end
S_PAD: begin
crc_q <= crc_next;
if (pad_remaining == 6'd1) begin
pad_remaining <= 6'd0;
fcs_q <= ~crc_next;
byte_count <= 4'd0;
state <= S_FCS;
end else pad_remaining <= pad_remaining - 6'd1;
end
S_FCS: begin
if (byte_count == 4'd3) begin byte_count <= 4'd0; ifg_count <= 4'd0; state <= S_IFG; end
else byte_count <= byte_count + 4'd1;
end
S_IFG: begin
if (ifg_count == 4'd11) begin
ifg_count <= 4'd0;
state <= S_IDLE;
frame_done <= 1'b1;
frames_transmitted <= frames_transmitted + 64'd1;
payload_bytes_transmitted <= payload_bytes_transmitted + {53'd0, payload_len_q};
end else ifg_count <= ifg_count + 4'd1;
end
default: state <= S_IDLE;
endcase
end
end
`ifndef SYNTHESIS
// The packetizer preassembles every frame, so this contract must hold and
// hardware never encodes TX_ER on RGMII.
always_ff @(posedge clk) begin
if (!rst && state == S_PAYLOAD && !payload_valid)
$error("ethernet_tx_mac payload stream underflow");
end
`endif
endmodule
`default_nettype wire
+42
View File
@@ -0,0 +1,42 @@
`default_nettype none
// Safe observation of a monotonic source-domain counter. The Gray encoding
// changes one bit per increment; two destination flops isolate metastability.
module gray_counter_sync #(
parameter int unsigned WIDTH = 64
) (
input wire logic src_clk,
input wire logic src_rst,
input wire logic [WIDTH-1:0] src_binary,
input wire logic dst_clk,
input wire logic dst_rst,
output logic [WIDTH-1:0] dst_binary
);
logic [WIDTH-1:0] src_gray;
(* ASYNC_REG = "TRUE" *) logic [WIDTH-1:0] gray_sync1;
(* ASYNC_REG = "TRUE" *) logic [WIDTH-1:0] gray_sync2;
integer i;
always_ff @(posedge src_clk) begin
if (src_rst) src_gray <= '0;
else src_gray <= (src_binary >> 1) ^ src_binary;
end
always_ff @(posedge dst_clk) begin
if (dst_rst) begin
gray_sync1 <= '0;
gray_sync2 <= '0;
end else begin
gray_sync1 <= src_gray;
gray_sync2 <= gray_sync1;
end
end
always_comb begin
dst_binary[WIDTH-1] = gray_sync2[WIDTH-1];
for (i = WIDTH-2; i >= 0; i = i - 1)
dst_binary[i] = dst_binary[i+1] ^ gray_sync2[i];
end
endmodule
`default_nettype wire
+69
View File
@@ -0,0 +1,69 @@
`default_nettype none
module max2771_capture #(
parameter bit CAPTURE_RISING = 1'b1
) (
input wire logic rf_clk_pmod,
input wire logic rst,
input wire logic [3:0] rf_data_pmod,
input wire logic fifo_full,
output wire logic fifo_w_clk,
output wire logic fifo_w_en,
output wire logic [68:0] fifo_w_data,
output logic [63:0] samples_seen,
output logic [63:0] samples_captured,
output logic [63:0] samples_dropped,
output logic loss_latched,
output logic sample_activity_pulse
);
// The inversion selects the physical falling edge while retaining a single
// positive-edge sequential implementation. Vivado maps clock inversion onto
// the clocking resource; fifo_w_clk clocks both capture bookkeeping and FIFO.
assign fifo_w_clk = CAPTURE_RISING ? rf_clk_pmod : ~rf_clk_pmod;
(* IOB = "TRUE" *) logic [3:0] sample_iob;
logic [63:0] sample_index_iob;
logic sample_valid;
logic discontinuity_pending;
assign fifo_w_en = sample_valid;
// Entry format: loss immediately before this sample, absolute index, nibble.
assign fifo_w_data = {discontinuity_pending, sample_index_iob, sample_iob};
always_ff @(posedge fifo_w_clk or posedge rst) begin
if (rst) begin
sample_iob <= 4'd0;
sample_index_iob <= 64'd0;
sample_valid <= 1'b0;
discontinuity_pending <= 1'b0;
samples_seen <= 64'd0;
samples_captured <= 64'd0;
samples_dropped <= 64'd0;
loss_latched <= 1'b0;
sample_activity_pulse <= 1'b0;
end else begin
// IOB input register: no combinational data path reaches the FIFO.
sample_iob <= rf_data_pmod;
sample_index_iob <= samples_seen;
sample_valid <= 1'b1;
samples_seen <= samples_seen + 64'd1;
sample_activity_pulse <= 1'b0;
if (samples_seen[15:0] == 16'hffff)
sample_activity_pulse <= 1'b1;
if (sample_valid) begin
if (fifo_full) begin
samples_dropped <= samples_dropped + 64'd1;
discontinuity_pending <= 1'b1;
loss_latched <= 1'b1;
end else begin
samples_captured <= samples_captured + 64'd1;
discontinuity_pending <= 1'b0;
end
end
end
end
endmodule
`default_nettype wire
+97
View File
@@ -0,0 +1,97 @@
`default_nettype none
// IEEE 802.3 Clause-22 management interface. MDC is stopped low between
// commands. A read releases MDIO for both turnaround bits and all data bits.
module mdio_master #(
parameter int unsigned MDC_DIV = 25
) (
input wire logic clk,
input wire logic rst,
input wire logic start,
input wire logic command_write,
input wire logic [4:0] phy_address,
input wire logic [4:0] register_address,
input wire logic [15:0] write_data,
output logic busy,
output logic done,
output logic [15:0] read_data,
output logic read_valid,
output logic mdc,
input wire logic mdio_i,
output logic mdio_o,
output logic mdio_oe
);
localparam int unsigned DIV_W = (MDC_DIV < 2) ? 1 : $clog2(MDC_DIV);
logic [DIV_W-1:0] div_count;
logic [5:0] bit_index;
logic [63:0] frame_bits;
logic [15:0] read_shift;
logic read_command_q;
initial begin
if (MDC_DIV < 2) $error("MDC_DIV must be at least two");
end
always_comb begin
mdio_o = frame_bits[bit_index];
mdio_oe = busy;
if (busy && read_command_q && bit_index <= 6'd17)
mdio_oe = 1'b0;
end
always_ff @(posedge clk) begin
if (rst) begin
busy <= 1'b0;
done <= 1'b0;
read_valid <= 1'b0;
read_data <= 16'd0;
read_shift <= 16'd0;
mdc <= 1'b0;
div_count <= '0;
bit_index <= 6'd63;
frame_bits <= 64'hffffffff_6000_0000;
read_command_q <= 1'b0;
end else begin
done <= 1'b0;
read_valid <= 1'b0;
if (!busy) begin
mdc <= 1'b0;
div_count <= '0;
if (start) begin
busy <= 1'b1;
bit_index <= 6'd63;
read_shift <= 16'd0;
read_command_q <= !command_write;
frame_bits[63:32] <= 32'hffffffff;
frame_bits[31:30] <= 2'b01;
frame_bits[29:28] <= command_write ? 2'b01 : 2'b10;
frame_bits[27:23] <= phy_address;
frame_bits[22:18] <= register_address;
frame_bits[17:16] <= 2'b10;
frame_bits[15:0] <= write_data;
end
end else if (div_count == MDC_DIV-1) begin
div_count <= '0;
if (!mdc) begin
// Rising MDC: PHY sources read data; master samples it.
mdc <= 1'b1;
if (read_command_q && bit_index <= 6'd15)
read_shift <= {read_shift[14:0], mdio_i};
end else begin
// Falling MDC: move to the next bit or finish the command.
mdc <= 1'b0;
if (bit_index == 6'd0) begin
busy <= 1'b0;
done <= 1'b1;
if (read_command_q) begin
read_data <= read_shift;
read_valid <= 1'b1;
end
end else bit_index <= bit_index - 6'd1;
end
end else div_count <= div_count + {{(DIV_W-1){1'b0}},1'b1};
end
end
endmodule
`default_nettype wire
+33
View File
@@ -0,0 +1,33 @@
`default_nettype none
module phy_reset_ctrl #(
parameter int unsigned CLK_HZ = 125_000_000,
parameter int unsigned RESET_LOW_MS = 10
) (
input wire logic clk,
input wire logic rst,
output logic phy_reset_n,
output logic reset_complete
);
localparam longint unsigned RESET_CYCLES = (CLK_HZ / 1000) * RESET_LOW_MS;
localparam int unsigned COUNT_W = (RESET_CYCLES < 2) ? 1 : $clog2(RESET_CYCLES + 1);
logic [COUNT_W-1:0] count;
always_ff @(posedge clk) begin
if (rst) begin
phy_reset_n <= 1'b0;
reset_complete <= 1'b0;
count <= '0;
end else if (!reset_complete) begin
phy_reset_n <= 1'b0;
if (count == RESET_CYCLES-1) begin
phy_reset_n <= 1'b1;
reset_complete <= 1'b1;
end else count <= count + {{(COUNT_W-1){1'b0}},1'b1};
end else begin
phy_reset_n <= 1'b1;
end
end
endmodule
`default_nettype wire
+38
View File
@@ -0,0 +1,38 @@
`default_nettype none
// Toggle-based pulse crossing. Pulses must be separated by at least two
// destination clock cycles. Assertion is asynchronous only at the synchronizer.
module pulse_sync (
input wire logic src_clk,
input wire logic src_rst,
input wire logic src_pulse,
input wire logic dst_clk,
input wire logic dst_rst,
output logic dst_pulse
);
logic src_toggle;
(* ASYNC_REG = "TRUE" *) logic dst_sync1;
(* ASYNC_REG = "TRUE" *) logic dst_sync2;
logic dst_sync2_d;
always_ff @(posedge src_clk) begin
if (src_rst) src_toggle <= 1'b0;
else if (src_pulse) src_toggle <= ~src_toggle;
end
always_ff @(posedge dst_clk) begin
if (dst_rst) begin
dst_sync1 <= 1'b0;
dst_sync2 <= 1'b0;
dst_sync2_d <= 1'b0;
dst_pulse <= 1'b0;
end else begin
dst_sync1 <= src_toggle;
dst_sync2 <= dst_sync1;
dst_sync2_d <= dst_sync2;
dst_pulse <= dst_sync2 ^ dst_sync2_d;
end
end
endmodule
`default_nettype wire
+27
View File
@@ -0,0 +1,27 @@
`default_nettype none
module reset_sync #(
parameter int unsigned STAGES = 2
) (
input wire logic clk,
input wire logic arst,
output logic srst
);
(* ASYNC_REG = "TRUE" *) logic [STAGES-1:0] sync_ff;
initial begin
if (STAGES < 2) $error("reset_sync STAGES must be at least two");
end
always_ff @(posedge clk or posedge arst) begin
if (arst) begin
sync_ff <= '1;
end else begin
sync_ff <= {sync_ff[STAGES-2:0], 1'b0};
end
end
assign srst = sync_ff[STAGES-1];
endmodule
`default_nettype wire