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)
|
$(run-vvp)
|
||||||
|
|
||||||
MODULES := pcs pmd_io nrzi_encode nrzi_decode scramble descramble mdio mdio_io mii_io_rx mii_io_tx
|
MODULES := pcs pmd_io nrzi_encode nrzi_decode scramble descramble mdio mdio_io mii_io_rx mii_io_tx
|
||||||
|
MODULES += mdio_regs
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))
|
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