Add MDIO I/O module

This module implements the I/O portion of the MII management interface.
The output is delayed by 2 clocks in order to ensure that the external
level shifter has switched directions before we drive it. The latency
increase (around 16 ns) is not consequential, since we have around 300
ns from the rising edge of MDC before MDIO has to be valid.

On the other end, the timing requirements for MDIO driven by the STA are
very lenient (for them); MDIO only has to be valid for 10 ns on either
side of the rising edge of MDC. This effectively means we must sample
MDIO synchronously to MDC (not easy with nextpnr), or oversample by 50x.
Fortunately, we have a 125 MHz clock which the rest of the phty runs off
of. However, this basically makes 10x oversampling with the MII clock
impossible.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2022-08-27 16:00:37 -04:00
parent f1b345299e
commit dd4183991d
3 changed files with 137 additions and 1 deletions

View File

@ -63,7 +63,7 @@ endef
%.post.fst: rtl/%.post.vvp tb/%.py FORCE
$(run-vvp)
MODULES := pcs pmd nrzi_encode nrzi_decode scramble descramble mdio
MODULES := pcs pmd nrzi_encode nrzi_decode scramble descramble mdio mdio_io
.PHONY: test
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))

83
rtl/mdio_io.v Normal file
View File

@ -0,0 +1,83 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
*/
`include "common.vh"
`include "io.vh"
module mdio_io (
input clk,
input mdc,
inout mdio,
output reg mdio_oe,
input mdo,
input mdo_valid,
output reg ce,
output reg mdi
);
wire ce_next;
reg mdi_next;
reg [1:0] last_mdc;
/* Two clock delay to allow the level shifter to reverse direction */
reg [2:0] oe = 0;
`ifdef SYNTHESIS
SB_IO #(
.PIN_TYPE(`PIN_OUTPUT_NEVER | `PIN_INPUT_REGISTERED)
) mdc_pin (
.PACKAGE_PIN(mdc),
.INPUT_CLK(clk),
.D_IN_0(last_mdc[0])
);
SB_IO #(
.PIN_TYPE(`PIN_OUTPUT_REGISTERED | `PIN_OUTPUT_ENABLE | `PIN_INPUT_REGISTERED)
) mdio_pin (
.PACKAGE_PIN(mdio),
.INPUT_CLK(clk),
.OUTPUT_CLK(clk),
.OUTPUT_ENABLE(oe[2]),
.D_OUT_0(mdo),
.D_IN_0(mdi_next),
);
SB_IO #(
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED)
) mdio_oe_pin (
.PACKAGE_PIN(mdio_oe),
.OUTPUT_CLK(clk),
.D_OUT_0(mdo_valid),
);
`else
reg mdio_next;
always @(posedge clk) begin
last_mdc[0] <= mdc;
mdi_next <= mdio;
mdio_next <= mdo;
mdio_oe <= mdo_valid;
end
assign mdio = oe[2] ? mdio_next : 1'bZ;
`endif
assign ce_next = last_mdc[0] && !last_mdc[1];
always @(posedge clk) begin
mdi <= mdi_next;
last_mdc[1] <= last_mdc[0];
ce <= ce_next;
mdio_oe <= mdo_valid;
if (mdo_valid)
oe <= { oe[1:0], mdo_valid };
else
oe <= 0;
end
`DUMP(0)
endmodule

53
tb/mdio_io.py Normal file
View File

@ -0,0 +1,53 @@
# SPDX-License-Identifier: AGPL-3.0-Only
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
import random
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles, Edge, FallingEdge, First, RisingEdge, Timer
from cocotb.types import LogicArray
@cocotb.test(timeout_time=50, timeout_unit='us')
async def test_io(io):
io.mdio.value = LogicArray('X')
io.mdc.value = 0
io.mdo_valid.value = 0
await Timer(1)
await cocotb.start(Clock(io.clk, 8, units='ns').start())
# random phase
await Timer(random.randrange(1, 9), units='ns')
await cocotb.start(Clock(io.mdc, 400, units='ns').start())
ins = [random.randrange(2) for _ in range(10)]
await FallingEdge(io.mdc)
async def send_ins():
for bit in ins:
await Timer(190, 'ns')
io.mdio.value = bit
await Timer(20, 'ns')
io.mdio.value = LogicArray('X')
await FallingEdge(io.mdc)
await cocotb.start(send_ins())
for bit in ins:
while not io.ce.value:
await RisingEdge(io.clk)
assert io.mdi.value == bit
await FallingEdge(io.clk)
outs = [random.randrange(2) for _ in range(10)]
io.mdio.value = LogicArray('Z')
await FallingEdge(io.clk)
for bit in outs:
io.mdo.value = bit
io.mdo_valid.value = 1
await FallingEdge(io.clk)
assert io.mdio_oe.value
await FallingEdge(io.clk)
await FallingEdge(io.clk)
assert io.mdio.value == bit
io.mdo_valid.value = 0
await FallingEdge(io.clk)
assert io.mdio.value.binstr == 'z'