Add pmd
This commit is contained in:
parent
d351291ff8
commit
1d65661bd3
|
@ -0,0 +1,19 @@
|
|||
`ifndef IO_VH
|
||||
`define IO_VH
|
||||
|
||||
`define PIN_INPUT_REGISTERED 6'b000000
|
||||
`define PIN_INPUT_UNREGISTERED 6'b000001
|
||||
`define PIN_INPUT_LATCH 6'b000010
|
||||
`define PIN_INPUT_DDR 6'b000000
|
||||
|
||||
`define PIN_OUTPUT_NEVER 6'b000000
|
||||
`define PIN_OUTPUT_ALWAYS 6'b010000
|
||||
`define PIN_OUTPUT_ENABLE 6'b100000
|
||||
`define PIN_OUTPUT_ENABLE_REGISTERED 6'b110000
|
||||
|
||||
`define PIN_OUTPUT_DDR 6'b000000
|
||||
`define PIN_OUTPUT_REGISTERED 6'b000100
|
||||
`define PIN_OUTPUT_UNREGISTERED 6'b001000
|
||||
`define PIN_OUTPUT_REGISTERED_INVERTED 6'b001100
|
||||
|
||||
`endif /* IO_VH */
|
|
@ -0,0 +1,216 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||
/*
|
||||
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
*
|
||||
* This roughly follows the design of XAPP225. However, we use a 2x rate DDR
|
||||
* clock instead of two clocks 90 degrees out of phase. Yosys/nextpnr cannot
|
||||
* guarantee the phase relationship of any clocks, even those from the same
|
||||
* PLL. Because of this, we assume that rx_clk_250 and rx_clk_125 are unrelated.
|
||||
*/
|
||||
|
||||
`include "common.vh"
|
||||
`include "io.vh"
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module pmd (
|
||||
input tx_clk,
|
||||
input rx_clk_250,
|
||||
input rx_clk_125,
|
||||
|
||||
input signal_detect,
|
||||
output reg tx_p, tx_n,
|
||||
input rx,
|
||||
|
||||
/* PMD */
|
||||
output reg signal_status,
|
||||
input pmd_data_tx,
|
||||
output reg [1:0] pmd_data_rx,
|
||||
output reg [1:0] pmd_data_rx_valid
|
||||
);
|
||||
|
||||
reg [1:0] rx_p, rx_n;
|
||||
|
||||
`ifdef SYNTHESIS
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_NEVER | `PIN_INPUT_REGISTERED),
|
||||
.IO_STANDARD("SB_LVDS_INPUT")
|
||||
) signal_detect_pin (
|
||||
.PACKAGE_PIN(signal_detect),
|
||||
.INPUT_rx_clk(rx_clk_125),
|
||||
.D_IN_0(signal_status)
|
||||
);
|
||||
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_NEVER | `PIN_INPUT_DDR),
|
||||
.IO_STANDARD("SB_LVDS_INPUT")
|
||||
) data_rx_pin (
|
||||
.PACKAGE_PIN(rx),
|
||||
.INPUT_rx_clk(rx_clk_250),
|
||||
.D_IN_0(rx_p[0]),
|
||||
.D_IN_1(rx_n[0])
|
||||
);
|
||||
`else
|
||||
always @(posedge rx_clk_125)
|
||||
signal_status <= signal_detect;
|
||||
|
||||
always @(posedge rx_clk_250)
|
||||
rx_p[0] <= rx;
|
||||
|
||||
always @(negedge rx_clk_250)
|
||||
rx_n[0] <= rx;
|
||||
`endif
|
||||
|
||||
/*
|
||||
* Get things into the rx_clk_250 domain so that we sample posedge before
|
||||
* negedge. Without this we can have a negedge which happens before the
|
||||
* posedge.
|
||||
*/
|
||||
always @(posedge rx_clk_250) begin
|
||||
rx_p[1] = rx_p[0];
|
||||
rx_n[1] = rx_n[0];
|
||||
end
|
||||
|
||||
reg [2:0] rx_a, rx_b, rx_c, rx_d;
|
||||
|
||||
/* Get everything in the rx_clk_125 domain */
|
||||
always @(posedge rx_clk_125) begin
|
||||
rx_a[0] <= rx_p[1];
|
||||
rx_b[0] <= rx_n[1];
|
||||
end
|
||||
|
||||
always @(negedge rx_clk_125) begin
|
||||
rx_c[0] <= rx_p[1];
|
||||
rx_d[0] <= rx_n[1];
|
||||
end
|
||||
|
||||
/*
|
||||
* Buffer things a bit. We wait a cycle to avoid metastability. After
|
||||
* that, we need two cycles of history to detect edges.
|
||||
*/
|
||||
always @(posedge rx_clk_125) begin
|
||||
rx_a[2:1] = rx_a[1:0];
|
||||
rx_b[2:1] = rx_b[1:0];
|
||||
rx_c[2:1] = rx_c[1:0];
|
||||
rx_d[2:1] = rx_d[1:0];
|
||||
end
|
||||
|
||||
localparam A = 0;
|
||||
localparam B = 1;
|
||||
localparam C = 2;
|
||||
localparam D = 3;
|
||||
|
||||
reg [1:0] state, state_next;
|
||||
initial state = A;
|
||||
reg valid, valid_next;
|
||||
initial valid = 0;
|
||||
reg [1:0] pmd_data_rx_next, pmd_data_rx_valid_next;
|
||||
reg [3:0] rx_r, rx_f;
|
||||
|
||||
always @(*) begin
|
||||
rx_r = {
|
||||
rx_a[1] & ~rx_a[2],
|
||||
rx_b[1] & ~rx_b[2],
|
||||
rx_c[1] & ~rx_c[2],
|
||||
rx_d[1] & ~rx_d[2]
|
||||
};
|
||||
|
||||
rx_f = {
|
||||
~rx_a[1] & rx_a[2],
|
||||
~rx_b[1] & rx_b[2],
|
||||
~rx_c[1] & rx_c[2],
|
||||
~rx_d[1] & rx_d[2]
|
||||
};
|
||||
|
||||
state_next = state;
|
||||
valid_next = 1;
|
||||
if (rx_r == 4'b1111 || rx_f == 4'b1111)
|
||||
state_next = C;
|
||||
else if (rx_r == 4'b1000 || rx_f == 4'b1000)
|
||||
state_next = D;
|
||||
else if (rx_r == 4'b1100 || rx_f == 4'b1100)
|
||||
state_next = A;
|
||||
else if (rx_r == 4'b1110 || rx_f == 4'b1110)
|
||||
state_next = B;
|
||||
else
|
||||
valid_next = valid;
|
||||
|
||||
if (!signal_status)
|
||||
valid_next = 0;
|
||||
|
||||
pmd_data_rx_next[0] = rx_d[2];
|
||||
pmd_data_rx_valid_next = 1;
|
||||
case (state_next)
|
||||
A: begin
|
||||
pmd_data_rx_next[1] = rx_a[2];
|
||||
if (state == D)
|
||||
pmd_data_rx_valid_next = 0;
|
||||
end
|
||||
B: begin
|
||||
pmd_data_rx_next[1] = rx_b[2];
|
||||
end
|
||||
C: begin
|
||||
pmd_data_rx_next[1] = rx_c[2];
|
||||
end
|
||||
D: begin
|
||||
pmd_data_rx_next[1] = rx_d[2];
|
||||
if (state == A) begin
|
||||
pmd_data_rx_next[1] = rx_a[2];
|
||||
pmd_data_rx_valid_next = 2;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
|
||||
if (!valid_next)
|
||||
pmd_data_rx_valid_next = 0;
|
||||
end
|
||||
|
||||
always @(posedge rx_clk_125) begin
|
||||
state <= state_next;
|
||||
valid <= valid_next;
|
||||
pmd_data_rx <= pmd_data_rx_next;
|
||||
pmd_data_rx_valid <= pmd_data_rx_valid_next;
|
||||
end
|
||||
|
||||
`ifdef SYNTHESIS
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED),
|
||||
.IO_STANDARD("SB_LVDS_INPUT")
|
||||
) data_txp_pin (
|
||||
.PACKAGE_PIN(tx_p),
|
||||
.OUTPUT_rx_clk(rx_clk_125),
|
||||
.D_OUT_0(pmd_data_tx)
|
||||
);
|
||||
|
||||
SB_IO #(
|
||||
.PIN_TYPE(`PIN_OUTPUT_ALWAYS | `PIN_OUTPUT_REGISTERED_INVERTED),
|
||||
.IO_STANDARD("SB_LVDS_INPUT")
|
||||
) data_txn_pin (
|
||||
.PACKAGE_PIN(tx_n),
|
||||
.OUTPUT_rx_clk(rx_clk_125),
|
||||
.D_OUT_0(pmd_data_tx)
|
||||
);
|
||||
`else
|
||||
always @(posedge tx_clk) begin
|
||||
tx_p <= pmd_data_tx;
|
||||
tx_n <= ~pmd_data_tx;
|
||||
end
|
||||
`endif
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
reg [255:0] state_text;
|
||||
input [13:0] delay;
|
||||
|
||||
always @(*) begin
|
||||
case (state)
|
||||
A: state_text = "A";
|
||||
B: state_text = "B";
|
||||
C: state_text = "C";
|
||||
D: state_text = "D";
|
||||
endcase
|
||||
end
|
||||
`endif
|
||||
|
||||
`DUMP
|
||||
|
||||
endmodule
|
|
@ -0,0 +1,64 @@
|
|||
import random
|
||||
from statistics import NormalDist
|
||||
|
||||
import cocotb
|
||||
from cocotb.binary import BinaryValue
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
|
||||
@cocotb.test(timeout_time=100, timeout_unit='us')
|
||||
async def test_rx(pmd):
|
||||
pmd.rx.value = BinaryValue('X')
|
||||
await cocotb.start(Clock(pmd.rx_clk_125, 8, units='ns').start())
|
||||
# random phase
|
||||
await Timer(random.randrange(0, 8000), units='ps')
|
||||
await cocotb.start(Clock(pmd.rx_clk_250, 4, units='ns').start())
|
||||
|
||||
ins = [random.randrange(2) for _ in range(1000)]
|
||||
async def generate_bits():
|
||||
# random phase
|
||||
await Timer(random.randrange(0, 8000), units='ps')
|
||||
pmd.signal_detect.value = 1
|
||||
# Target BER is 1e9 and the maximum jitter is 1.4ns
|
||||
# This is just random jitter (not DDJ or DCJ) but it'll do
|
||||
delay_dist = NormalDist(8000, 1400 / NormalDist().inv_cdf(1-2e-9))
|
||||
for i, delay in zip(ins, (int(delay) for delay in delay_dist.samples(len(ins)))):
|
||||
pmd.rx.value = i
|
||||
pmd.delay.value = delay
|
||||
await Timer(delay, units='ps')
|
||||
#await Timer(8100, units='ps')
|
||||
pmd.signal_detect.value = 0
|
||||
await cocotb.start(generate_bits())
|
||||
|
||||
# Wait for things to stabilize
|
||||
await RisingEdge(pmd.valid)
|
||||
outs = []
|
||||
while pmd.signal_status.value:
|
||||
await RisingEdge(pmd.rx_clk_125)
|
||||
valid = pmd.pmd_data_rx_valid.value
|
||||
if valid == 0:
|
||||
pass
|
||||
elif valid == 1:
|
||||
outs.append(pmd.pmd_data_rx[1].value)
|
||||
else:
|
||||
outs.append(pmd.pmd_data_rx[1].value)
|
||||
outs.append(pmd.pmd_data_rx[0].value)
|
||||
|
||||
best_corr = -1
|
||||
best_off = None
|
||||
for off in range(-7, 8):
|
||||
corr = sum(i == o for i, o in zip(ins[off:], outs))
|
||||
if corr > best_corr:
|
||||
best_corr = corr
|
||||
best_off = off
|
||||
|
||||
print(f"best offset is {best_off} correlation {best_corr/(len(ins) - best_off)}")
|
||||
for idx, (i, o) in enumerate(zip(ins[best_off:], outs)):
|
||||
if i != o:
|
||||
print(idx)
|
||||
print(*ins[idx+best_off-50:idx+best_off+50], sep='')
|
||||
print(*outs[idx-50:idx+50], sep='')
|
||||
assert False
|
||||
# There will be a few bits at the end not recorded because signal_detect
|
||||
# isn't delayed like the data signals
|
||||
assert best_corr > len(ins) - best_off - 10
|
Loading…
Reference in New Issue