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:
parent
f1b345299e
commit
dd4183991d
2
Makefile
2
Makefile
|
@ -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))
|
||||
|
|
|
@ -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
|
|
@ -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'
|
Loading…
Reference in New Issue