588 lines
15 KiB
Verilog
588 lines
15 KiB
Verilog
// SPDX-License-Identifier: AGPL-3.0-Only OR CERN-OHL-S-2.0
|
|
/*
|
|
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
|
*
|
|
* This is a 100M, Half-duplex MAC (or most of the TX side, anyway). It takes
|
|
* an AXI stream of input data, appends the FCS, and drives the MII. The first
|
|
* 57 bytes of data are stored to allow retrying upon collisions.
|
|
*
|
|
* The following diagram shows two frames, one under 56 bytes, and another
|
|
* greater than 64 bytes. It is adapted from figure 4-5, but reworked to
|
|
* remove complications added for 1000M, and to show the important state
|
|
* transitions.
|
|
*
|
|
* | PREAMBLE | DATA_EARLY | PAD_EARLY | PAD_LATE | FCS |
|
|
* | PREAMBLE | DATA_EARLY | DATA_MIDDLE | DATA_LATE | FCS |
|
|
* |<-- Slot time -->|
|
|
* |<-- Minimum frame size -->|
|
|
*/
|
|
|
|
module axis_mii_tx (
|
|
input clk, rst,
|
|
|
|
/* MII */
|
|
output reg mii_tx_ce,
|
|
output reg mii_tx_en,
|
|
output reg [3:0] mii_txd,
|
|
|
|
input mii_col,
|
|
input mii_crs,
|
|
|
|
/* AXI Stream */
|
|
input [7:0] axis_data,
|
|
input axis_valid,
|
|
output axis_ready,
|
|
input axis_last,
|
|
input axis_err,
|
|
|
|
/* Use a slot time of one octet for backoff; test purposes only */
|
|
input short_backoff,
|
|
/* Enable Half-Duplex */
|
|
input half_duplex,
|
|
|
|
/* Clause 4 TransmitStatus */
|
|
output reg transmit_ok,
|
|
output reg gave_up,
|
|
output reg late_collision,
|
|
output reg underflow
|
|
);
|
|
|
|
/*
|
|
* The inter-packet states. In IPG_EARLY, any assertion of CRS will
|
|
* reset the state counter. In IPG_LATE, CRS is ignored. However, if
|
|
* there is no packet waiting and CRS is asserted, then it will
|
|
* transition back to IPG_EARLY. Interestingly, IPG_EARLY_BYTES can be
|
|
* reduced to 0 if desired, removing this functionality altogether.
|
|
* Similarly, we go directly to IPG_LATE after transmitting a packet.
|
|
*/
|
|
localparam IPG_EARLY = 0;
|
|
localparam IPG_LATE = 1;
|
|
/*
|
|
* The preamble state. Even if we have a collision, we still transmit
|
|
* the full preamble before jamming.
|
|
*/
|
|
localparam PREAMBLE = 2;
|
|
/*
|
|
* The states used to transmit data. They are divided as follows:
|
|
* - DATA_EARLY: Collisions during this state are early, and we need
|
|
* to pad to the minimum frame size.
|
|
* - DATA_MIDDLE: Collisions during this state are late, but we
|
|
* still need to pad to the minimum frame size.
|
|
* - DATA_LATE: Collisions are late, and we don't need to pad.
|
|
* An important consideration here is the management of the done
|
|
* signal. It must be asserted whenever we transition from early
|
|
* collisions to late collisions.
|
|
*/
|
|
localparam DATA_EARLY = 3;
|
|
localparam DATA_MIDDLE = 4;
|
|
localparam DATA_LATE = 5;
|
|
/*
|
|
* Padding states:
|
|
* - PAD_EARLY: If we get a collision we jam and retry.
|
|
* - PAD_LATE: Collisions are late.
|
|
*/
|
|
localparam PAD_EARLY = 6;
|
|
localparam PAD_LATE = 7;
|
|
/*
|
|
* Transmit the FCS.
|
|
*/
|
|
localparam FCS = 8;
|
|
/*
|
|
* Because bytes are sent over the MII one byte-time after the state
|
|
* machine moves on, we can have a late collision even after the FCS
|
|
* state is done. To detect this, we use an additional state which is
|
|
* either the first byte of the jam sequence or the first byte of the
|
|
* IPG, depending on whether we get a collision.
|
|
*/
|
|
localparam IPG_OR_JAM = 9;
|
|
/*
|
|
* The various jam sequences. They all send the same jam pattern, but
|
|
* differ on what happens next. JAM_FAIL just goes straight to the
|
|
* IPG. JAM_DRAIN goes to DRAIN. JAM_RETRY goes to BACKOFF if we still
|
|
* have retries, or to DRAIN if we don't.
|
|
*/
|
|
localparam JAM_RETRY = 10;
|
|
localparam JAM_DRAIN = 11;
|
|
localparam JAM_FAIL = 12;
|
|
/*
|
|
* Drain the replay buffer and whatever is feeding it until we get to
|
|
* the last byte.
|
|
*/
|
|
localparam DRAIN = 13;
|
|
/*
|
|
* Perform backoff. The amount is set by JAM_RETRY.
|
|
*/
|
|
localparam BACKOFF = 14;
|
|
|
|
localparam PREAMBLE_BYTES = 8;
|
|
localparam FCS_BYTES = 4;
|
|
localparam JAM_BYTES = 4;
|
|
localparam SLOT_BYTES = 64;
|
|
localparam MIN_FRAME_BYTES = 64;
|
|
localparam IPG_BYTES = 12;
|
|
parameter IPG_EARLY_BYTES = 8;
|
|
localparam IPG_LATE_BYTES = IPG_BYTES - IPG_EARLY_BYTES;
|
|
/* +1 since we transition 8 bit times earlier than the MII */
|
|
localparam DATA_EARLY_BYTES = SLOT_BYTES - PREAMBLE_BYTES + 1;
|
|
localparam DATA_MIDDLE_BYTES = MIN_FRAME_BYTES - DATA_EARLY_BYTES - FCS_BYTES;
|
|
localparam PAD_LATE_BYTES = DATA_MIDDLE_BYTES;
|
|
|
|
localparam MAX_RETRIES = 16;
|
|
|
|
parameter MII_100_RATIO = 5;
|
|
|
|
localparam DATA_PREAMBLE = 8'h55;
|
|
localparam DATA_SFD = 8'hd5;
|
|
parameter DATA_JAM = 8'hff;
|
|
parameter DATA_PAD = 8'h00;
|
|
|
|
localparam CRC_INIT = 32'hffffffff;
|
|
|
|
reg [7:0] data, data_next;
|
|
reg [31:0] crc_state, crc_state_next, crc_state_out;
|
|
|
|
lfsr #(
|
|
.LFSR_WIDTH(32),
|
|
.LFSR_POLY(32'h04c11db7),
|
|
.LFSR_CONFIG("GALOIS"),
|
|
.REVERSE(1),
|
|
.DATA_WIDTH(8),
|
|
.STYLE("REDUCTION")
|
|
) crc (
|
|
.data_in(data),
|
|
.state_in(crc_state),
|
|
.state_out(crc_state_out)
|
|
);
|
|
|
|
wire [7:0] buf_data;
|
|
wire buf_err, buf_valid, buf_last;
|
|
reg buf_ready, buf_ready_next, replay, replay_next, done, done_next;
|
|
|
|
axis_replay_buffer #(
|
|
.DATA_WIDTH(9),
|
|
.BUF_SIZE(DATA_EARLY_BYTES)
|
|
) replay_buffer (
|
|
.clk(clk),
|
|
.rst(rst),
|
|
.s_axis_data({ axis_data, axis_err }),
|
|
.s_axis_valid(axis_valid),
|
|
.s_axis_ready(axis_ready),
|
|
.s_axis_last(axis_last),
|
|
.m_axis_data({ buf_data, buf_err }),
|
|
.m_axis_valid(buf_valid),
|
|
.m_axis_ready(buf_ready),
|
|
.m_axis_last(buf_last),
|
|
.replay(replay),
|
|
.done(done)
|
|
);
|
|
|
|
reg [2:0] mii_tx_counter, mii_tx_counter_next;
|
|
reg mii_tx_ce_next, mii_tx_ce_next_next, mii_tx_ce_next_next_next;
|
|
reg mii_tx_en_next;
|
|
reg do_crc, do_crc_next, do_state, odd, odd_next, collision, collision_next;
|
|
reg [3:0] state, state_next, retries, retries_next;
|
|
reg [5:0] state_counter, state_counter_next;
|
|
reg [9:0] lfsr, lfsr_next, backoff, backoff_next;
|
|
reg transmit_ok_next, gave_up_next, late_collision_next;
|
|
reg underflow_next, err, err_next;
|
|
initial lfsr <= 10'h3ff;
|
|
|
|
always @(*) begin
|
|
mii_tx_ce_next_next_next = 0;
|
|
mii_tx_counter_next = mii_tx_counter - 1;
|
|
if (!mii_tx_counter) begin
|
|
mii_tx_ce_next_next_next = 1;
|
|
mii_tx_counter_next = MII_100_RATIO - 1;
|
|
end
|
|
|
|
do_crc_next = 0;
|
|
crc_state_next = crc_state;
|
|
if (do_crc)
|
|
crc_state_next = crc_state_out;
|
|
|
|
lfsr_next = { lfsr[8:0], lfsr[9] ^ lfsr[6] };
|
|
case (retries)
|
|
15: backoff_next = lfsr[ 0];
|
|
14: backoff_next = lfsr[1:0];
|
|
13: backoff_next = lfsr[2:0];
|
|
12: backoff_next = lfsr[3:0];
|
|
11: backoff_next = lfsr[4:0];
|
|
10: backoff_next = lfsr[5:0];
|
|
9: backoff_next = lfsr[6:0];
|
|
8: backoff_next = lfsr[7:0];
|
|
7: backoff_next = lfsr[8:0];
|
|
6, 5, 4, 3, 2, 1, 0:
|
|
backoff_next = lfsr[9:0];
|
|
endcase
|
|
|
|
odd_next = odd ^ mii_tx_ce_next;
|
|
mii_tx_en_next = mii_tx_en;
|
|
|
|
do_state = 0;
|
|
state_next = state;
|
|
state_counter_next = state_counter;
|
|
if (mii_tx_ce_next && odd) begin
|
|
do_state = 1;
|
|
state_counter_next = state_counter - 1;
|
|
end
|
|
|
|
err_next = 0;
|
|
if (buf_ready && buf_valid && buf_err)
|
|
err_next = 1;
|
|
|
|
transmit_ok_next = 0;
|
|
gave_up_next = 0;
|
|
late_collision_next = 0;
|
|
underflow_next = 0;
|
|
|
|
buf_ready_next = 0;
|
|
data_next = data;
|
|
replay_next = 0;
|
|
done_next = 0;
|
|
retries_next = retries;
|
|
collision_next = collision || (half_duplex && mii_col);
|
|
case (state)
|
|
IPG_EARLY: begin
|
|
collision_next = 0;
|
|
crc_state_next = CRC_INIT;
|
|
if (mii_crs)
|
|
state_counter_next = IPG_EARLY_BYTES - 1;
|
|
|
|
if (do_state) begin
|
|
mii_tx_en_next = 0;
|
|
|
|
if (!state_counter && !mii_crs) begin
|
|
state_counter_next = IPG_LATE_BYTES - 1;
|
|
state_next = IPG_LATE;
|
|
end
|
|
end
|
|
end
|
|
IPG_LATE: begin
|
|
collision_next = 0;
|
|
crc_state_next = CRC_INIT;
|
|
if (do_state && !state_counter) begin
|
|
state_counter_next = state_counter;
|
|
if (buf_valid) begin
|
|
state_next = PREAMBLE;
|
|
state_counter_next = PREAMBLE_BYTES - 1;
|
|
end else if (half_duplex && mii_crs) begin
|
|
state_next = IPG_EARLY;
|
|
state_counter_next = IPG_EARLY_BYTES - 1;
|
|
end
|
|
end
|
|
end
|
|
PREAMBLE: begin
|
|
crc_state_next = CRC_INIT;
|
|
if (do_state) begin
|
|
mii_tx_en_next = 1;
|
|
data_next = DATA_PREAMBLE;
|
|
if (!state_counter) begin
|
|
data_next = DATA_SFD;
|
|
state_next = DATA_EARLY;
|
|
state_counter_next = DATA_EARLY_BYTES - 1;
|
|
if (collision_next) begin
|
|
state_next = JAM_RETRY;
|
|
state_counter_next = JAM_BYTES - 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
DATA_EARLY,
|
|
DATA_MIDDLE,
|
|
DATA_LATE: begin
|
|
buf_ready_next = mii_tx_ce_next_next && odd;
|
|
if (do_state) begin
|
|
data_next = buf_data;
|
|
do_crc_next = 1;
|
|
|
|
case (state)
|
|
DATA_EARLY: begin
|
|
if (!state_counter) begin
|
|
if (buf_last)
|
|
state_next = PAD_LATE;
|
|
else
|
|
state_next = DATA_MIDDLE;
|
|
state_counter_next = DATA_MIDDLE_BYTES - 1;
|
|
done_next = 1;
|
|
end else if (buf_last) begin
|
|
state_next = PAD_EARLY;
|
|
end
|
|
end
|
|
DATA_MIDDLE: begin
|
|
if (!state_counter) begin
|
|
if (buf_last)
|
|
state_next = FCS;
|
|
else
|
|
state_next = DATA_LATE;
|
|
state_counter_next = FCS_BYTES - 1;
|
|
end else if (buf_last) begin
|
|
state_next = PAD_LATE;
|
|
end
|
|
end
|
|
DATA_LATE: begin
|
|
if (buf_last)
|
|
state_next = FCS;
|
|
state_counter_next = FCS_BYTES - 1;
|
|
end
|
|
endcase
|
|
|
|
if (!buf_valid) begin
|
|
if (buf_last)
|
|
state_next = JAM_FAIL;
|
|
else
|
|
state_next = JAM_DRAIN;
|
|
|
|
state_counter_next = JAM_BYTES - 1;
|
|
underflow_next = 1;
|
|
if (state == DATA_EARLY)
|
|
done_next = 1;
|
|
end
|
|
|
|
if (collision_next) begin
|
|
underflow_next = 0;
|
|
if (state == DATA_EARLY) begin
|
|
state_next = JAM_RETRY;
|
|
done_next = 0;
|
|
end else begin
|
|
if (buf_last)
|
|
state_next = JAM_FAIL;
|
|
else
|
|
state_next = JAM_DRAIN;
|
|
late_collision_next = 1;
|
|
end
|
|
state_counter_next = JAM_BYTES - 1;
|
|
end
|
|
end
|
|
|
|
if (err) begin
|
|
state_next = JAM_DRAIN;
|
|
state_counter_next = JAM_BYTES - 1;
|
|
underflow_next = 1;
|
|
if (state == DATA_EARLY)
|
|
done_next = 1;
|
|
end
|
|
end
|
|
PAD_EARLY,
|
|
PAD_LATE: begin
|
|
if (do_state) begin
|
|
data_next = DATA_PAD;
|
|
do_crc_next = 1;
|
|
if (collision_next) begin
|
|
if (state == PAD_EARLY) begin
|
|
state_next = JAM_RETRY;
|
|
end else begin
|
|
state_next = JAM_FAIL;
|
|
late_collision_next = 1;
|
|
end
|
|
state_counter_next = JAM_BYTES - 1;
|
|
end else if (!state_counter) begin
|
|
if (state == PAD_EARLY) begin
|
|
state_next = PAD_LATE;
|
|
state_counter_next =
|
|
PAD_LATE_BYTES - 1;
|
|
done_next = 1;
|
|
end else begin
|
|
state_next = FCS;
|
|
state_counter_next =
|
|
FCS_BYTES - 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (err) begin
|
|
state_next = JAM_FAIL;
|
|
state_counter_next = JAM_BYTES - 1;
|
|
underflow_next = 1;
|
|
if (state == PAD_EARLY)
|
|
done_next = 1;
|
|
end
|
|
end
|
|
FCS: begin
|
|
if (do_state) begin
|
|
case (state_counter[1:0])
|
|
2'd3: data_next = ~crc_state[7:0];
|
|
2'd2: data_next = ~crc_state[15:8];
|
|
2'd1: data_next = ~crc_state[23:16];
|
|
2'd0: begin
|
|
data_next = ~crc_state[31:24];
|
|
state_next = IPG_OR_JAM;
|
|
end
|
|
endcase
|
|
|
|
if (collision_next) begin
|
|
state_next = JAM_FAIL;
|
|
state_counter_next = JAM_BYTES - 1;
|
|
late_collision_next = 1;
|
|
transmit_ok_next = 0;
|
|
end
|
|
end
|
|
|
|
if (err) begin
|
|
state_next = JAM_FAIL;
|
|
state_counter_next = JAM_BYTES - 1;
|
|
underflow_next = 1;
|
|
end
|
|
end
|
|
IPG_OR_JAM: begin
|
|
if (do_state) begin
|
|
data_next = DATA_JAM;
|
|
if (collision_next) begin
|
|
state_next = JAM_FAIL;
|
|
state_counter_next = JAM_BYTES - 2;
|
|
late_collision_next = 1;
|
|
end else begin
|
|
mii_tx_en_next = 0;
|
|
state_next = IPG_LATE;
|
|
state_counter_next = IPG_BYTES - 2;
|
|
transmit_ok_next = 1;
|
|
retries_next = MAX_RETRIES - 1;
|
|
end
|
|
end
|
|
end
|
|
JAM_RETRY,
|
|
JAM_DRAIN,
|
|
JAM_FAIL: begin
|
|
if (do_state)
|
|
data_next = DATA_JAM;
|
|
|
|
if (do_state && !state_counter) begin
|
|
if (state == JAM_RETRY && retries) begin
|
|
state_next = BACKOFF;
|
|
/*
|
|
* A value of 0 would otherwise mean
|
|
* "wait one slot time", so start at
|
|
* the "end" of a slot time so we
|
|
* don't wait for no reason.
|
|
*/
|
|
state_counter_next = 0;
|
|
replay_next = 1;
|
|
retries_next = retries - 1;
|
|
end else begin
|
|
state_counter_next = IPG_BYTES - 1;
|
|
if (state == JAM_FAIL) begin
|
|
state_next = IPG_LATE;
|
|
mii_tx_en_next = 0;
|
|
end else begin
|
|
state_next = DRAIN;
|
|
end
|
|
retries_next = MAX_RETRIES - 1;
|
|
if (state == JAM_RETRY) begin
|
|
gave_up_next = 1;
|
|
done_next = 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
DRAIN: begin
|
|
if (buf_ready && buf_valid && buf_last) begin
|
|
if (half_duplex) begin
|
|
state_next = IPG_EARLY;
|
|
state_counter_next = IPG_EARLY_BYTES - 1;
|
|
end else begin
|
|
state_next = IPG_LATE;
|
|
state_counter_next = IPG_BYTES - 1;
|
|
end
|
|
end else begin
|
|
buf_ready_next = 1;
|
|
end
|
|
|
|
if (do_state)
|
|
mii_tx_en_next = 0;
|
|
end
|
|
BACKOFF: begin
|
|
backoff_next = backoff;
|
|
if (mii_tx_ce_next && odd) begin
|
|
mii_tx_en_next = 0;
|
|
if (!state_counter) begin
|
|
backoff_next = backoff - 1;
|
|
if (short_backoff)
|
|
state_counter_next = 0;
|
|
else
|
|
state_counter_next = SLOT_BYTES - 1;
|
|
|
|
if (!backoff) begin
|
|
state_next = IPG_EARLY;
|
|
state_counter_next = IPG_EARLY_BYTES - 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
endcase
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
do_crc <= do_crc_next;
|
|
data <= data_next;
|
|
mii_txd <= odd_next ? data_next[7:4] : data_next[3:0];
|
|
backoff <= backoff_next;
|
|
crc_state <= crc_state_next;
|
|
collision <= collision_next;
|
|
lfsr <= lfsr_next;
|
|
end
|
|
|
|
always @(posedge clk, posedge rst) begin
|
|
if (rst) begin
|
|
mii_tx_counter <= MII_100_RATIO;
|
|
mii_tx_ce <= 0;
|
|
mii_tx_ce_next <= 0;
|
|
mii_tx_ce_next_next <= 0;
|
|
mii_tx_en <= 0;
|
|
odd <= 0;
|
|
state <= IPG_EARLY;
|
|
state_counter <= IPG_EARLY_BYTES - 1;
|
|
retries <= MAX_RETRIES - 1;
|
|
transmit_ok <= 0;
|
|
gave_up <= 0;
|
|
late_collision <= 0;
|
|
underflow <= 0;
|
|
buf_ready <= 0;
|
|
err <= 0;
|
|
done <= 0;
|
|
replay <= 0;
|
|
end else begin
|
|
mii_tx_counter <= mii_tx_counter_next;
|
|
mii_tx_ce <= mii_tx_ce_next;
|
|
mii_tx_ce_next <= mii_tx_ce_next_next;
|
|
mii_tx_ce_next_next <= mii_tx_ce_next_next_next;
|
|
mii_tx_en <= mii_tx_en_next;
|
|
odd <= odd_next;
|
|
state <= state_next;
|
|
state_counter <= state_counter_next;
|
|
retries <= retries_next;
|
|
transmit_ok <= transmit_ok_next;
|
|
gave_up <= gave_up_next;
|
|
late_collision <= late_collision_next;
|
|
underflow <= underflow_next;
|
|
buf_ready <= buf_ready_next;
|
|
err <= err_next;
|
|
done <= done_next;
|
|
replay <= replay_next;
|
|
end
|
|
end
|
|
|
|
`ifndef SYNTHESIS
|
|
reg [255:0] state_text;
|
|
|
|
always @(*) begin
|
|
case (state)
|
|
IPG_EARLY: state_text = "IPG_EARLY";
|
|
IPG_LATE: state_text = "IPG_LATE";
|
|
PREAMBLE: state_text = "PREAMBLE";
|
|
DATA_EARLY: state_text = "DATA_EARLY";
|
|
DATA_MIDDLE: state_text = "DATA_MIDDLE";
|
|
DATA_LATE: state_text = "DATA_LATE";
|
|
PAD_EARLY: state_text = "PAD_EARLY";
|
|
PAD_LATE: state_text = "PAD_LATE";
|
|
FCS: state_text = "FCS";
|
|
IPG_OR_JAM: state_text = "IPG_OR_JAM";
|
|
JAM_RETRY: state_text = "JAM_RETRY";
|
|
JAM_DRAIN: state_text = "JAM_DRAIN";
|
|
JAM_FAIL: state_text = "JAM_FAIL";
|
|
DRAIN: state_text = "DRAIN";
|
|
BACKOFF: state_text = "BACKOFF";
|
|
endcase
|
|
end
|
|
`endif
|
|
|
|
endmodule
|