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 += axis_replay_buffer
|
||||||
MODULES += descramble
|
MODULES += descramble
|
||||||
|
MODULES += hub
|
||||||
MODULES += led_blinker
|
MODULES += led_blinker
|
||||||
MODULES += mdio
|
MODULES += mdio
|
||||||
MODULES += mdio_io
|
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