Add a basic hub

This adds a basic clause 27 repeater (hub), mostly for test purposes.
It's effectively just the state machine in figure 27-4 and nothing else
(e.g. no partitioning or jabber detection). This is surprisingly simple.

Unfortunately, yosys doesn't allow memories in port declarations, even
for systemverilog. This complicates the implementation and testbench,
since we have to do the slicing ourselves. This is particularly awful
for the testbench, since

	module.signal[0].value != module.signal.value[0]

and module.signal can't be indexed by slices, and module.signal.value is
big endian (ugh ugh ugh). There is no clean solution here.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2023-01-21 17:39:25 -05:00
parent 798968d3d6
commit b68e1312c4
2 changed files with 142 additions and 0 deletions

68
rtl/hub_core.v Normal file
View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
*/
`include "common.vh"
`include "io.vh"
module hub_core (
input clk,
/* MII */
input [PORT_COUNT - 1:0] rx_dv,
input [PORT_COUNT - 1:0] rx_er,
input [PORT_COUNT * 4 - 1:0] rxd,
output reg [PORT_COUNT - 1:0] tx_en,
output reg [PORT_COUNT - 1:0] tx_er,
output reg [PORT_COUNT * 4 - 1:0] txd
);
parameter PORT_COUNT = 4;
localparam PORT_BITS = $clog2(PORT_COUNT);
localparam DATA_JAM = 4'h5;
integer i;
reg jam, activity;
reg [PORT_BITS - 1:0] active_port;
reg [PORT_COUNT - 1:0] tx_en_next, tx_er_next;
reg [3:0] txd_next [PORT_COUNT - 1:0];
always @(*) begin
jam = 0;
activity = 0;
active_port = {PORT_BITS{1'bx}};
for (i = 0; i < PORT_COUNT; i = i + 1) begin
if (rx_dv[i]) begin
if (activity)
jam = 1;
else
active_port = i;
activity = 1;
end
end
for (i = 0; i < PORT_COUNT; i = i + 1) begin
tx_en_next[i] = 0;
tx_er_next[i] = rx_er[active_port];
txd_next[i] = rxd[active_port * 4 +: 4];
if (jam) begin
tx_en_next[i] = 1;
tx_er_next[i] = 0;
txd_next[i] = DATA_JAM;
end else if (activity && i != active_port) begin
tx_en_next[i] = 1;
end
end
end
always @(posedge clk) begin
tx_en <= tx_en_next;
tx_er <= tx_er_next;
for (i = 0; i < PORT_COUNT; i = i + 1)
txd[i * 4 +: 4] <= txd_next[i];
end
endmodule

74
tb/hub_core.py Normal file
View File

@ -0,0 +1,74 @@
# SPDX-License-Identifier: AGPL-3.0-Only
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
import itertools
import cocotb
from cocotb.binary import BinaryValue
from cocotb.clock import Clock
from cocotb.handle import ModifiableObject
from cocotb.triggers import ClockCycles, Event, FallingEdge, RisingEdge, Timer
from cocotb.types import LogicArray
PORT_COUNT = 4
class Memory(ModifiableObject):
def __init__(self, handle, path):
super().__init__(handle, path)
@cocotb.test(timeout_time=100, timeout_unit='ns')
async def test_hub(hub):
hub.rx_dv.value = 0
await Timer(1)
await cocotb.start(Clock(hub.clk, 8, units='ns').start())
await FallingEdge(hub.clk)
assert not hub.tx_en.value
def check(jam, active_port=None, data=None):
if jam:
data = 5
for i in range(PORT_COUNT):
if not jam and i == active_port:
assert not hub.tx_en[i].value
else:
assert hub.tx_en[i].value
if data is None:
assert hub.tx_er[i].value
else:
assert not hub.tx_er[i].value
assert hub.txd.value[i * 4:(i + 1) * 4 - 1] == data
hub.rx_dv[0].value = 1
hub.rx_er[0].value = 0
rxd = LogicArray(itertools.repeat('X', PORT_COUNT * 4))
rxd[3:0] = BinaryValue(1, 4, False).binstr
hub.rxd.value = rxd
await FallingEdge(hub.clk)
check(False, 0, 1)
hub.rx_er[0].value = 1
await FallingEdge(hub.clk)
check(False, 0)
hub.rx_dv[1].value = 1
hub.rx_er[1].value = 0
rxd[7:4] = BinaryValue(2, 4, False).binstr
hub.rxd.value = rxd
await FallingEdge(hub.clk)
check(True)
hub.rx_dv[0].value = 0
for i in range(1, PORT_COUNT):
hub.rx_dv[i].value = 1
hub.rx_er[i].value = 0
rxd = LogicArray(itertools.repeat('X', PORT_COUNT * 4))
rxd[(i + 1) * 4 - 1:i * 4] = BinaryValue(i + 1, 4, False).binstr
hub.rxd.value = rxd
await FallingEdge(hub.clk)
check(False, i, i + 1)
hub.rx_dv[i].value = 0
await FallingEdge(hub.clk)
assert not hub.tx_en.value