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 <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2023-02-20 18:24:49 -05:00
parent cc29d2050c
commit b351beb9a0
4 changed files with 406 additions and 0 deletions

View File

@ -119,6 +119,7 @@ endef
MODULES += axis_replay_buffer
MODULES += descramble
MODULES += hub
MODULES += led_blinker
MODULES += mdio
MODULES += mdio_io

170
rtl/hub.v Normal file
View File

@ -0,0 +1,170 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
*/
`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

134
rtl/phy_internal.v Normal file
View File

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

101
tb/hub.py Normal file
View File

@ -0,0 +1,101 @@
# 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.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