Add MII management functions

This adds a module implementing the the MII management functions (the
MDIO regs). For the moment, we just implement the standard registers.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2022-08-29 21:37:10 -04:00
parent 4cc574048d
commit d9602b6f78
3 changed files with 250 additions and 0 deletions

View File

@ -64,6 +64,7 @@ endef
$(run-vvp)
MODULES := pcs pmd_io nrzi_encode nrzi_decode scramble descramble mdio mdio_io mii_io_rx mii_io_tx
MODULES += mdio_regs
.PHONY: test
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))

154
rtl/mdio_regs.v Normal file
View File

@ -0,0 +1,154 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
*/
`include "common.vh"
module mdio_regs (
/* Wishbone */
input clk,
output reg ack, err,
input cyc, stb, we,
input [4:0] addr,
input [15:0] data_write,
output reg [15:0] data_read,
/* Control signals */
input link_status,
output reg loopback,
output reg pdown,
output reg isolate,
output reg coltest
);
/* The current price of a CID is $805... */
parameter [23:0] OUI = 0;
parameter [5:0] MODEL = 0;
parameter [3:0] REVISION = 0;
/*
* Normally, this module will assert err when read/writing to an
* unknown register. The master will detect this and won't drive MDIO
* line. However, this might be undesirable if there is no external
* MDIO bus. Setting this parameter to 0 will cause it to ack all
* transactions. Writes to unknown registers will be ignored, and
* reads from unknown registers will yield 16'hffff, emulating
* a pull-up on MDIO.
*/
parameter EMULATE_PULLUP = 0;
localparam BMCR = 0;
localparam BMSR = 1;
localparam ID1 = 2;
localparam ID2 = 3;
localparam BMCR_RESET = 15;
localparam BMCR_LOOPBACK = 14;
localparam BMCR_SPEED_LSB = 13;
localparam BMCR_PDOWN = 11;
localparam BMCR_ISOLATE = 10;
localparam BMCR_DUPLEX = 8;
localparam BMCR_COLTEST = 7;
localparam BMCR_SPEED_MSB = 6;
localparam BMSR_100FULL = 14;
localparam BMSR_100HALF = 13;
localparam BMSR_LSTATUS = 2;
localparam BMSR_EXTCAP = 0;
integer i;
reg duplex, link_status_latched;
reg loopback_next, pdown_next, isolate_next, duplex_next, coltest_next;
reg link_status_latched_next;
reg [15:0] data_read_next;
initial begin
loopback = 0;
pdown = 0;
isolate = 1;
duplex = 0;
coltest = 0;
link_status_latched = 0;
end
always @(*) begin
loopback_next = loopback;
pdown_next = pdown;
isolate_next = isolate;
duplex_next = duplex;
coltest_next = coltest;
link_status_latched_next = link_status_latched && link_status;
data_read_next = 0;
ack = cyc && stb;
err = 0;
case (addr)
BMCR: begin
data_read_next[BMCR_LOOPBACK] = loopback;
data_read_next[BMCR_SPEED_LSB] = 1; /* 100 Mb/s */
data_read_next[BMCR_PDOWN] = pdown;
data_read_next[BMCR_ISOLATE] = isolate;
data_read_next[BMCR_DUPLEX] = duplex;
data_read_next[BMCR_COLTEST] = coltest;
if (cyc && stb && we) begin
loopback_next = data_write[BMCR_LOOPBACK];
pdown_next = data_write[BMCR_PDOWN];
isolate_next = data_write[BMCR_ISOLATE];
duplex_next = data_write[BMCR_DUPLEX];
coltest_next = data_write[BMCR_COLTEST];
if (data_write[BMCR_RESET]) begin
loopback_next = 0;
pdown_next = 0;
isolate_next = 1;
duplex_next = 0;
coltest_next = 0;
link_status_latched_next = link_status;
end
end
end
BMSR: begin
data_read_next[BMSR_100FULL] = 1;
data_read_next[BMSR_100HALF] = 1;
data_read_next[BMSR_LSTATUS] = link_status_latched;
data_read_next[BMSR_EXTCAP] = 1;
if (cyc && stb && !we)
link_status_latched_next = link_status;
end
ID1: begin
for (i = 0; i < 16; i = i + 1)
data_read_next[i] = OUI[17 - i];
end
ID2: begin
data_read_next[3:0] = REVISION;
data_read_next[9:4] = MODEL;
for (i = 0; i < 6; i = i + 1)
data_read_next[i + 4] = OUI[23 - i];
end
default: begin
if (EMULATE_PULLUP) begin
data_read_next = 16'hFFFF;
end else begin
ack = 0;
err = stb && cyc;
data_read_next = 16'hXXXX;
end
end
endcase
end
always @(posedge clk) begin
loopback <= loopback_next;
pdown <= pdown_next;
isolate <= isolate_next;
duplex <= duplex_next;
coltest <= coltest_next;
link_status_latched <= link_status_latched_next;
data_read <= data_read_next;
end
`DUMP(0)
endmodule

