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 <seanga2@gmail.com>
This commit is contained in:
parent
cc29d2050c
commit
b351beb9a0
1
Makefile
1
Makefile
|
@ -119,6 +119,7 @@ endef
|
|||
|
||||
MODULES += axis_replay_buffer
|
||||
MODULES += descramble
|
||||
MODULES += hub
|
||||
MODULES += led_blinker
|
||||
MODULES += mdio
|
||||
MODULES += mdio_io
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue