// ethernet MAC with axi stream IO with UDP `timescale 1 ns / 1 ns module axis_mac ( input rst_n, input gmii_tx_clk, input gmii_rx_clk, input gmii_rx_dv, input [7:0] gmii_rxd, output reg gmii_tx_en, output reg [7:0] gmii_txd, // AXI-stream RX output (clock domain = gmii_rx_clk) (* MARK_DEBUG="true" *)output reg [7:0] m_axis_rx_tdata, (* MARK_DEBUG="true" *)output reg m_axis_rx_tvalid, (* MARK_DEBUG="true" *)input wire m_axis_rx_tready, (* MARK_DEBUG="true" *)output reg m_axis_rx_tlast, (* MARK_DEBUG="true" *)output [15:0] udp_rec_data_length, // tx part (* MARK_DEBUG="true" *)input wire send_req, input wire [15:0] data_length, (* MARK_DEBUG="true" *)output reg req_ready, (* MARK_DEBUG="true" *)input wire [7:0] s_axis_tx_tdata, (* MARK_DEBUG="true" *)input wire s_axis_tx_tvalid, (* MARK_DEBUG="true" *)output reg s_axis_tx_tready, (* MARK_DEBUG="true" *)input wire s_axis_tx_tlast ); // ---------------------------------------------------------------- // GMII RX input registering // ---------------------------------------------------------------- reg gmii_rx_dv_d0; reg [7:0] gmii_rxd_d0; always @(posedge gmii_rx_clk or negedge rst_n) begin if (!rst_n) begin gmii_rx_dv_d0 <= 1'b0; gmii_rxd_d0 <= 8'd0; end else begin gmii_rx_dv_d0 <= gmii_rx_dv; gmii_rxd_d0 <= gmii_rxd; end end // ---------------------------------------------------------------- // TX path from mac_top // ---------------------------------------------------------------- wire gmii_tx_en_tmp; wire [7:0] gmii_txd_tmp; always @(posedge gmii_tx_clk or negedge rst_n) begin if (!rst_n) begin gmii_tx_en <= 1'b0; gmii_txd <= 8'd0; end else begin gmii_tx_en <= gmii_tx_en_tmp; gmii_txd <= gmii_txd_tmp; end end // TX signals reg [7:0] tx_ram_wr_data; reg tx_ram_wr_en; reg [15:0] udp_send_data_length; reg udp_tx_req; reg arp_request_req; wire mac_send_end; wire udp_ram_data_req; wire udp_tx_end; wire almost_full; (* MARK_DEBUG="true" *)wire arp_found; wire mac_not_exist; wire [15:0] udp_ram_data_count; // RX signals reg [10:0] udp_rec_ram_read_addr; wire [7:0] udp_rec_ram_rdata; wire [15:0] udp_rec_data_length; wire udp_rec_data_valid; mac_top mac_top0 ( .gmii_tx_clk (gmii_tx_clk), .gmii_rx_clk (gmii_rx_clk), .rst_n (rst_n), .source_mac_addr (48'h00_0a_35_01_fe_c0), .TTL (8'h80), .source_ip_addr (32'hc0a80002), // 192.168.0.2 .destination_ip_addr (32'hc0a80003), // 192.168.0.3 .udp_send_source_port (16'h1f90), // 8080 .udp_send_destination_port (16'h1f90), // 8080 .ram_wr_data (tx_ram_wr_data), .ram_wr_en (tx_ram_wr_en), .udp_ram_data_req (udp_ram_data_req), .udp_send_data_length (udp_send_data_length), .udp_tx_end (udp_tx_end), .almost_full (almost_full), .udp_tx_req (udp_tx_req), .arp_request_req (arp_request_req), .mac_send_end (mac_send_end), .mac_data_valid (gmii_tx_en_tmp), .mac_tx_data (gmii_txd_tmp), .rx_dv (gmii_rx_dv_d0), .mac_rx_datain (gmii_rxd_d0), .udp_rec_ram_rdata (udp_rec_ram_rdata), .udp_rec_ram_read_addr (udp_rec_ram_read_addr), .udp_rec_data_length (udp_rec_data_length), .udp_rec_data_valid (udp_rec_data_valid), .arp_found (arp_found), .mac_not_exist (mac_not_exist) ); // Detect "new packet ready" on udp_rec_data_valid rising edge reg udp_rec_data_valid_d0; always @(posedge gmii_rx_clk or negedge rst_n) begin if (!rst_n) udp_rec_data_valid_d0 <= 1'b0; else udp_rec_data_valid_d0 <= udp_rec_data_valid; end wire udp_pkt_done = udp_rec_data_valid & ~udp_rec_data_valid_d0; // ---------------------------------------------------------------- // RX RAM -> AXI-stream bridge // // Assumption: // udp_rec_data_length includes 8-byte UDP header, // so payload length = udp_rec_data_length - 8 // // This implementation is simple and safe: // - start on udp_pkt_done // - read bytes 0 .. payload_len-1 from RX RAM // - output them on AXIS // // ---------------------------------------------------------------- localparam RX_IDLE = 2'd0; localparam RX_NOTREADY = 2'd1; localparam RX_START = 2'd2; localparam RX_DATA = 2'd3; (* MARK_DEBUG="true" *) reg [1:0] rx_state; (* MARK_DEBUG="true" *) reg [15:0] rx_payload_len; (* MARK_DEBUG="true" *) reg [15:0] rx_index; always @(posedge gmii_rx_clk or negedge rst_n) begin if (!rst_n) begin rx_state <= RX_IDLE; rx_payload_len <= 16'd0; rx_index <= 16'd0; udp_rec_ram_read_addr <= 11'd0; m_axis_rx_tdata <= 8'd0; m_axis_rx_tvalid <= 1'b0; m_axis_rx_tlast <= 1'b0; end else begin case (rx_state) RX_IDLE: begin m_axis_rx_tvalid <= 1'b0; m_axis_rx_tlast <= 1'b0; rx_index <= 16'd0; udp_rec_ram_read_addr <= 11'd0; if (udp_pkt_done) begin // protect against pathological short values if (udp_rec_data_length > 16'd8) begin rx_payload_len <= udp_rec_data_length - 16'd8; udp_rec_ram_read_addr <= 11'd0; // issue read for byte 0 rx_state <= RX_NOTREADY; end else begin rx_payload_len <= 16'd0; rx_state <= RX_IDLE; end end end // one cycle for synchronous BRAM read latency RX_NOTREADY: begin m_axis_rx_tvalid <= 1'b0; m_axis_rx_tlast <= 1'b0; if (m_axis_rx_tready) rx_state <= RX_START; end RX_START: begin if (m_axis_rx_tready) begin // put current data // end of data? if (rx_index == (rx_payload_len - 1)) begin rx_state <= RX_IDLE; end else begin rx_state <= RX_DATA; end // always increment pointer rx_index <= rx_index + 1'b1; udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte end else begin rx_state <= RX_NOTREADY; end end RX_DATA: begin // hold valid until accepted if (!m_axis_rx_tready) begin // take a break while not ready m_axis_rx_tvalid <= m_axis_rx_tvalid; m_axis_rx_tlast <= m_axis_rx_tlast; rx_state <= RX_NOTREADY; // reset increment rx_index <= rx_index - 1'b1; udp_rec_ram_read_addr <= rx_index - 1'b1; end else begin // present current byte from RAM m_axis_rx_tdata <= udp_rec_ram_rdata; m_axis_rx_tvalid <= 1'b1; m_axis_rx_tlast <= (rx_index == (rx_payload_len)); if (rx_index == (rx_payload_len)) begin // last byte accepted immediately if ready=1, // otherwise valid/last remain asserted until ready rx_state <= RX_IDLE; end rx_index <= rx_index + 1'b1; udp_rec_ram_read_addr <= rx_index + 1'b1; // next byte end end default: begin rx_state <= RX_IDLE; m_axis_rx_tvalid <= 1'b0; m_axis_rx_tlast <= 1'b0; end endcase end end // ---------------------------------------------------------------- // TX FSM // Semantics: // - send_req/data_length form a packet send request // - udp_tx_req is held HIGH until udp_ram_data_req pulses // - udp_ram_data_req is a pulse - start feeding payload now to RAM // - AXIS ready is asserted only during payload write phase // ---------------------------------------------------------------- localparam TX_IDLE = 3'd0; localparam TX_ARP_REQ = 3'd1; localparam TX_ARP_SEND = 3'd2; localparam TX_WAIT_ARP = 3'd3; localparam TX_WAIT_RAM_REQ = 3'd4; localparam TX_STREAM = 3'd5; localparam TX_WAIT_DRAIN = 3'd6; (* MARK_DEBUG="true" *)reg [2:0] tx_state; assign arp_request_req = (tx_state == TX_ARP_REQ) ; reg [15:0] tx_req_len; reg [15:0] tx_bytes_written; reg [15:0] tx_release_threshold; reg tx_req_inflight; // register for long arp timeout, if no got no response reg [31:0] arp_delay; reg arp_cached; always @(posedge gmii_tx_clk or negedge rst_n) begin if (!rst_n) begin tx_state <= TX_IDLE; tx_ram_wr_data <= 8'd0; arp_cached <= 1'b0; tx_ram_wr_en <= 1'b0; udp_send_data_length <= 16'd0; udp_tx_req <= 1'b0; arp_delay <= 32'b0; s_axis_tx_tready <= 1'b0; req_ready <= 1'b0; tx_req_len <= 16'd0; tx_bytes_written <= 16'd0; tx_release_threshold <= 16'd0; tx_req_inflight <= 1'b0; end else begin // defaults tx_ram_wr_en <= 1'b0; case (tx_state) // Ready to accept a new packet request TX_IDLE: begin udp_tx_req <= 1'b0; s_axis_tx_tready <= 1'b0; tx_bytes_written <= 16'd0; tx_req_inflight <= 1'b0; req_ready <= arp_cached && !almost_full; if (send_req && req_ready) begin tx_req_len <= data_length; udp_send_data_length <= data_length; tx_req_inflight <= 1'b1; // threshold for allowing next packet // to be written to the RAM if (data_length > 16'd16) tx_release_threshold <= data_length - 16'd16; else tx_release_threshold <= 16'd0; tx_state <= TX_WAIT_RAM_REQ; end // arp check if (!arp_cached) begin tx_state <= TX_ARP_REQ; end end // Pulse ARP request TX_ARP_REQ: begin req_ready <= 1'b0; s_axis_tx_tready <= 1'b0; udp_tx_req <= 1'b0; arp_delay <= 32'ha000000; tx_state <= TX_ARP_SEND; end // Wait until ARP is resolved TX_ARP_SEND: begin req_ready <= 1'b0; s_axis_tx_tready <= 1'b0; udp_tx_req <= 1'b0; // sent if (mac_send_end) tx_state <= TX_WAIT_ARP; end // wait for ARP response TX_WAIT_ARP: begin if (arp_found) begin arp_cached <= 1'b1; tx_state <= TX_IDLE; end // timeout to not spam ARPs if (arp_delay == 32'b0) begin // re-try tx_state <= TX_ARP_REQ; end else begin // wait arp_delay = arp_delay - 32'b1; end end // Hold udp_tx_req until udp_ram_data_req pulse arrives TX_WAIT_RAM_REQ: begin req_ready <= 1'b0; udp_tx_req <= 1'b1; if (udp_ram_data_req) begin udp_tx_req <= 1'b0; s_axis_tx_tready <= 1'b1; tx_state <= TX_STREAM; end end // Accept AXIS bytes and write them into TX RAM TX_STREAM: begin req_ready <= 1'b0; udp_tx_req <= 1'b0; // keep ready high while receiving payload bytes s_axis_tx_tready <= (tx_bytes_written < tx_req_len); if (s_axis_tx_tvalid && s_axis_tx_tready) begin tx_ram_wr_data <= s_axis_tx_tdata; tx_ram_wr_en <= 1'b1; tx_bytes_written <= tx_bytes_written + 1'b1; if (tx_bytes_written + 1'b1 >= tx_req_len) begin s_axis_tx_tready <= 1'b0; tx_state <= TX_WAIT_DRAIN; end end end // Packet payload is already in RAM. // Wait until TX RAM starts draining enough to allow // the next request. TX_WAIT_DRAIN: begin s_axis_tx_tready <= 1'b0; udp_tx_req <= 1'b0; if (udp_ram_data_count <= tx_release_threshold) tx_state <= TX_IDLE; end default: begin tx_state <= TX_IDLE; tx_ram_wr_en <= 1'b0; udp_tx_req <= 1'b0; s_axis_tx_tready <= 1'b0; req_ready <= 1'b0; end endcase end end endmodule