95
tb/mdio_regs.py Normal file
View File

@ -0,0 +1,95 @@
# SPDX-License-Identifier: AGPL-3.0-Only
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import FallingEdge, Timer
from cocotb.types import LogicArray
def BIT(n):
return 1 << n
BMCR = 0
BMSR = 1
PHYID1 = 2
PHYID2 = 3
EXTSTATUS = 15
BMCR_RESET = BIT(15)
BMCR_LOOPBACK = BIT(14)
BMCR_SPEED_LSB = BIT(13)
BMCR_PDOWN = BIT(11)
BMCR_ISOLATE = BIT(10)
BMCR_DUPLEX = BIT(8)
BMCR_COLTEST = BIT(7)
BMCR_SPEED_MSB = BIT(6)
BMSR_100BASEXFD = BIT(14)
BMSR_100BASEXHD = BIT(13)
BMSR_LSTATUS = BIT(2)
BMSR_EXTCAP = BIT(0)
@cocotb.test(timeout_time=1, timeout_unit='us')
async def test_mdio(regs):
regs.cyc.value = 1
regs.stb.value = 0
regs.link_status.value = 1
await Timer(1)
await cocotb.start(Clock(regs.clk, 8, units='ns').start())
async def xfer(regad, data=None):
await FallingEdge(regs.clk)
regs.stb.value = 1
regs.addr.value = regad
if data is None:
regs.we.value = 0
else:
regs.we.value = 1
regs.data_write.value = data
await FallingEdge(regs.clk)
assert regs.ack.value or regs.err.value
regs.stb.value = 0
regs.we.value = LogicArray('X')
regs.addr.value = LogicArray('X' * 4)
regs.data_write.value = LogicArray('X' * 16)
if data is None and regs.ack.value:
return regs.data_read.value
async def bmcr_toggle(bit, signal):
if signal:
assert not signal.value
await xfer(BMCR, bit)
if signal:
assert signal.value
assert await xfer(BMCR) == (BMCR_SPEED_LSB | bit)
await xfer(BMCR, 0)
if signal:
assert not signal.value
assert await xfer(BMCR) == (BMCR_SPEED_LSB | BMCR_ISOLATE)
await bmcr_toggle(BMCR_LOOPBACK, regs.loopback)
await bmcr_toggle(BMCR_PDOWN, regs.pdown)
await bmcr_toggle(BMCR_ISOLATE, regs.isolate)
await bmcr_toggle(BMCR_DUPLEX, None)
await bmcr_toggle(BMCR_COLTEST, regs.coltest)
await xfer(BMCR, BMCR_RESET)
assert await xfer(BMCR) == (BMCR_SPEED_LSB | BMCR_ISOLATE)
await xfer(BMSR, 0xffff)
assert await xfer(BMSR) == (BMSR_100BASEXFD | BMSR_100BASEXHD | BMSR_LSTATUS | BMSR_EXTCAP)
regs.link_status.value = 0
assert not await xfer(BMSR) & BMSR_LSTATUS
regs.link_status.value = 1
assert not await xfer(BMSR) & BMSR_LSTATUS
assert await xfer(BMSR) & BMSR_LSTATUS
await xfer(PHYID1, 0xffff)
assert await xfer(PHYID1) == 0
await xfer(PHYID2, 0xffff)
assert await xfer(PHYID2) == 0
# I'm pretty sure this register will never be implemented
assert await xfer(EXTSTATUS) is None
assert await xfer(EXTSTATUS, 0) is None