Add MII input transmit interface

The actitecture is overall fairly similar to the receive interface,
except that the directions are mostly different. The timing is a bit
easier, since we control the ce signal. Data is sampled one clock before
tx_clk goes high, which is the earliest that it is guarantee'd to be
valid. We could get an extra half-clock by having tx_clk go high at the
negedge of clk, but it's unnecessary at the moment.

Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
Sean Anderson 2022-08-28 17:16:33 -04:00
parent e2544d702f
commit 0c2989b13c
3 changed files with 163 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 mdio_io mii_io_rx
MODULES := pcs pmd nrzi_encode nrzi_decode scramble descramble mdio mdio_io mii_io_rx mii_io_tx
.PHONY: test
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))

111
rtl/mii_io_tx.v Normal file
View File

@ -0,0 +1,111 @@
// SPDX-License-Identifier: AGPL-3.0-Only
/*
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
*/
`include "common.vh"
`include "io.vh"
module mii_io_tx (
/* On-chip */
input clk,
output reg ce,
output reg enable,
output reg err,
output reg [3:0] data,
/* Off-chip */
output reg tx_clk,
input tx_en,
input tx_er,
input [3:0] txd
);
reg ce_next;
reg tx_clk_p_next, tx_clk_n, tx_clk_n_next;
reg [2:0] counter, counter_next;
/* I have no idea why we need to use initial... */
initial counter = 4;
always @(*) begin
tx_clk_p_next = 0;
tx_clk_n_next = 0;
ce_next = 0;
counter_next = counter - 1;
case (counter)
4, 3: begin
tx_clk_p_next = 1;
tx_clk_n_next = 1;
end
2: tx_clk_p_next = 1;
1: ;
0: begin
ce_next = 1;
counter_next = 4;
end
endcase
end
always @(posedge clk) begin
counter <= counter_next;
tx_clk_n <= tx_clk_n_next;
ce <= ce_next;
end
`ifdef SYNTHESIS
SB_IO #(
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_DDR)
) tx_clk_pin (
.PACKAGE_PIN(tx_clk),
.OUTPUT_CLK(clk),
.D_OUT_0(tx_clk_p_next),
.D_OUT_1(tx_clk_n)
);
SB_IO #(
.PIN_TYPE(`PIN_INPUT_REGISTERED)
) tx_en_pin (
.PACKAGE_PIN(tx_en),
.CLOCK_ENABLE(ce_next),
.INPUT_CLK(clk),
.D_IN_0(enable)
);
SB_IO #(
.PIN_TYPE(`PIN_INPUT_REGISTERED)
) tx_er_pin (
.PACKAGE_PIN(tx_er),
.CLOCK_ENABLE(ce_next),
.INPUT_CLK(clk),
.D_IN_0(err)
);
genvar i;
generate for (i = 0; i < 4; i = i + 1) begin
SB_IO #(
.PIN_TYPE(`PIN_INPUT_REGISTERED)
) txd_pin (
.PACKAGE_PIN(txd[i]),
.CLOCK_ENABLE(ce_next),
.INPUT_CLK(clk),
.D_IN_0(data[i])
);
end
endgenerate
`else
always @(posedge clk) begin
tx_clk <= tx_clk_p_next;
if (ce_next) begin
enable <= tx_en;
err <= tx_er;
data <= txd;
end
end
always @(negedge clk)
tx_clk <= tx_clk_n;
`endif
`DUMP(0)
endmodule

51
tb/mii_io_tx.py Normal file
View File

@ -0,0 +1,51 @@
# 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.tx_en.value = LogicArray('X')
io.tx_er.value = LogicArray('X')
io.txd.value = LogicArray('X' * 4)
await Timer(1)
await cocotb.start(Clock(io.clk, 8, units='ns').start())
async def send_datum(enable, err, data):
await RisingEdge(io.tx_clk)
io.tx_en.value = LogicArray('X')
io.tx_er.value = LogicArray('X')
io.txd.value = LogicArray('X' * 4)
await Timer(25, 'ns')
io.tx_en.value = enable
io.tx_er.value = err
io.txd.value = data
async def send_data():
await send_datum(0, 1, 1)
await send_datum(1, 0, 2)
await send_datum(0, 1, 3)
await send_datum(1, 0, 4)
await send_datum(0, 1, 5)
await cocotb.start(send_data())
async def recv_datum(enable, err, data):
await RisingEdge(io.ce)
await RisingEdge(io.clk)
assert io.ce.value
assert io.enable.value == enable
assert io.err.value == err
assert io.data.value == data
await recv_datum(0, 1, 1)
await recv_datum(1, 0, 2)
await recv_datum(0, 1, 3)
await recv_datum(1, 0, 4)
await recv_datum(0, 1, 5)