diff --git a/rtl/axis_mii_tx.v b/rtl/axis_mii_tx.v new file mode 100644 index 0000000..7122df2 --- /dev/null +++ b/rtl/axis_mii_tx.v @@ -0,0 +1,559 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2022 Sean Anderson + * + * 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, + + /* 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, + + /* 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; + initial crc_state = CRC_INIT; + + 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; + initial begin + buf_ready = 0; + done = 0; + replay = 0; + end + + axis_replay_buffer #( + .DATA_WIDTH(9), + .BUF_SIZE(DATA_EARLY_BYTES) + ) replay_buffer ( + .clk(clk), + .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; + + initial 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; + mii_txd = 0; + do_crc = 0; + odd = 0; + collision = 0; + state = IPG_EARLY; + state_counter = IPG_EARLY_BYTES - 1; + retries = MAX_RETRIES - 1; + lfsr = 10'h3ff; + transmit_ok = 0; + gave_up = 0; + late_collision = 0; + underflow = 0; + end + + 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; + + backoff_next = backoff; + lfsr_next = { lfsr[8:0], lfsr[9] ^ lfsr[6] }; + + 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 + + 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 || 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 (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 || buf_err) begin + if (buf_valid && buf_last) + state_next = JAM_FAIL; + else + state_next = JAM_DRAIN; + + /* + * Start jamming immediately to avoid + * sending Xs + */ + state_counter_next = JAM_BYTES - 2; + data_next = DATA_JAM; + 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 + 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 + 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 + 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; + 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 + 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 + state_next = IPG_EARLY; + state_counter_next = IPG_EARLY_BYTES - 1; + end else begin + buf_ready_next = 1; + end + + if (do_state) + mii_tx_en_next = 0; + end + BACKOFF: begin + 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 + 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_counter <= mii_tx_counter_next; + mii_tx_en <= mii_tx_en_next; + mii_txd <= odd_next ? data_next[7:4] : data_next[3:0]; + do_crc <= do_crc_next; + odd <= odd_next; + state <= state_next; + state_counter <= state_counter_next; + buf_ready <= buf_ready_next; + data <= data_next; + replay <= replay_next; + done <= done_next; + retries <= retries_next; + backoff <= backoff_next; + lfsr <= lfsr_next; + crc_state <= crc_state_next; + collision <= collision_next; + transmit_ok <= transmit_ok_next; + gave_up <= gave_up_next; + late_collision <= late_collision_next; + underflow <= underflow_next; + 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 diff --git a/tb/axis_mii_tx.py b/tb/axis_mii_tx.py new file mode 100644 index 0000000..d9865ba --- /dev/null +++ b/tb/axis_mii_tx.py @@ -0,0 +1,321 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2022 Sean Anderson + +import enum +import random +import zlib + +import cocotb +from cocotb.clock import Clock +from cocotb.regression import TestFactory +from cocotb.triggers import ClockCycles, Edge, FallingEdge, First, RisingEdge, Timer +from cocotb.types import LogicArray +from cocotb.utils import get_sim_time + +from . import axis_replay_buffer +from .pcs_rx import mii_recv_packet +from .util import alist, lookahead, timeout + +import os + +skip_slow = not os.environ.get('RUN_SLOW', False) + +async def init(mac): + mac.mii_col.value = 0 + mac.mii_crs.value = 0 + mac.axis_valid.value = 0 + mac.axis_err.value = 0 + mac.short_backoff.value = 1 + await Timer(1) + await cocotb.start(Clock(mac.clk, 8, units='ns').start()) + +def send_packet(mac, packet, ratio=1): + return axis_replay_buffer.send_packet({ + 'clk': mac.clk, + 'data': mac.axis_data, + 'err': mac.axis_err, + 'valid': mac.axis_valid, + 'last': mac.axis_last, + 'ready': mac.axis_ready, + }, packet, ratio) + +class MACError(Exception): + pass + +class FrameCheckError(MACError): + pass + +class AlignmentError(MACError): + pass + +class PaddingError(MACError): + pass + +async def nibbles_to_bytes(nibbles): + while True: + try: + lo = await anext(nibbles) + except StopAsyncIteration: + return + + try: + hi = await anext(nibbles) + except StopAsyncIteration: + raise AlignmentError + + yield hi << 4 | lo + +SFD = 0xd5 + +async def skip_preamble(packet): + saw_ssd = False + async for byte in packet: + if saw_ssd: + yield byte + elif byte == SFD: + saw_ssd = True + +FCS_GOOD = zlib.crc32(b'\0\0\0\0') + +async def check_fcs(packet): + packet = await alist(packet) + fcs = zlib.crc32(bytes(packet)) + if fcs != FCS_GOOD: + raise FrameCheckError + elif len(packet) < 64: + raise PaddingError + return packet[:-4] + +def recv_packet(mac): + return check_fcs(skip_preamble(nibbles_to_bytes(mii_recv_packet(mac, { + 'ce': mac.mii_tx_ce, + 'data': mac.mii_txd, + 'valid': mac.mii_tx_en, + })))) + +async def expect_bad_fcs(mac): + try: + await recv_packet(mac) + except FrameCheckError: + pass + else: + raise AssertionError + +class Status(enum.Enum): + OK = enum.auto() + GAVE_UP = enum.auto() + LATE_COLLISION = enum.auto() + UNDERFLOW = enum.auto() + +async def get_status(mac): + ok = 0 + gave_up = 0 + late = 0 + underflow = 0 + + while not (ok or gave_up or late or underflow) or mac.mii_tx_en.value: + ok += mac.transmit_ok.value + gave_up += mac.gave_up.value + late += mac.late_collision.value + underflow += mac.underflow.value + await FallingEdge(mac.clk) + + assert ok + gave_up + late + underflow == 1 + if ok: + return Status.OK + elif gave_up: + return Status.GAVE_UP + elif late: + return Status.LATE_COLLISION + elif underflow: + return Status.UNDERFLOW + +async def start(mac, packet, ratio=1): + send = await cocotb.start(send_packet(mac, packet, ratio)) + status = await cocotb.start(get_status(mac)) + return send, status + +BIT_TIME_NS = 10 +BYTE_TIME_NS = 8 * BIT_TIME_NS + +async def collide(mac, ns, duration=16): + while not mac.mii_tx_en.value: + await FallingEdge(mac.clk) + + if ns > 4: + await Timer(ns - 4, 'ns') + mac.mii_col.value = 1 + await Timer(duration, 'ns') + mac.mii_col.value = 0 + +def randtime(min_bytes, max_bytes): + return random.randrange(min_bytes * BYTE_TIME_NS, max_bytes * BYTE_TIME_NS) + +async def restart(mac, ns): + await cocotb.start(collide(mac, ns)) + await expect_bad_fcs(mac) + +def compare(actual, expected): + if actual[:len(expected)] != expected: + print(actual) + print(expected) + raise AssertionError + +async def ok(mac, packet, status, ns=None): + if ns is not None: + await cocotb.start(collide(mac, ns)) + compare(await recv_packet(mac), packet) + assert await status.join() == Status.OK + +@timeout(50, 'us') +async def test_send(mac, ratio): + await init(mac) + + packets = ( + list(range(32)), + list(range(56)), + list(range(256)), + ) + + async def send_packets(): + for packet in packets: + await send_packet(mac, packet, ratio) + + await cocotb.start(send_packets()) + for i, packet in enumerate(packets): + status = await cocotb.start(get_status(mac)) + recv = await cocotb.start(recv_packet(mac)) + + # Measure the IPG to ensure throughput + start = get_sim_time('ns') + while not mac.mii_tx_en.value: + await RisingEdge(mac.clk) + # The first IPG may not be exact + if i: + assert get_sim_time('ns') - start == 12 * 80 - 4 + + compare(await recv.join(), packet) + assert await status.join() == Status.OK + +send_tests = TestFactory(test_send) +send_tests.add_option('ratio', (1, BIT_TIME_NS)) +send_tests.generate_tests() + +@cocotb.test(timeout_time=100, timeout_unit='us') +async def test_underflow(mac): + await init(mac) + + async def underflow(mac, send, status): + await expect_bad_fcs(mac) + await send.join() + assert await status.join() == Status.UNDERFLOW + + send, status = await start(mac, range(32), 30) + await underflow(mac, send, status) + + send, status = await start(mac, [*range(56), None]) + await underflow(mac, send, status) + send, status = await start(mac, [*range(58), None]) + await underflow(mac, send, status) + send, status = await start(mac, [*range(60), None]) + await underflow(mac, send, status) + + send, status = await start(mac, [*range(56), None, 1]) + await underflow(mac, send, status) + send, status = await start(mac, [*range(58), None, 1]) + await underflow(mac, send, status) + send, status = await start(mac, [*range(60), None, 1]) + await underflow(mac, send, status) + + send, status = await start(mac, [*range(56), None]) + await restart(mac, (8 + 55) * BYTE_TIME_NS) + await underflow(mac, send, status) + send, status = await start(mac, [*range(58), None]) + await restart(mac, (8 + 57) * BYTE_TIME_NS) + await send.join() + assert await status.join() == Status.LATE_COLLISION + send, status = await start(mac, [*range(60), None]) + await restart(mac, (8 + 59) * BYTE_TIME_NS) + await send.join() + assert await status.join() == Status.LATE_COLLISION + +@cocotb.test(timeout_time=1250, timeout_unit='us', skip=skip_slow) +async def test_backoff(mac): + await init(mac) + + packet = list(range(32)) + for collisions in (15, 16): + send, status = await start(mac, packet) + then = None + for n in range(collisions): + await restart(mac, 0) + now = get_sim_time('ns') + if then is not None: + assert now - then <= (8 + 4 + 12 + 2 ** min(n, 10)) * BYTE_TIME_NS + then = now + + if collisions == 16: + assert await status.join() == Status.GAVE_UP + else: + await ok(mac, packet, status) + +@cocotb.test(timeout_time=10, timeout_unit='us') +async def test_defer(mac): + await init(mac) + + # Skip to the end of IPG_LATE + mac.mii_crs.value = 1 + await Timer(13 * BYTE_TIME_NS, 'ns') + packet = list(range(32)) + send, status = await start(mac, packet) + # Ensure IPG_EARLY works + await Timer(13 * BYTE_TIME_NS, 'ns') + assert not mac.mii_tx_en.value + + # Test the early 2/3s; not exact since the last 1/3 takes the slack + mac.mii_crs.value = 0 + await Timer(7 * BYTE_TIME_NS, 'ns') + mac.mii_crs.value = 1 + await Timer(6 * BYTE_TIME_NS, 'ns') + assert not mac.mii_tx_en.value + + # And the late + mac.mii_crs.value = 0 + await Timer(8 * BYTE_TIME_NS, 'ns') + mac.mii_crs.value = 1 + await Timer(5 * BYTE_TIME_NS, 'ns') + assert mac.mii_tx_en.value + assert await status.join() == Status.OK + +@cocotb.test(timeout_time=150, timeout_unit='us') +async def test_collision(mac): + await init(mac) + + async def late(mac, packet, ns): + send, status = await start(mac, packet) + await restart(mac, ns) + await send.join() + assert await status.join() == Status.LATE_COLLISION + + packet = list(range(32)) + send, status = await start(mac, packet) + await restart(mac, randtime(0, 8)) + await restart(mac, randtime(8, 40)) + await restart(mac, randtime(40, 64)) + await restart(mac, 64 * BYTE_TIME_NS - 1) + await ok(mac, packet, status, 72 * BYTE_TIME_NS) + + await late(mac, packet, 64 * BYTE_TIME_NS) + await late(mac, packet, randtime(64, 72)) + await late(mac, packet, (8 + 64) * BYTE_TIME_NS - 1) + + packet = list(range(256)) + send, status = await start(mac, packet) + await restart(mac, randtime(8, 40)) + await restart(mac, randtime(40, 64)) + await restart(mac, 64 * BYTE_TIME_NS - 1) + await ok(mac, packet, status, (8 + 256 + 4) * BYTE_TIME_NS) + + await late(mac, packet, 64 * BYTE_TIME_NS) + await late(mac, packet, randtime(64, 256)) + await late(mac, packet, 72 * BYTE_TIME_NS - 1)