`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