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"
|
`include "common.vh"
|
||||||
|
`include "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
|
|
||||||
|
|
||||||
`timescale 1ns/1ns
|
`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 (
|
module pcs_rx_bits (
|
||||||
input clk,
|
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 enum
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import cocotb
|
from .util import classproperty
|
||||||
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
|
|
||||||
|
|
||||||
class Code(enum.Enum):
|
class Code(enum.Enum):
|
||||||
_0 = (0b11110, '0')
|
_0 = (0b11110, '0')
|
||||||
|
@ -103,162 +96,3 @@ def as_nibbles(data):
|
||||||
for byte in data:
|
for byte in data:
|
||||||
yield byte >> 4
|
yield byte >> 4
|
||||||
yield byte & 0xf
|
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