From b68e1312c4c545047470cefd38e2e62513668c90 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Sat, 21 Jan 2023 17:39:25 -0500 Subject: [PATCH] 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 --- rtl/hub_core.v | 68 ++++++++++++++++++++++++++++++++++++++++++++++ tb/hub_core.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 rtl/hub_core.v create mode 100644 tb/hub_core.py diff --git a/rtl/hub_core.v b/rtl/hub_core.v new file mode 100644 index 0000000..bc7be5f --- /dev/null +++ b/rtl/hub_core.v @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2022 Sean Anderson + */ + +`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 diff --git a/tb/hub_core.py b/tb/hub_core.py new file mode 100644 index 0000000..2cf7f54 --- /dev/null +++ b/tb/hub_core.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2022 Sean Anderson + +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