Add MII output receive interface
This generates the appropriate output for MII receive signals. Because we don't have a clock synchronous to the recieved data, we may occasionally have some cycles which are 32 ns or 48 ns long (instead of the nominal 40 ns). This distorts the duty cycle to 38% or 58%, respectively, which is within the specified 35% to 65%. This does change the frequency to either 31 MHz or 21 MHz, respectively, which *is* a violation of the spec. This could be avoided by introducing a FIFO to smooth out any variations in jitter, like what RMII does. The generation of rx_clk is a bit tricky. We can use a combinatorial signal for the posedge, since that is what the rest of the logic is referenced to, However, we need to register the negedge to prevent an early (or late) ce from modifying the duty cycle. Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
parent
27d4c6457e
commit
e2544d702f
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 mdio_io
|
||||
MODULES := pcs pmd nrzi_encode nrzi_decode scramble descramble mdio mdio_io mii_io_rx
|
||||
|
||||
.PHONY: test
|
||||
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||
/*
|
||||
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
*/
|
||||
|
||||
`include "common.vh"
|
||||
`include "io.vh"
|
||||
|
||||
module mii_io_rx (
|
||||
/* On-chip */
|
||||
input clk,
|
||||
input ce,
|
||||
input valid,
|
||||
input err,
|
||||
input [3:0] data,
|
||||
|
||||
/* Off-chip */
|
||||
output reg rx_clk,
|
||||
output reg rx_dv,
|
||||
output reg rx_er,
|
||||
output reg [3:0] rxd
|
||||
);
|
||||
|
||||
reg rx_clk_p_next, rx_clk_n, rx_clk_n_next;
|
||||
reg [1:0] state = HIGH, state_next;
|
||||
|
||||
parameter LOW = 2;
|
||||
parameter RISING = 1;
|
||||
parameter HIGH = 0;
|
||||
|
||||
always @(*) begin
|
||||
rx_clk_p_next = 0;
|
||||
rx_clk_n_next = 0;
|
||||
if (ce) begin
|
||||
state_next = LOW;
|
||||
end else case (state)
|
||||
LOW: begin
|
||||
state_next = RISING;
|
||||
end
|
||||
RISING: begin
|
||||
state_next = HIGH;
|
||||
rx_clk_n_next = 1;
|
||||
end
|
||||
HIGH: begin
|
||||
state_next = HIGH;
|
||||
rx_clk_p_next = 1;
|
||||
rx_clk_n_next = 1;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
state <= state_next;
|
||||
rx_clk_n <= rx_clk_n_next;
|
||||
end
|
||||
|
||||
`ifdef SYNTHESIS
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_DDR)
|
||||
) rx_clk_pin (
|
||||
.PACKAGE_PIN(rx_clk),
|
||||
.OUTPUT_CLK(clk),
|
||||
.D_OUT_0(rx_clk_p_next),
|
||||
.D_OUT_1(rx_clk_n)
|
||||
);
|
||||
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED)
|
||||
) rx_dv_pin (
|
||||
.PACKAGE_PIN(rx_dv),
|
||||
.CLOCK_ENABLE(ce),
|
||||
.OUTPUT_CLK(clk),
|
||||
.D_OUT_0(valid)
|
||||
);
|
||||
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED)
|
||||
) rx_er_pin (
|
||||
.PACKAGE_PIN(rx_er),
|
||||
.CLOCK_ENABLE(ce),
|
||||
.OUTPUT_CLK(clk),
|
||||
.D_OUT_0(err)
|
||||
);
|
||||
|
||||
genvar i;
|
||||
generate for (i = 0; i < 4; i = i + 1) begin
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED)
|
||||
) rxd_pin (
|
||||
.PACKAGE_PIN(rxd[i]),
|
||||
.CLOCK_ENABLE(ce),
|
||||
.OUTPUT_CLK(clk),
|
||||
.D_OUT_0(data[i])
|
||||
);
|
||||
end
|
||||
endgenerate
|
||||
`else
|
||||
always @(posedge clk) begin
|
||||
rx_clk <= rx_clk_p_next;
|
||||
if (ce) begin
|
||||
rx_dv <= valid;
|
||||
rx_er <= err;
|
||||
rxd <= data;
|
||||
end
|
||||
end
|
||||
|
||||
always @(negedge clk)
|
||||
rx_clk <= rx_clk_n;
|
||||
`endif
|
||||
|
||||
`DUMP(0)
|
||||
|
||||
endmodule
|
|
@ -0,0 +1,80 @@
|
|||
# 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
|
||||
|
||||
from .util import ClockEnable
|
||||
|
||||
@cocotb.test(timeout_time=500, timeout_unit='ns')
|
||||
async def test_io(io):
|
||||
io.valid.value = LogicArray('X')
|
||||
io.err.value = LogicArray('X')
|
||||
io.data.value = LogicArray('X' * 4)
|
||||
io.ce.value = 0
|
||||
await Timer(1)
|
||||
await cocotb.start(Clock(io.clk, 8, units='ns').start())
|
||||
await ClockCycles(io.clk, 1)
|
||||
|
||||
async def clock_monitor():
|
||||
await FallingEdge(io.rx_clk)
|
||||
fall = cocotb.utils.get_sim_time('ns')
|
||||
while True:
|
||||
await RisingEdge(io.rx_clk)
|
||||
rise = cocotb.utils.get_sim_time('ns')
|
||||
assert round(rise - fall) == 20
|
||||
await FallingEdge(io.rx_clk)
|
||||
fall = cocotb.utils.get_sim_time('ns')
|
||||
high = round(fall - rise)
|
||||
assert high >= 12 and high <= 28
|
||||
await cocotb.start(clock_monitor())
|
||||
|
||||
async def send_datum(valid, err, data, delay, early=True):
|
||||
if early:
|
||||
await Timer(1)
|
||||
io.valid.value = valid
|
||||
io.err.value = err
|
||||
io.data.value = data
|
||||
io.ce.value = 1
|
||||
await ClockCycles(io.clk, 2 if early else 1, False)
|
||||
io.ce.value = 0
|
||||
io.valid.value = LogicArray('X')
|
||||
io.err.value = LogicArray('X')
|
||||
io.data.value = LogicArray('X' * 4)
|
||||
await ClockCycles(io.clk, delay - 1, early)
|
||||
|
||||
async def send_data():
|
||||
await send_datum(1, 0, 1, 5, True)
|
||||
await send_datum(0, 1, 2, 4, True)
|
||||
await send_datum(1, 0, 3, 5, True)
|
||||
await send_datum(0, 1, 4, 6, True)
|
||||
await send_datum(1, 0, 5, 5, True)
|
||||
await FallingEdge(io.clk)
|
||||
await send_datum(0, 1, 6, 5, False)
|
||||
await send_datum(1, 0, 7, 4, False)
|
||||
await send_datum(0, 1, 8, 5, False)
|
||||
await send_datum(1, 0, 9, 6, False)
|
||||
await send_datum(0, 1, 10, 5, False)
|
||||
await cocotb.start(send_data())
|
||||
|
||||
async def recv_datum(valid, err, data):
|
||||
await RisingEdge(io.rx_clk)
|
||||
assert io.rx_dv.value == valid
|
||||
assert io.rx_er.value == err
|
||||
assert io.rxd.value == data
|
||||
|
||||
await RisingEdge(io.rx_clk)
|
||||
await recv_datum(1, 0, 1)
|
||||
await recv_datum(0, 1, 2)
|
||||
await recv_datum(1, 0, 3)
|
||||
await recv_datum(0, 1, 4)
|
||||
await recv_datum(1, 0, 5)
|
||||
await recv_datum(0, 1, 6)
|
||||
await recv_datum(1, 0, 7)
|
||||
await recv_datum(0, 1, 8)
|
||||
await recv_datum(1, 0, 9)
|
||||
await recv_datum(0, 1, 10)
|
Loading…
Reference in New Issue