`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