From 2eac757fd742fbfddad212abba1b844dd76969f8 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Wed, 30 Nov 2022 18:05:30 -0500 Subject: [PATCH] Add DP83223-based PMD This adds the integrated PMD module to be used with the DP83223. It contains NRZI en/decoding as well as the I/O interfaces. The rx I/O was added a while back, and the tx is just the I/O cell. Signed-off-by: Sean Anderson --- Makefile | 1 + rtl/pmd_dp83223.v | 79 +++++++++++++++++++++++++++++++++++++++++++++++ tb/pmd_dp83223.py | 77 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 rtl/pmd_dp83223.v create mode 100644 tb/pmd_dp83223.py diff --git a/Makefile b/Makefile index 17f9cd0..eceb588 100644 --- a/Makefile +++ b/Makefile @@ -102,6 +102,7 @@ MODULES += nrzi_encode MODULES += pcs_rx MODULES += pcs_tx MODULES += phy_core +MODULES += pmd_dp83223 MODULES += pmd_dp83223_rx MODULES += scramble diff --git a/rtl/pmd_dp83223.v b/rtl/pmd_dp83223.v new file mode 100644 index 0000000..6b413c7 --- /dev/null +++ b/rtl/pmd_dp83223.v @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2022 Sean Anderson + */ + +`include "common.vh" +`include "io.vh" + +`timescale 1ns/1ps + +module pmd_dp83223 ( + input clk_250, + input clk_125, + + /* I/O */ + input indicate_data, + input signal_detect, + output reg request_data, + + /* "PMD" */ + input tx_data, + output [1:0] rx_data, + output [1:0] rx_data_valid, + output reg signal_status, + + /* Control */ + input loopback +); + + wire tx_nrzi; + + nrzi_encode encoder ( + .clk(clk_125), + .nrz(tx_data), + .nrzi(tx_nrzi) + ); + +`ifdef SYNTHESIS + SB_IO #( + .PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED), + ) tx_data_pin ( + .PACKAGE_PIN(request_data), + .OUTPUT_CLK(clk_125), + .D_OUT_0(loopback ? 1'b0 : tx_nrzi) + ); +`else + always @(posedge clk_125) + request_data <= loopback ? 1'b0 : tx_nrzi; +`endif + + wire signal_status_nrzi; + wire [1:0] rx_nrzi, rx_nrzi_valid; + + pmd_dp83223_rx rx ( + .clk_125(clk_125), + .clk_250(clk_250), + + .signal_detect(signal_detect), + .indicate_data(indicate_data), + + .signal_status(signal_status_nrzi), + .rx_data(rx_nrzi), + .rx_data_valid(rx_nrzi_valid) + ); + + nrzi_decode decoder ( + .clk(clk_125), + .rst(loopback ? 1'b0 : !signal_status_nrzi), + .nrzi(loopback ? { tx_nrzi, 1'bX } : rx_nrzi), + .nrzi_valid(loopback ? 2'b01 : rx_nrzi_valid), + .nrz(rx_data), + .nrz_valid(rx_data_valid) + ); + + initial signal_status = 0; + always @(posedge clk_125) + signal_status <= loopback ? 1'b1 : signal_status_nrzi; + +endmodule diff --git a/tb/pmd_dp83223.py b/tb/pmd_dp83223.py new file mode 100644 index 0000000..57bb89f --- /dev/null +++ b/tb/pmd_dp83223.py @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2022 Sean Anderson + +import random + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Combine, FallingEdge, Join, RisingEdge, Timer +from cocotb.types import LogicArray + +from .nrzi_encode import nrzi_encode +from .nrzi_decode import nrzi_decode +from .pmd_dp83223_rx import check_bits +from .util import alist, compare_lists, print_list_at + +@cocotb.test(timeout_time=3, timeout_unit='us') +async def test_loopback(pmd): + rx_bits = [random.randrange(2) for _ in range(100)] + tx_bits = [random.randrange(2) for _ in range(100)] + + pmd.loopback.value = 0 + pmd.signal_detect.value = 0 + pmd.tx_data.value = tx_bits[0] + await Timer(1) + await cocotb.start(Clock(pmd.clk_125, 8, units='ns').start()) + await cocotb.start(Clock(pmd.clk_250, 4, units='ns').start()) + + async def send_rx(): + for bit in nrzi_encode(rx_bits): + pmd.signal_detect.value = 1 + pmd.indicate_data.value = bit + await Timer(8, units='ns') + pmd.signal_detect.value = 0 + pmd.indicate_data.value = LogicArray('X') + + async def send_tx(): + for bit in tx_bits: + pmd.tx_data.value = bit + await FallingEdge(pmd.clk_125) + + async def recv_tx(delay): + async def bits(): + await ClockCycles(pmd.clk_125, delay) + for _ in range(len(tx_bits)): + await RisingEdge(pmd.clk_125) + yield pmd.request_data.value + + compare_lists(tx_bits, await alist(nrzi_decode(bits()))) + + async def test_normal(delay=2): + await cocotb.start(send_rx()) + await cocotb.start(send_tx()) + rx_task = await cocotb.start(check_bits(pmd, rx_bits)) + tx_task = await cocotb.start(recv_tx(delay)) + + await Combine(Join(rx_task), Join(tx_task)) + + await test_normal() + + async def loopback_monitor(): + await ClockCycles(pmd.clk_125, 2) + while pmd.loopback.value: + assert not pmd.request_data.value + await RisingEdge(pmd.clk_125) + + tx_task = await cocotb.start(send_rx()) + await cocotb.start(send_tx()) + rx_task = await cocotb.start(check_bits(pmd, tx_bits)) + loop_task = await cocotb.start(loopback_monitor()) + pmd.loopback.value = 1 + + await Join(tx_task) + pmd.loopback.value = 0 + await Join(rx_task) + loop_task.kill() + + await test_normal(1)