From b351beb9a0e794c341aeebc7195df91f9e1bc7f7 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 20 Feb 2023 18:24:49 -0500 Subject: [PATCH] Add hub This adds a basic hub wrapper module which incorperates the core introduced in b68e131 ("Add a basic hub"). For each port, it instantiates a phy (itself using a phy_internal wrapper) and an elastic buffer. A WISHBONE parameter is used to control whether to instantiate a wishbone interface. When disabled, we just respond to any request with err. I've ommitted a separate testbench for phy_internal, since it is much easier to create a smoke test using the hub interface. Signed-off-by: Sean Anderson --- Makefile | 1 + rtl/hub.v | 170 +++++++++++++++++++++++++++++++++++++++++++++ rtl/phy_internal.v | 134 +++++++++++++++++++++++++++++++++++ tb/hub.py | 101 +++++++++++++++++++++++++++ 4 files changed, 406 insertions(+) create mode 100644 rtl/hub.v create mode 100644 rtl/phy_internal.v create mode 100644 tb/hub.py diff --git a/Makefile b/Makefile index 64bf264..0974e16 100644 --- a/Makefile +++ b/Makefile @@ -119,6 +119,7 @@ endef MODULES += axis_replay_buffer MODULES += descramble +MODULES += hub MODULES += led_blinker MODULES += mdio MODULES += mdio_io diff --git a/rtl/hub.v b/rtl/hub.v new file mode 100644 index 0000000..0187b0e --- /dev/null +++ b/rtl/hub.v @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2023 Sean Anderson + */ + +`include "common.vh" + +module hub ( + input clk_125, + input clk_250, + + input [PORT_COUNT - 1:0] indicate_data, + input [PORT_COUNT - 1:0] signal_detect, + output [PORT_COUNT - 1:0] request_data, + + /* Wishbone management */ + output wb_ack, wb_err, + input wb_cyc, wb_stb, wb_we, + input [PORT_COUNT + 5 - 1:0] wb_addr, + input [15:0] wb_data_write, + output [15:0] wb_data_read, + + /* Other status (for LEDs) */ + output reg collision, transmitting, + output [PORT_COUNT - 1:0] link_status, + output [PORT_COUNT - 1:0] receiving +); + + parameter WISHBONE = 1; + parameter PORT_COUNT = 4; + parameter ELASTIC_BUF_SIZE = 3; + parameter ENABLE_COUNTERS = 1; + parameter [23:0] OUI = 0; + parameter [5:0] MODEL = 0; + parameter [3:0] REVISION = 0; + + localparam MII_100_RATIO = 5; + + reg buf_ce, tx_ce; + wire collision_next, transmitting_next; + wire [PORT_COUNT - 1:0] rx_ce, rx_dv, rx_er, tx_en, tx_er, buf_dv, buf_er, col, crs; + wire [PORT_COUNT * 4 - 1:0] rxd, txd, buf_data; + + hub_core #( + .PORT_COUNT(PORT_COUNT) + ) hub ( + .clk(clk_125), + .rx_dv(buf_dv), + .rx_er(buf_er), + .rxd(buf_data), + .tx_en(tx_en), + .tx_er(tx_er), + .txd(txd), + .jam(collision_next), + .activity(transmitting_next) + ); + + wire [PORT_COUNT - 1:0] bus_ack, bus_err, bus_cyc, bus_stb, bus_we; + wire [5 * PORT_COUNT - 1:0] bus_addr; + wire [16 * PORT_COUNT - 1:0] bus_data_write, bus_data_read; + + generate if (WISHBONE) begin + wb_mux #( + .ADDR_WIDTH(5), + .DATA_WIDTH(16), + .SLAVES(PORT_COUNT) + ) mux ( + .m_ack(wb_ack), + .m_err(wb_err), + .m_cyc(wb_cyc), + .m_stb(wb_stb), + .m_we(wb_we), + .m_addr(wb_addr), + .m_data_write(wb_data_write), + .m_data_read(wb_data_read), + .s_ack(bus_ack), + .s_err(bus_err), + .s_cyc(bus_cyc), + .s_stb(bus_stb), + .s_we(bus_we), + .s_addr(bus_addr), + .s_data_write(bus_data_write), + .s_data_read(bus_data_read) + ); + end else begin + assign wb_ack = 0; + assign wb_err = wb_cyc && wb_stb; + assign bus_cyc = {PORT_COUNT{1'b0}}; + assign bus_stb = {PORT_COUNT{1'b0}}; + end endgenerate + + genvar i; + + generate for (i = 0; i < PORT_COUNT; i = i + 1) begin : port + phy_internal #( + .WISHBONE(WISHBONE), + .ENABLE_COUNTERS(ENABLE_COUNTERS), + .OUI(OUI), + .MODEL(MODEL), + .REVISION(REVISION) + ) phy ( + .clk_125(clk_125), + .clk_250(clk_250), + .indicate_data(indicate_data[i]), + .signal_detect(signal_detect[i]), + .request_data(request_data[i]), + .mii_tx_ce(tx_ce), + .mii_tx_en(tx_en[i]), + .mii_tx_er(tx_er[i]), + .mii_txd(txd[i * 4 +: 4]), + .mii_rx_ce(rx_ce[i]), + .mii_rx_dv(rx_dv[i]), + .mii_rx_er(rx_er[i]), + .mii_rxd(rxd[i * 4 +: 4]), + .wb_ack(bus_ack[i]), + .wb_err(bus_err[i]), + .wb_cyc(bus_cyc[i]), + .wb_stb(bus_stb[i]), + .wb_we(bus_we[i]), + .wb_addr(bus_addr[i * 5 +: 5]), + .wb_data_write(bus_data_write[i * 16 +: 16]), + .wb_data_read(bus_data_read[i * 16 +: 16]), + .link_status(link_status[i]), + .receiving(receiving[i]) + ); + + mii_elastic_buffer #( + .BUF_SIZE(ELASTIC_BUF_SIZE) + ) buffer ( + .clk(clk_125), + .tx_ce(rx_ce[i]), + .tx_en(rx_dv[i]), + .tx_er(rx_er[i]), + .txd(rxd[i * 4 +: 4]), + .rx_ce(buf_ce), + .rx_dv(buf_dv[i]), + .rx_er(buf_er[i]), + .rxd(buf_data[i * 4 +: 4]) + ); + end endgenerate + + reg buf_ce_next; + reg [3:0] tx_counter, tx_counter_next; + + initial begin + buf_ce = 0; + tx_ce = 0; + tx_counter = MII_100_RATIO - 1; + end + + always @(*) begin + buf_ce_next = 0; + tx_counter_next = tx_counter - 1; + if (!tx_counter) begin + tx_counter_next = MII_100_RATIO - 1; + buf_ce_next = 1; + end + end + + always @(posedge clk_125) begin + buf_ce <= buf_ce_next; + tx_ce <= buf_ce; + tx_counter <= tx_counter_next; + if (tx_ce) begin + collision <= collision_next; + transmitting <= transmitting_next; + end + end + +endmodule diff --git a/rtl/phy_internal.v b/rtl/phy_internal.v new file mode 100644 index 0000000..31b869c --- /dev/null +++ b/rtl/phy_internal.v @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2022 Sean Anderson + * + * PHY with internal (unexposed) MII and a wishbone management interface + */ + +`include "common.vh" +`include "io.vh" + +module phy_internal ( + input clk_125, + input clk_250, + + /* DP83223 */ + input indicate_data, + input signal_detect, + output request_data, + + /* MII */ + input mii_tx_ce, + input mii_tx_en, + input mii_tx_er, + input [3:0] mii_txd, + + output mii_rx_ce, + output mii_rx_dv, + output mii_rx_er, + output [3:0] mii_rxd, + + output mii_col, + output mii_crs, + + /* Wishbone management */ + output wb_ack, wb_err, + input wb_cyc, wb_stb, wb_we, + input [4:0] wb_addr, + input [15:0] wb_data_write, + output [15:0] wb_data_read, + + /* Control/status */ + output link_status, + output receiving, + output false_carrier, + output symbol_error +); + + parameter WISHBONE = 1; + parameter ENABLE_COUNTERS = 1; + parameter [23:0] OUI = 0; + parameter [5:0] MODEL = 0; + parameter [3:0] REVISION = 0; + + wire isolate, tx_data, signal_status, link_monitor_test, descrambler_test; + wire loopback, coltest; + wire [1:0] rx_data, rx_data_valid; + + phy_core phy_core ( + .clk(clk_125), + .tx_data(tx_data), + .rx_data(rx_data), + .rx_data_valid(rx_data_valid), + .signal_status(signal_status), + .tx_ce(mii_tx_ce), + .tx_en(mii_tx_en), + .txd(mii_txd), + .tx_er(mii_tx_er), + .rx_ce(mii_rx_ce), + .rx_dv(mii_rx_dv), + .rxd(mii_rxd), + .rx_er(mii_rx_er), + .crs(mii_crs), + .col(mii_col), + .loopback(loopback), + .coltest(coltest), + .link_monitor_test_mode(link_monitor_test), + .descrambler_test_mode(descrambler_test), + .link_status(link_status), + .receiving(receiving), + .false_carrier(false_carrier), + .symbol_error(symbol_error) + ); + + pmd_dp83223 pmd ( + .clk_125(clk_125), + .clk_250(clk_250), + .signal_detect(signal_detect), + .request_data(request_data), + .indicate_data(indicate_data), + .tx_data(tx_data), + .rx_data(rx_data), + .rx_data_valid(rx_data_valid), + .signal_status(signal_status), + .loopback(loopback) + ); + + generate if (WISHBONE) begin + mdio_regs #( + .OUI(OUI), + .MODEL(MODEL), + .REVISION(REVISION), + .EMULATE_PULLUP(1'b1), + .ENABLE_COUNTERS(ENABLE_COUNTERS) + ) mdio_regs ( + .clk(clk_125), + .ack(wb_ack), + .err(wb_err), + .cyc(wb_cyc), + .stb(wb_stb), + .we(wb_we), + .addr(wb_addr), + .data_write(wb_data_write), + .data_read(wb_data_read), + .link_status(link_status), + .negative_wraparound(!rx_data_valid), + .positive_wraparound(rx_data_valid[1]), + .false_carrier(false_carrier), + .symbol_error(symbol_error), + .loopback(loopback), + .isolate(isolate), + .coltest(coltest), + .descrambler_test(descrambler_test), + .link_monitor_test(link_monitor_test) + ); + end else begin + assign wb_ack = 0; + assign wb_err = wb_cyc && wb_stb; + assign loopback = 0; + assign coltest = 0; + assign descrambler_test = 0; + assign link_monitor_test = 0; + end endgenerate + +endmodule diff --git a/tb/hub.py b/tb/hub.py new file mode 100644 index 0000000..990f760 --- /dev/null +++ b/tb/hub.py @@ -0,0 +1,101 @@ +# 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.triggers import ClockCycles, Combine, FallingEdge, Join, RisingEdge, Timer +from cocotb.types import LogicArray + +from .descramble import scramble +from .mdio_regs import BMSR, BMSR_LSTATUS, VCR, VCR_LTEST, wb_xfer +from .nrzi_encode import nrzi_encode +from .nrzi_decode import nrzi_decode +from .pcs_tx import as_nibbles, mii_send_packet, pcs_recv_packet +from .pcs_rx import frame, mii_recv_packet +from .scramble import descramble +from .util import BIT, alist + +@cocotb.test(timeout_time=5, timeout_unit='us') +async def test_hub(hub): + hub.clk_125.value = BinaryValue('Z') + hub.clk_250.value = BinaryValue('Z') + hub.signal_detect.value = 0 + hub.wb_cyc.value = 1 + + await Timer(1) + await cocotb.start(Clock(hub.clk_125, 8, units='ns').start()) + await cocotb.start(Clock(hub.clk_250, 4, units='ns').start()) + + wb = { + 'clk': hub.clk_125, + 'cyc': hub.wb_cyc, + 'stb': hub.wb_stb, + 'we': hub.wb_we, + 'addr': hub.wb_addr, + 'data_write': hub.wb_data_write, + 'data_read': hub.wb_data_read, + 'ack': hub.wb_ack, + 'err': hub.wb_err, + } + + # Enable fast link stabilization for testing + for i in range(4): + await wb_xfer(wb, BIT(i + 5) + VCR, VCR_LTEST) + + packet = list(as_nibbles((0x55, *b"Hello world!"))) + packet_bits = list(itertools.chain.from_iterable(frame(packet))) + itertools.chain(itertools.repeat(1, 120), packet_bits, itertools.repeat(1)) + + async def send_rx(i, bits): + hub.signal_detect[i].value = 1 + for bit in nrzi_encode(scramble(bits)): + hub.indicate_data[i].value = bit + await Timer(8, units='ns') + hub.signal_detect[i].value = 0 + hub.indicate_data[i].value = BinaryValue('X') + + async def recv_tx(i, packets): + async def bits(): + await ClockCycles(hub.clk_125, 1) + while True: + await RisingEdge(hub.clk_125) + yield hub.request_data[i].value + + data = descramble(nrzi_decode(bits())) + for expected, valid in packets: + actual = await alist(pcs_recv_packet(None, data)) + if valid: + assert actual == expected + else: + assert actual != expected + + await cocotb.start(send_rx(0, itertools.chain( + itertools.repeat(1, 120), + packet_bits, + itertools.repeat(1, 120), + packet_bits, + itertools.repeat(1), + ))) + await cocotb.start(send_rx(1, itertools.chain( + itertools.repeat(1, 300), + packet_bits, + itertools.repeat(1), + ))) + await cocotb.start(send_rx(2, itertools.repeat(1))) + await cocotb.start(send_rx(3, itertools.repeat(1))) + + receivers = [ + await cocotb.start(recv_tx(0, ((packet, False),))), + await cocotb.start(recv_tx(1, ((packet, True), (packet, False)))), + await cocotb.start(recv_tx(2, ((packet, True), (packet, False)))), + await cocotb.start(recv_tx(3, ((packet, True), (packet, False)))), + ] + + await Combine(*(Join(t) for t in receivers)) + + for i in range(4): + assert not await wb_xfer(wb, BIT(i + 5) + BMSR) & BMSR_LSTATUS + assert await wb_xfer(wb, BIT(i + 5) + BMSR) & BMSR_LSTATUS