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:
parent
e2544d702f
commit
0c2989b13c
2
Makefile
2
Makefile
|
@ -63,7 +63,7 @@ endef
|
||||||
%.post.fst: rtl/%.post.vvp tb/%.py FORCE
|
%.post.fst: rtl/%.post.vvp tb/%.py FORCE
|
||||||
$(run-vvp)
|
$(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
|
.PHONY: test
|
||||||
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))
|
test: $(addsuffix .fst,$(MODULES)) $(addsuffix .post.fst,$(MODULES))
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue