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:
parent
4cc574048d
commit
d9602b6f78
1
Makefile
1
Makefile
|
@ -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))
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue