Implement generic MAX2771 raw Ethernet recorder
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user