Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-Only
|
|
|
|
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
|
|
|
|
|
|
|
import itertools
|
|
|
|
import random
|
|
|
|
|
|
|
|
import cocotb
|
|
|
|
from cocotb.clock import Clock
|
|
|
|
from cocotb.triggers import ClockCycles, Event, FallingEdge, RisingEdge, Timer
|
|
|
|
from cocotb.types import LogicArray
|
|
|
|
|
|
|
|
from .scramble import descramble
|
|
|
|
from .descramble import scramble
|
|
|
|
from .pcs_tx import as_nibbles, mii_send_packet, pcs_recv_packet
|
|
|
|
from .pcs_rx import frame, mii_recv_packet
|
|
|
|
from .util import alist, ClockEnable
|
|
|
|
|
|
|
|
@cocotb.test(timeout_time=15, timeout_unit='us')
|
|
|
|
async def test_transfer(phy):
|
|
|
|
phy.coltest.value = 0
|
|
|
|
phy.descrambler_test_mode.value = 0
|
|
|
|
phy.tx_en.value = 0
|
|
|
|
phy.rx_data_valid.value = 0
|
|
|
|
phy.signal_status.value = 0
|
|
|
|
phy.loopback.value = 0
|
|
|
|
phy.link_monitor_test_mode.value = 1
|
|
|
|
await cocotb.start(ClockEnable(phy.clk, phy.tx_ce, 5))
|
|
|
|
await Timer(1)
|
|
|
|
await cocotb.start(Clock(phy.clk, 8, units='ns').start())
|
2023-03-04 23:13:13 -06:00
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
phy.signal_status.value = 1
|
Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
|
|
|
|
tx_data = list(as_nibbles([0x55, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]))
|
|
|
|
rx_data = list(as_nibbles((0x55, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10)))
|
|
|
|
|
|
|
|
async def send_rx_packets():
|
|
|
|
def rx_bits():
|
|
|
|
packet_bits = list(itertools.chain.from_iterable(frame(rx_data)))
|
|
|
|
|
|
|
|
# First packet is OK, second is a collision
|
|
|
|
yield from itertools.repeat(1, 120)
|
|
|
|
yield from packet_bits
|
|
|
|
yield from itertools.repeat(1, 240)
|
|
|
|
yield from packet_bits
|
|
|
|
|
|
|
|
while not phy.loopback.value:
|
|
|
|
yield 1
|
|
|
|
|
|
|
|
for bit in scramble(rx_bits()):
|
|
|
|
phy.rx_data.value = LogicArray((bit, 'X'))
|
|
|
|
phy.rx_data_valid.value = 1
|
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
|
|
|
|
async def send_tx_packets():
|
|
|
|
signals = {
|
|
|
|
'ce': phy.tx_ce,
|
|
|
|
'enable': phy.tx_en,
|
|
|
|
'err': phy.tx_er,
|
|
|
|
'data': phy.txd,
|
|
|
|
}
|
|
|
|
|
|
|
|
# Send a packet, and then cause a collision
|
|
|
|
await ClockCycles(phy.clk, 240)
|
|
|
|
await mii_send_packet(phy, tx_data, signals)
|
|
|
|
await ClockCycles(phy.clk, 120)
|
|
|
|
await mii_send_packet(phy, tx_data, signals)
|
|
|
|
|
|
|
|
while not phy.loopback.value:
|
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
|
|
|
|
# Loopback
|
|
|
|
await ClockCycles(phy.clk, 120)
|
|
|
|
await mii_send_packet(phy, tx_data, signals)
|
|
|
|
|
|
|
|
# Collision test
|
|
|
|
await ClockCycles(phy.clk, 120)
|
|
|
|
phy.coltest.value = 1
|
|
|
|
await mii_send_packet(phy, tx_data, signals)
|
|
|
|
|
|
|
|
while phy.loopback.value:
|
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
|
|
|
|
await ClockCycles(phy.clk, 240)
|
|
|
|
await mii_send_packet(phy, tx_data, signals)
|
|
|
|
|
|
|
|
async def loopback():
|
|
|
|
while phy.loopback.value:
|
|
|
|
phy.rx_data.value = LogicArray((int(phy.tx_data.value), 'X'))
|
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
|
|
|
|
await cocotb.start(send_tx_packets())
|
|
|
|
await cocotb.start(send_rx_packets())
|
|
|
|
|
|
|
|
rx_ready = Event()
|
|
|
|
tx_ready = Event()
|
|
|
|
|
|
|
|
async def recv_rx_packets():
|
|
|
|
async def packets():
|
|
|
|
while True:
|
|
|
|
yield await alist(mii_recv_packet(phy, {
|
|
|
|
'ce': phy.rx_ce,
|
|
|
|
'err': phy.rx_er,
|
|
|
|
'data': phy.rxd,
|
|
|
|
'valid': phy.rx_dv,
|
|
|
|
}))
|
|
|
|
|
|
|
|
packets = packets()
|
|
|
|
assert rx_data == await anext(packets)
|
|
|
|
assert rx_data == await anext(packets)
|
|
|
|
rx_ready.set()
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
rx_ready.set()
|
|
|
|
assert rx_data == await anext(packets)
|
|
|
|
rx_ready.set()
|
|
|
|
|
|
|
|
async def recv_tx_packets():
|
|
|
|
async def recv():
|
|
|
|
while True:
|
|
|
|
await RisingEdge(phy.clk)
|
|
|
|
yield phy.tx_data.value
|
|
|
|
|
|
|
|
async def packets():
|
|
|
|
while True:
|
|
|
|
yield await alist(pcs_recv_packet(phy, descramble(recv())))
|
|
|
|
|
|
|
|
packets = packets()
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
tx_ready.set()
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
tx_ready.set()
|
|
|
|
assert tx_data == await anext(packets)
|
|
|
|
tx_ready.set()
|
|
|
|
|
|
|
|
await cocotb.start(recv_rx_packets())
|
|
|
|
await cocotb.start(recv_tx_packets())
|
|
|
|
|
|
|
|
crs = 0
|
|
|
|
col = 0
|
|
|
|
|
|
|
|
async def count_crs():
|
|
|
|
nonlocal crs
|
2023-01-09 19:50:00 -06:00
|
|
|
last_crs = 0
|
Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
while True:
|
2023-01-09 19:50:00 -06:00
|
|
|
await RisingEdge(phy.clk)
|
|
|
|
if phy.crs.value and not last_crs:
|
|
|
|
crs += 1
|
|
|
|
last_crs = phy.crs.value
|
Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
|
|
|
|
async def count_col():
|
|
|
|
nonlocal col
|
2023-01-09 19:50:00 -06:00
|
|
|
last_col = 0
|
Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
while True:
|
2023-01-09 19:50:00 -06:00
|
|
|
await RisingEdge(phy.clk)
|
|
|
|
if phy.col.value and not last_col:
|
|
|
|
col += 1
|
|
|
|
last_col = phy.col.value
|
Add phy_core
This module integrates the PCS with the descrambler, implements the PMA
(which is just the link monitor), and implements loopback and coltest
functions. This is more of the PCS/PMA, but the descrambler is
technically part of the PMD, so it's the "core" instead.
We deviate from the standard in one important way: the link doesn't come
up until the descambler is locked. I think this makes sense, since if
the descrambler isn't locked, then the incoming data will be gibberish.
I suspect this isn't part of the standard because the descrambler
doesn't have a locked output in X3.263, so IEEE would have had to
specify it.
Loopback is actually implemented in the PMD, but it modifies the
behavior in several places. It disables collisions (unless
coltest is enabled). Additionally, we need to force the link up (to
avoid the lengthy stabilization timer), but ensure it is down for at
least once cycle (to ensure the descrambler desynchronizes).
On the test side, we just go through the "happy path," as many of the
edge conditions are tested for in the submodule tests. Several of those
tests are modified so that their helper functions can be reused in this
test. In particular, the rx path is now async so that we can feed it
rx_data.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-11-05 11:14:58 -05:00
|
|
|
|
|
|
|
await cocotb.start(count_crs())
|
|
|
|
await cocotb.start(count_col())
|
|
|
|
|
|
|
|
await rx_ready.wait()
|
|
|
|
await tx_ready.wait()
|
|
|
|
assert crs == 3
|
|
|
|
assert col == 1
|
|
|
|
rx_ready.clear()
|
|
|
|
tx_ready.clear()
|
|
|
|
|
|
|
|
phy.loopback.value = 1
|
|
|
|
await ClockCycles(phy.clk, 1)
|
|
|
|
await cocotb.start(loopback())
|
|
|
|
|
|
|
|
await rx_ready.wait()
|
|
|
|
await tx_ready.wait()
|
|
|
|
assert crs == 5
|
|
|
|
assert col == 2
|
|
|
|
rx_ready.clear()
|
|
|
|
tx_ready.clear()
|
|
|
|
|
|
|
|
await FallingEdge(phy.clk)
|
|
|
|
phy.loopback.value = 0
|
|
|
|
phy.coltest.value = 0
|
|
|
|
await cocotb.start(send_rx_packets())
|
|
|
|
|
|
|
|
await rx_ready.wait()
|
|
|
|
await tx_ready.wait()
|
|
|
|
assert crs == 7
|
|
|
|
assert col == 2
|