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