pcs: Split into rx/tx
For easier integration, split the PCS into its rx and tx components. This was already done on the module level, but now they live in separate files. Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
parent
c02d3f3ad0
commit
494ef2a2a9
|
@ -0,0 +1,36 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||
/*
|
||||
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
*/
|
||||
|
||||
`ifndef PCS_VH
|
||||
`define PCS_VH
|
||||
|
||||
/* 4b5b code groups */
|
||||
`define CODE_0 5'b11110
|
||||
`define CODE_1 5'b01001
|
||||
`define CODE_2 5'b10100
|
||||
`define CODE_3 5'b10101
|
||||
`define CODE_4 5'b01010
|
||||
`define CODE_5 5'b01011
|
||||
`define CODE_6 5'b01110
|
||||
`define CODE_7 5'b01111
|
||||
`define CODE_8 5'b10010
|
||||
`define CODE_9 5'b10011
|
||||
`define CODE_A 5'b10110
|
||||
`define CODE_B 5'b10111
|
||||
`define CODE_C 5'b11010
|
||||
`define CODE_D 5'b11011
|
||||
`define CODE_E 5'b11100
|
||||
`define CODE_F 5'b11101
|
||||
|
||||
`define CODE_I 5'b11111
|
||||
|
||||
`define CODE_J 5'b11000
|
||||
`define CODE_K 5'b10001
|
||||
`define CODE_T 5'b01101
|
||||
`define CODE_R 5'b00111
|
||||
|
||||
`define CODE_H 5'b00100
|
||||
|
||||
`endif /* PCS_VH */
|
|
@ -4,246 +4,10 @@
|
|||
*/
|
||||
|
||||
`include "common.vh"
|
||||
|
||||
/* 4b5b code groups */
|
||||
`define CODE_0 5'b11110
|
||||
`define CODE_1 5'b01001
|
||||
`define CODE_2 5'b10100
|
||||
`define CODE_3 5'b10101
|
||||
`define CODE_4 5'b01010
|
||||
`define CODE_5 5'b01011
|
||||
`define CODE_6 5'b01110
|
||||
`define CODE_7 5'b01111
|
||||
`define CODE_8 5'b10010
|
||||
`define CODE_9 5'b10011
|
||||
`define CODE_A 5'b10110
|
||||
`define CODE_B 5'b10111
|
||||
`define CODE_C 5'b11010
|
||||
`define CODE_D 5'b11011
|
||||
`define CODE_E 5'b11100
|
||||
`define CODE_F 5'b11101
|
||||
|
||||
`define CODE_I 5'b11111
|
||||
|
||||
`define CODE_J 5'b11000
|
||||
`define CODE_K 5'b10001
|
||||
`define CODE_T 5'b01101
|
||||
`define CODE_R 5'b00111
|
||||
|
||||
`define CODE_H 5'b00100
|
||||
`include "pcs.vh"
|
||||
|
||||
`timescale 1ns/1ns
|
||||
|
||||
module pcs (
|
||||
/* MII */
|
||||
input tx_clk,
|
||||
input tx_ce,
|
||||
input tx_en,
|
||||
input [3:0] txd,
|
||||
input tx_er,
|
||||
|
||||
input rx_clk,
|
||||
output rx_ce,
|
||||
output rx_dv,
|
||||
output [3:0] rxd,
|
||||
output rx_er,
|
||||
|
||||
output crs,
|
||||
output col,
|
||||
|
||||
/* PMA */
|
||||
output pma_tx_data,
|
||||
input [1:0] pma_rx_data,
|
||||
input [1:0] pma_rx_data_valid,
|
||||
input link_status
|
||||
);
|
||||
|
||||
wire transmitting, receiving;
|
||||
|
||||
pcs_tx tx (
|
||||
.clk(tx_clk),
|
||||
.ce(tx_ce),
|
||||
.enable(tx_en),
|
||||
.data(txd),
|
||||
.err(tx_er),
|
||||
.bits(pma_tx_data),
|
||||
.link_status(link_status),
|
||||
.tx(transmitting)
|
||||
);
|
||||
|
||||
pcs_rx rx (
|
||||
.clk(rx_clk),
|
||||
.ce(rx_ce),
|
||||
.valid(rx_dv),
|
||||
.data(rxd),
|
||||
.err(rx_er),
|
||||
.bits(pma_rx_data),
|
||||
.bits_valid(pma_rx_data_valid),
|
||||
.link_status(link_status),
|
||||
.rx(receiving)
|
||||
);
|
||||
|
||||
/*
|
||||
* NB: These signals are not required to be in any particular clock
|
||||
* domain.
|
||||
*/
|
||||
assign col = transmitting && receiving;
|
||||
assign crs = transmitting || receiving;
|
||||
|
||||
endmodule
|
||||
|
||||
/* Transmit process */
|
||||
module pcs_tx (
|
||||
/* MII */
|
||||
input clk,
|
||||
input ce,
|
||||
input enable,
|
||||
input [3:0] data,
|
||||
input err,
|
||||
|
||||
/* PMA */
|
||||
output bits,
|
||||
input link_status,
|
||||
|
||||
/* Internal */
|
||||
output reg tx
|
||||
);
|
||||
|
||||
localparam IDLE = 0;
|
||||
localparam START_J = 1;
|
||||
localparam START_K = 2;
|
||||
localparam ERROR_J = 3;
|
||||
localparam ERROR_K = 4;
|
||||
localparam ERROR = 5;
|
||||
localparam DATA = 6;
|
||||
localparam END_T = 7;
|
||||
localparam END_R = 8;
|
||||
|
||||
reg [3:0] last_data;
|
||||
reg tx_next;
|
||||
reg [4:0] code, code_next;
|
||||
reg [3:0] state, state_next;
|
||||
|
||||
initial tx = 0;
|
||||
initial code = `CODE_I;
|
||||
initial state = IDLE;
|
||||
|
||||
always @(*) begin
|
||||
case (last_data)
|
||||
4'h0: code_next = `CODE_0;
|
||||
4'h1: code_next = `CODE_1;
|
||||
4'h2: code_next = `CODE_2;
|
||||
4'h3: code_next = `CODE_3;
|
||||
4'h4: code_next = `CODE_4;
|
||||
4'h5: code_next = `CODE_5;
|
||||
4'h6: code_next = `CODE_6;
|
||||
4'h7: code_next = `CODE_7;
|
||||
4'h8: code_next = `CODE_8;
|
||||
4'h9: code_next = `CODE_9;
|
||||
4'hA: code_next = `CODE_A;
|
||||
4'hB: code_next = `CODE_B;
|
||||
4'hC: code_next = `CODE_C;
|
||||
4'hD: code_next = `CODE_D;
|
||||
4'hE: code_next = `CODE_E;
|
||||
4'hF: code_next = `CODE_F;
|
||||
endcase
|
||||
|
||||
tx_next = tx;
|
||||
if (enable) begin
|
||||
if (err)
|
||||
state_next = ERROR;
|
||||
else
|
||||
state_next = DATA;
|
||||
end else begin
|
||||
state_next = END_T;
|
||||
end
|
||||
|
||||
case (state)
|
||||
IDLE: begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_I;
|
||||
state_next = IDLE;
|
||||
if (enable) begin
|
||||
tx_next = 1;
|
||||
if (err)
|
||||
state_next = ERROR_J;
|
||||
else
|
||||
state_next = START_J;
|
||||
end
|
||||
end
|
||||
START_J: begin
|
||||
code_next = `CODE_J;
|
||||
if (err)
|
||||
state_next = ERROR_K;
|
||||
else
|
||||
state_next = START_K;
|
||||
end
|
||||
START_K: begin
|
||||
code_next = `CODE_K;
|
||||
end
|
||||
ERROR_J: begin
|
||||
code_next = `CODE_J;
|
||||
state_next = ERROR_K;
|
||||
end
|
||||
ERROR_K: begin
|
||||
code_next = `CODE_K;
|
||||
state_next = ERROR;
|
||||
end
|
||||
ERROR: begin
|
||||
code_next = `CODE_H;
|
||||
end
|
||||
DATA: ;
|
||||
END_T: begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_T;
|
||||
state_next = END_R;
|
||||
end
|
||||
END_R: begin
|
||||
code_next = `CODE_R;
|
||||
state_next = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
if (!link_status) begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_I;
|
||||
state_next = IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (ce) begin
|
||||
last_data <= data;
|
||||
tx <= tx_next;
|
||||
code <= code_next;
|
||||
state <= state_next;
|
||||
end else begin
|
||||
code <= code << 1;
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
reg [255:0] state_text;
|
||||
|
||||
always @(*) begin
|
||||
case (state)
|
||||
IDLE: state_text = "IDLE";
|
||||
START_J: state_text = "START_J";
|
||||
START_K: state_text = "START_K";
|
||||
ERROR_J: state_text = "ERROR_J";
|
||||
ERROR_K: state_text = "ERROR_K";
|
||||
ERROR: state_text = "ERROR";
|
||||
DATA: state_text = "DATA";
|
||||
END_T: state_text = "END_T";
|
||||
END_R: state_text = "END_R";
|
||||
endcase
|
||||
end
|
||||
`endif
|
||||
|
||||
/* Transmit bits process */
|
||||
assign bits = code[4];
|
||||
endmodule
|
||||
|
||||
module pcs_rx_bits (
|
||||
input clk,
|
||||
/*
|
|
@ -0,0 +1,161 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||
/*
|
||||
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
*/
|
||||
|
||||
`include "common.vh"
|
||||
`include "pcs.vh"
|
||||
|
||||
`timescale 1ns/1ns
|
||||
|
||||
/* Transmit process */
|
||||
module pcs_tx (
|
||||
/* MII */
|
||||
input clk,
|
||||
input ce,
|
||||
input enable,
|
||||
input [3:0] data,
|
||||
input err,
|
||||
|
||||
/* PMA */
|
||||
output bits,
|
||||
input link_status,
|
||||
|
||||
/* Internal */
|
||||
output reg tx
|
||||
);
|
||||
|
||||
localparam IDLE = 0;
|
||||
localparam START_J = 1;
|
||||
localparam START_K = 2;
|
||||
localparam ERROR_J = 3;
|
||||
localparam ERROR_K = 4;
|
||||
localparam ERROR = 5;
|
||||
localparam DATA = 6;
|
||||
localparam END_T = 7;
|
||||
localparam END_R = 8;
|
||||
|
||||
reg [3:0] last_data;
|
||||
reg tx_next;
|
||||
reg [4:0] code, code_next;
|
||||
reg [3:0] state, state_next;
|
||||
|
||||
initial tx = 0;
|
||||
initial code = `CODE_I;
|
||||
initial state = IDLE;
|
||||
|
||||
always @(*) begin
|
||||
case (last_data)
|
||||
4'h0: code_next = `CODE_0;
|
||||
4'h1: code_next = `CODE_1;
|
||||
4'h2: code_next = `CODE_2;
|
||||
4'h3: code_next = `CODE_3;
|
||||
4'h4: code_next = `CODE_4;
|
||||
4'h5: code_next = `CODE_5;
|
||||
4'h6: code_next = `CODE_6;
|
||||
4'h7: code_next = `CODE_7;
|
||||
4'h8: code_next = `CODE_8;
|
||||
4'h9: code_next = `CODE_9;
|
||||
4'hA: code_next = `CODE_A;
|
||||
4'hB: code_next = `CODE_B;
|
||||
4'hC: code_next = `CODE_C;
|
||||
4'hD: code_next = `CODE_D;
|
||||
4'hE: code_next = `CODE_E;
|
||||
4'hF: code_next = `CODE_F;
|
||||
endcase
|
||||
|
||||
tx_next = tx;
|
||||
if (enable) begin
|
||||
if (err)
|
||||
state_next = ERROR;
|
||||
else
|
||||
state_next = DATA;
|
||||
end else begin
|
||||
state_next = END_T;
|
||||
end
|
||||
|
||||
case (state)
|
||||
IDLE: begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_I;
|
||||
state_next = IDLE;
|
||||
if (enable) begin
|
||||
tx_next = 1;
|
||||
if (err)
|
||||
state_next = ERROR_J;
|
||||
else
|
||||
state_next = START_J;
|
||||
end
|
||||
end
|
||||
START_J: begin
|
||||
code_next = `CODE_J;
|
||||
if (err)
|
||||
state_next = ERROR_K;
|
||||
else
|
||||
state_next = START_K;
|
||||
end
|
||||
START_K: begin
|
||||
code_next = `CODE_K;
|
||||
end
|
||||
ERROR_J: begin
|
||||
code_next = `CODE_J;
|
||||
state_next = ERROR_K;
|
||||
end
|
||||
ERROR_K: begin
|
||||
code_next = `CODE_K;
|
||||
state_next = ERROR;
|
||||
end
|
||||
ERROR: begin
|
||||
code_next = `CODE_H;
|
||||
end
|
||||
DATA: ;
|
||||
END_T: begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_T;
|
||||
state_next = END_R;
|
||||
end
|
||||
END_R: begin
|
||||
code_next = `CODE_R;
|
||||
state_next = IDLE;
|
||||
end
|
||||
endcase
|
||||
|
||||
if (!link_status) begin
|
||||
tx_next = 0;
|
||||
code_next = `CODE_I;
|
||||
state_next = IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (ce) begin
|
||||
last_data <= data;
|
||||
tx <= tx_next;
|
||||
code <= code_next;
|
||||
state <= state_next;
|
||||
end else begin
|
||||
code <= code << 1;
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
reg [255:0] state_text;
|
||||
|
||||
always @(*) begin
|
||||
case (state)
|
||||
IDLE: state_text = "IDLE";
|
||||
START_J: state_text = "START_J";
|
||||
START_K: state_text = "START_K";
|
||||
ERROR_J: state_text = "ERROR_J";
|
||||
ERROR_K: state_text = "ERROR_K";
|
||||
ERROR: state_text = "ERROR";
|
||||
DATA: state_text = "DATA";
|
||||
END_T: state_text = "END_T";
|
||||
END_R: state_text = "END_R";
|
||||
endcase
|
||||
end
|
||||
`endif
|
||||
|
||||
/* Transmit bits process */
|
||||
assign bits = code[4];
|
||||
endmodule
|
168
tb/pcs.py
168
tb/pcs.py
|
@ -4,14 +4,7 @@
|
|||
import enum
|
||||
import itertools
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.regression import TestFactory
|
||||
from cocotb.triggers import ClockCycles, Edge, RisingEdge, FallingEdge, Timer
|
||||
from cocotb.types import LogicArray
|
||||
|
||||
from .util import alist, ClockEnable, classproperty, ReverseList, send_recovered_bits, \
|
||||
timeout, with_valids
|
||||
from .util import classproperty
|
||||
|
||||
class Code(enum.Enum):
|
||||
_0 = (0b11110, '0')
|
||||
|
@ -103,162 +96,3 @@ def as_nibbles(data):
|
|||
for byte in data:
|
||||
yield byte >> 4
|
||||
yield byte & 0xf
|
||||
|
||||
def as_codes(nibbles):
|
||||
for nibble in nibbles:
|
||||
if nibble is None:
|
||||
yield Code('H')
|
||||
else:
|
||||
yield Code.encode[nibble]
|
||||
|
||||
def frame(data):
|
||||
return itertools.chain(
|
||||
(Code('J'), Code('K')),
|
||||
# Chop off the SSD
|
||||
as_codes(data[2:]),
|
||||
(Code('T'), Code('R')),
|
||||
)
|
||||
|
||||
async def mii_send_packet(pcs, nibbles):
|
||||
await FallingEdge(pcs.tx_ce)
|
||||
for nibble in nibbles:
|
||||
pcs.tx_en.value = 1
|
||||
pcs.tx_er.value = 0
|
||||
if nibble is None:
|
||||
pcs.tx_er.value = 1
|
||||
else:
|
||||
pcs.txd.value = nibble
|
||||
await FallingEdge(pcs.tx_ce)
|
||||
|
||||
pcs.tx_en.value = 0
|
||||
pcs.tx_er.value = 0
|
||||
pcs.txd.value = LogicArray("XXXX")
|
||||
await FallingEdge(pcs.tx_ce)
|
||||
|
||||
async def mii_recv_packet(pcs):
|
||||
while not (pcs.rx_ce.value and pcs.rx_dv.value):
|
||||
await RisingEdge(pcs.rx_clk)
|
||||
|
||||
while pcs.rx_dv.value:
|
||||
if pcs.rx_ce.value:
|
||||
if pcs.rx_er.value:
|
||||
yield None
|
||||
else:
|
||||
yield pcs.rxd.value
|
||||
await RisingEdge(pcs.rx_clk)
|
||||
|
||||
class PCSError(Exception):
|
||||
pass
|
||||
|
||||
class BadSSD(PCSError):
|
||||
pass
|
||||
|
||||
class PrematureEnd(PCSError):
|
||||
pass
|
||||
|
||||
async def pcs_recv_packet(pcs):
|
||||
rx_bits = ReverseList([1] * 10)
|
||||
|
||||
async def read_bit():
|
||||
await RisingEdge(pcs.tx_clk)
|
||||
rx_bits.append(pcs.pma_tx_data.value)
|
||||
|
||||
async def read_code():
|
||||
for _ in range(5):
|
||||
await read_bit()
|
||||
|
||||
async def bad_ssd():
|
||||
while not all(rx_bits[9:0]):
|
||||
await read_bit()
|
||||
raise BadSSDError()
|
||||
|
||||
while all(rx_bits[9:2]) or rx_bits[0]:
|
||||
await read_bit()
|
||||
|
||||
if Code.decode(rx_bits[9:5]) != Code('I') or \
|
||||
Code.decode(rx_bits[4:0]) != Code('J'):
|
||||
await bad_ssd()
|
||||
|
||||
await read_code()
|
||||
if Code.decode(rx_bits[4:0]) != Code('K'):
|
||||
await bad_ssd()
|
||||
|
||||
yield 0x5
|
||||
await read_code()
|
||||
|
||||
yield 0x5
|
||||
while any(rx_bits[9:0]):
|
||||
await read_code()
|
||||
code = Code.decode(rx_bits[9:5])
|
||||
if code == Code('T') and Code.decode(rx_bits[4:0]) == Code('R'):
|
||||
return
|
||||
yield code.data
|
||||
raise PrematureEndError()
|
||||
|
||||
async def pcs_send_codes(pcs, codes, valids):
|
||||
await send_recovered_bits(pcs.rx_clk, pcs.pma_rx_data, pcs.pma_rx_data_valid,
|
||||
itertools.chain(*codes), valids)
|
||||
|
||||
@cocotb.test(timeout_time=10, timeout_unit='us')
|
||||
async def test_tx(pcs):
|
||||
pcs.tx_en.value = 0
|
||||
pcs.tx_er.value = 0
|
||||
pcs.txd.value = LogicArray("XXXX")
|
||||
pcs.link_status.value = 1
|
||||
await cocotb.start(ClockEnable(pcs.tx_clk, pcs.tx_ce, 5))
|
||||
await Timer(1)
|
||||
await cocotb.start(Clock(pcs.tx_clk, 8, units='ns').start())
|
||||
await FallingEdge(pcs.tx_ce)
|
||||
|
||||
# Test that all bytes can be transmitted
|
||||
packet = list(as_nibbles((0x55, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF)))
|
||||
# And ensure errors are propagated
|
||||
packet.insert(10, None)
|
||||
await cocotb.start(mii_send_packet(pcs, packet))
|
||||
assert packet == await alist(pcs_recv_packet(pcs))
|
||||
|
||||
# Test start errors
|
||||
await cocotb.start(mii_send_packet(pcs, [None]))
|
||||
assert [0x5, 0x5, None] == await alist(pcs_recv_packet(pcs))
|
||||
await cocotb.start(mii_send_packet(pcs, [0x5, None]))
|
||||
assert [0x5, 0x5, None] == await alist(pcs_recv_packet(pcs))
|
||||
|
||||
@timeout(10, 'us')
|
||||
async def test_rx(pcs, valids):
|
||||
pcs.pma_rx_data.value = LogicArray('11')
|
||||
pcs.pma_rx_data_valid.value = 2
|
||||
pcs.link_status.value = 1
|
||||
await Timer(1)
|
||||
await cocotb.start(Clock(pcs.rx_clk, 8, units='ns').start())
|
||||
|
||||
packet = list(as_nibbles((0x55, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF)))
|
||||
# And test errors too
|
||||
packet.insert(10, None)
|
||||
|
||||
await cocotb.start(pcs_send_codes(pcs, itertools.chain(
|
||||
frame(packet),
|
||||
# Bad SSDs
|
||||
(Code('C'), Code('I'), Code('I')),
|
||||
(Code('J'), Code('I'), Code('I')),
|
||||
(Code('J'), Code('H'), Code('I'), Code('I')),
|
||||
# Premature end, plus two clocks since we don't have instant turnaround
|
||||
(Code('J'), Code('K'), Code('I'), Code('I'), (1,1)),
|
||||
# Packet spacing
|
||||
*((*frame([0x55, 0x55]), (1,) * i) for i in range(10))
|
||||
), valids))
|
||||
|
||||
assert packet == await alist(mii_recv_packet(pcs))
|
||||
|
||||
for _ in range(3):
|
||||
while not (pcs.receiving.value and pcs.rx_er.value and pcs.rx_ce.value):
|
||||
await RisingEdge(pcs.rx_clk)
|
||||
assert pcs.rxd.value == 0xE
|
||||
await FallingEdge(pcs.receiving)
|
||||
|
||||
assert [0x5, 0x5, None] == await alist(mii_recv_packet(pcs))
|
||||
|
||||
# Test packet spacing
|
||||
for _ in range(10):
|
||||
assert [0x5, 0x5] == await alist(mii_recv_packet(pcs))
|
||||
|
||||
with_valids(globals(), test_rx)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-Only
|
||||
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
|
||||
import itertools
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, FallingEdge, Timer
|
||||
from cocotb.types import LogicArray
|
||||
|
||||
from .pcs import Code, as_nibbles
|
||||
from .util import alist, send_recovered_bits, timeout, with_valids
|
||||
|
||||
def as_codes(nibbles):
|
||||
for nibble in nibbles:
|
||||
if nibble is None:
|
||||
yield Code('H')
|
||||
else:
|
||||
yield Code.encode[nibble]
|
||||
|
||||
def frame(data):
|
||||
return itertools.chain(
|
||||
(Code('J'), Code('K')),
|
||||
# Chop off the SSD
|
||||
as_codes(data[2:]),
|
||||
(Code('T'), Code('R')),
|
||||
)
|
||||
|
||||
async def mii_recv_packet(pcs):
|
||||
while not (pcs.ce.value and pcs.valid.value):
|
||||
await RisingEdge(pcs.clk)
|
||||
|
||||
while pcs.valid.value:
|
||||
if pcs.ce.value:
|
||||
if pcs.err.value:
|
||||
yield None
|
||||
else:
|
||||
yield pcs.data.value
|
||||
await RisingEdge(pcs.clk)
|
||||
|
||||
async def pcs_send_codes(pcs, codes, valids):
|
||||
await send_recovered_bits(pcs.clk, pcs.bits, pcs.bits_valid,
|
||||
itertools.chain(*codes), valids)
|
||||
|
||||
@timeout(10, 'us')
|
||||
async def test_rx(pcs, valids):
|
||||
pcs.bits.value = LogicArray('11')
|
||||
pcs.bits_valid.value = 2
|
||||
pcs.link_status.value = 1
|
||||
await Timer(1)
|
||||
await cocotb.start(Clock(pcs.clk, 8, units='ns').start())
|
||||
|
||||
packet = list(as_nibbles((0x55, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF)))
|
||||
# And test errors too
|
||||
packet.insert(10, None)
|
||||
|
||||
await cocotb.start(pcs_send_codes(pcs, itertools.chain(
|
||||
frame(packet),
|
||||
# Bad SSDs
|
||||
(Code('C'), Code('I'), Code('I')),
|
||||
(Code('J'), Code('I'), Code('I')),
|
||||
(Code('J'), Code('H'), Code('I'), Code('I')),
|
||||
# Premature end, plus two clocks since we don't have instant turnaround
|
||||
(Code('J'), Code('K'), Code('I'), Code('I'), (1,1)),
|
||||
# Packet spacing
|
||||
*((*frame([0x55, 0x55]), (1,) * i) for i in range(10))
|
||||
), valids))
|
||||
|
||||
assert packet == await alist(mii_recv_packet(pcs))
|
||||
|
||||
for _ in range(3):
|
||||
while not (pcs.rx.value and pcs.err.value and pcs.ce.value):
|
||||
await RisingEdge(pcs.clk)
|
||||
assert pcs.data.value == 0xE
|
||||
await FallingEdge(pcs.rx)
|
||||
|
||||
assert [0x5, 0x5, None] == await alist(mii_recv_packet(pcs))
|
||||
|
||||
# Test packet spacing
|
||||
for _ in range(10):
|
||||
assert [0x5, 0x5] == await alist(mii_recv_packet(pcs))
|
||||
|
||||
with_valids(globals(), test_rx)
|
|
@ -0,0 +1,108 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-Only
|
||||
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||
|
||||
import cocotb
|
||||
from cocotb.clock import Clock
|
||||
from cocotb.triggers import ClockCycles, RisingEdge, FallingEdge, Timer
|
||||
from cocotb.types import LogicArray
|
||||
|
||||
from .pcs import Code, as_nibbles
|
||||
from .util import alist, ClockEnable, ReverseList
|
||||
|
||||
async def mii_send_packet(pcs, nibbles):
|
||||
await FallingEdge(pcs.ce)
|
||||
for nibble in nibbles:
|
||||
pcs.enable.value = 1
|
||||
pcs.err.value = 0
|
||||
if nibble is None:
|
||||
pcs.err.value = 1
|
||||
else:
|
||||
pcs.data.value = nibble
|
||||
await FallingEdge(pcs.ce)
|
||||
|
||||
pcs.enable.value = 0
|
||||
pcs.err.value = 0
|
||||
pcs.data.value = LogicArray("XXXX")
|
||||
await FallingEdge(pcs.ce)
|
||||
|
||||
class PCSError(Exception):
|
||||
pass
|
||||
|
||||
class BadSSD(PCSError):
|
||||
pass
|
||||
|
||||
class PrematureEnd(PCSError):
|
||||
pass
|
||||
|
||||
async def pcs_recv_bits(pcs, data=None):
|
||||
if data is None:
|
||||
data = pcs.bits
|
||||
|
||||
while True:
|
||||
await RisingEdge(pcs.clk)
|
||||
yield data.value
|
||||
|
||||
async def pcs_recv_packet(pcs, bits=None):
|
||||
if bits is None:
|
||||
bits = pcs_recv_bits(pcs)
|
||||
|
||||
rx_bits = ReverseList([1] * 10)
|
||||
|
||||
async def read_bit():
|
||||
rx_bits.append(await anext(bits))
|
||||
|
||||
async def read_code():
|
||||
for _ in range(5):
|
||||
await read_bit()
|
||||
|
||||
async def bad_ssd():
|
||||
while not all(rx_bits[9:0]):
|
||||
await read_bit()
|
||||
raise BadSSDError()
|
||||
|
||||
while all(rx_bits[9:2]) or rx_bits[0]:
|
||||
await read_bit()
|
||||
|
||||
if Code.decode(rx_bits[9:5]) != Code('I') or \
|
||||
Code.decode(rx_bits[4:0]) != Code('J'):
|
||||
await bad_ssd()
|
||||
|
||||
await read_code()
|
||||
if Code.decode(rx_bits[4:0]) != Code('K'):
|
||||
await bad_ssd()
|
||||
|
||||
yield 0x5
|
||||
await read_code()
|
||||
|
||||
yield 0x5
|
||||
while any(rx_bits[9:0]):
|
||||
await read_code()
|
||||
code = Code.decode(rx_bits[9:5])
|
||||
if code == Code('T') and Code.decode(rx_bits[4:0]) == Code('R'):
|
||||
return
|
||||
yield code.data
|
||||
raise PrematureEndError()
|
||||
|
||||
@cocotb.test(timeout_time=10, timeout_unit='us')
|
||||
async def test_tx(pcs):
|
||||
pcs.enable.value = 0
|
||||
pcs.err.value = 0
|
||||
pcs.data.value = LogicArray("XXXX")
|
||||
pcs.link_status.value = 1
|
||||
await cocotb.start(ClockEnable(pcs.clk, pcs.ce, 5))
|
||||
await Timer(1)
|
||||
await cocotb.start(Clock(pcs.clk, 8, units='ns').start())
|
||||
await FallingEdge(pcs.ce)
|
||||
|
||||
# Test that all bytes can be transmitted
|
||||
packet = list(as_nibbles((0x55, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF)))
|
||||
# And ensure errors are propagated
|
||||
packet.insert(10, None)
|
||||
await cocotb.start(mii_send_packet(pcs, packet))
|
||||
assert packet == await alist(pcs_recv_packet(pcs))
|
||||
|
||||
# Test start errors
|
||||
await cocotb.start(mii_send_packet(pcs, [None]))
|
||||
assert [0x5, 0x5, None] == await alist(pcs_recv_packet(pcs))
|
||||
await cocotb.start(mii_send_packet(pcs, [0x5, None]))
|
||||
assert [0x5, 0x5, None] == await alist(pcs_recv_packet(pcs))
|
Loading…
Reference in New Issue