Add TX MAC (most of it)

This adds the transmit half of a MAC, supporting 100M and half-duplex.
It's roughly analogous to the axis_(x)gmii_tx modules in Alex
Forencich's ethernet repo. I've taken the approach of moving all state
into the state variable. All decisions are made once and have a
different state for each path. For example, instead of checking against
a "bytes_sent" variable to determine what to do on collision, we have a
different state for each set of actions.

This whole module is heinously complex, especially because of the many
corner cases caused by the spec. I have probably not tested it nearly
enough, but the basics of sending packets have mostly had the bugs wrung
out.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2023-01-09 21:05:31 -05:00
parent 1f925d39ff
commit 0495ae377c
2 changed files with 880 additions and 0 deletions

559
rtl/axis_mii_tx.v Normal file
View File

@ -0,0 +1,559 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* 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,
/* 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

321
tb/axis_mii_tx.py Normal file
View File

@ -0,0 +1,321 @@
# SPDX-License-Identifier: AGPL-3.0-Only
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
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)