29 Commits

Author SHA1 Message Date
Alex Forencich
40e3bd59ba Release v0.1.10 2021-03-24 21:04:42 -07:00
Alex Forencich
030e088b25 Revert back to cocotb.fork 2021-03-24 16:24:18 -07:00
Alex Forencich
295db437f7 Enforce max queue depth on streaming sources 2021-03-21 22:25:18 -07:00
Alex Forencich
a34a1cd125 Properly handle None 2021-03-21 21:03:35 -07:00
Alex Forencich
5c6510faea Factor out common recv code; throw QueueEmpty exception in get_nowait 2021-03-21 21:02:44 -07:00
Alex Forencich
f52f6dbe33 Trigger transmit complete events when flushing queue to prevent deadlocks 2021-03-21 18:46:41 -07:00
Alex Forencich
63e6eafc07 Handle dropped transmit frames during reset 2021-03-21 14:08:27 -07:00
Alex Forencich
f4054cfd65 Ensure idle event is set when queue is empty 2021-03-21 13:03:17 -07:00
Alex Forencich
448815b16d Reset processing on assert edge only 2021-03-21 12:25:09 -07:00
Alex Forencich
78bc288812 Use start_soon instead of fork 2021-03-21 12:22:51 -07:00
Alex Forencich
2b030f120d Bump to dev version 2021-03-17 18:55:03 -07:00
Alex Forencich
facd770568 Release v0.1.8 2021-03-17 18:52:05 -07:00
Alex Forencich
faad752b9a Use release version of cocotb for CI 2021-03-17 18:14:50 -07:00
Alex Forencich
5855644670 Use cocotb async queues 2021-03-17 18:13:04 -07:00
Alex Forencich
a81d43216e Bump to dev version v0.1.7 2021-03-06 18:39:03 -08:00
Alex Forencich
50a60b649e Release v0.1.6 2021-03-06 18:38:18 -08:00
Alex Forencich
bf19679007 Update readme 2021-03-06 18:34:50 -08:00
Alex Forencich
6835e921f8 Add reset_active_level parameters 2021-03-06 18:34:20 -08:00
Alex Forencich
1b180a1d64 Clean up reset implementation 2021-03-06 18:31:40 -08:00
Alex Forencich
63b60341ed Clear sim time fields on start transmit 2021-01-08 16:25:28 -08:00
Alex Forencich
f92bbaaa70 Update readme 2021-01-05 23:16:00 -08:00
Alex Forencich
a73c0b734e Support override of tx_complete 2021-01-04 22:42:53 -08:00
Alex Forencich
aa97848450 Store SFD transfer sim time 2021-01-03 23:28:33 -08:00
Alex Forencich
1bd01ae879 Fix RGMII error indication 2021-01-03 23:26:31 -08:00
Alex Forencich
cfbc80c0cb Improve transfer tracking 2021-01-03 22:55:09 -08:00
Alex Forencich
1d5688778a Add clear method 2021-01-03 12:52:21 -08:00
Alex Forencich
71d7c7e9d2 Remove extraneous code 2020-12-31 03:12:14 -08:00
Alex Forencich
30bc6f68a1 Rework sim_build output directory, fix default makefile target 2020-12-29 14:25:52 -08:00
Alex Forencich
16eaea6967 Bump to dev version v0.1.5 2020-12-27 23:29:34 -08:00
25 changed files with 633 additions and 313 deletions

View File

@@ -3,6 +3,7 @@
[![Build Status](https://github.com/alexforencich/cocotbext-eth/workflows/Regression%20Tests/badge.svg?branch=master)](https://github.com/alexforencich/cocotbext-eth/actions/) [![Build Status](https://github.com/alexforencich/cocotbext-eth/workflows/Regression%20Tests/badge.svg?branch=master)](https://github.com/alexforencich/cocotbext-eth/actions/)
[![codecov](https://codecov.io/gh/alexforencich/cocotbext-eth/branch/master/graph/badge.svg)](https://codecov.io/gh/alexforencich/cocotbext-eth) [![codecov](https://codecov.io/gh/alexforencich/cocotbext-eth/branch/master/graph/badge.svg)](https://codecov.io/gh/alexforencich/cocotbext-eth)
[![PyPI version](https://badge.fury.io/py/cocotbext-eth.svg)](https://pypi.org/project/cocotbext-eth) [![PyPI version](https://badge.fury.io/py/cocotbext-eth.svg)](https://pypi.org/project/cocotbext-eth)
[![Downloads](https://pepy.tech/badge/cocotbext-eth)](https://pepy.tech/project/cocotbext-eth)
GitHub repository: https://github.com/alexforencich/cocotbext-eth GitHub repository: https://github.com/alexforencich/cocotbext-eth
@@ -46,6 +47,13 @@ To send data into a design with a `GmiiSource`, call `send()` or `send_nowait()`
# wait for operation to complete (optional) # wait for operation to complete (optional)
await gmii_source.wait() await gmii_source.wait()
It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of the `GmiiFrame` object, and then awaiting the event. The frame, with simulation time fields set, will be returned in the event data. Example:
frame = GmiiFrame.from_payload(b'test data', tx_complete=Event())
await gmii_source.send(frame)
await frame.tx_complete.wait()
print(frame.tx_complete.data.sim_time_sfd)
To receive data with a `GmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data. To receive data with a `GmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data.
data = await gmii_sink.recv() data = await gmii_sink.recv()
@@ -77,6 +85,7 @@ The `GmiiPhy` class provides a model of a GMII PHY chip. It wraps instances of
* _reset_: reset signal (optional) * _reset_: reset signal (optional)
* _enable_: clock enable (optional) * _enable_: clock enable (optional)
* _mii_select_: MII mode select (optional) * _mii_select_: MII mode select (optional)
* _reset_active_level_: reset active level (optional, default `True`)
#### Attributes: #### Attributes:
@@ -92,7 +101,9 @@ The `GmiiPhy` class provides a model of a GMII PHY chip. It wraps instances of
* `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink) * `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink)
* `count()`: returns the number of items in the queue (all) * `count()`: returns the number of items in the queue (all)
* `empty()`: returns _True_ if the queue is empty (all) * `empty()`: returns _True_ if the queue is empty (all)
* `full()`: returns _True_ if the queue occupancy limits are met (source)
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
* `clear()`: drop all data in queue (all)
* `wait()`: wait for idle (source) * `wait()`: wait for idle (source)
* `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink) * `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
@@ -118,7 +129,11 @@ Attributes:
* `data`: bytearray * `data`: bytearray
* `error`: error field, optional; list, each entry qualifies the corresponding entry in `data`. * `error`: error field, optional; list, each entry qualifies the corresponding entry in `data`.
* `rx_sim_time`: simulation time when packet was received by sink. * `sim_time_start`: simulation time of first transfer cycle of frame.
* `sim_time_sfd`: simulation time at which the SFD was transferred.
* `sim_time_end`: simulation time of last transfer cycle of frame.
* `start_lane`: byte lane in which the start control character was transferred.
* `tx_complete`: event or callable triggered when frame is transmitted.
Methods: Methods:
@@ -151,6 +166,13 @@ To send data into a design with an `MiiSource`, call `send()` or `send_nowait()`
# wait for operation to complete (optional) # wait for operation to complete (optional)
await mii_source.wait() await mii_source.wait()
It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of the `GmiiFrame` object, and then awaiting the event. The frame, with simulation time fields set, will be returned in the event data. Example:
frame = GmiiFrame.from_payload(b'test data', tx_complete=Event())
await mii_source.send(frame)
await frame.tx_complete.wait()
print(frame.tx_complete.data.sim_time_sfd)
To receive data with an `MiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data. To receive data with an `MiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data.
data = await mii_sink.recv() data = await mii_sink.recv()
@@ -181,6 +203,7 @@ The `MiiPhy` class provides a model of an MII PHY chip. It wraps instances of `
* _clock_: clock signal * _clock_: clock signal
* _reset_: reset signal (optional) * _reset_: reset signal (optional)
* _enable_: clock enable (optional) * _enable_: clock enable (optional)
* _reset_active_level_: reset active level (optional, default `True`)
#### Attributes: #### Attributes:
@@ -195,7 +218,9 @@ The `MiiPhy` class provides a model of an MII PHY chip. It wraps instances of `
* `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink) * `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink)
* `count()`: returns the number of items in the queue (all) * `count()`: returns the number of items in the queue (all)
* `empty()`: returns _True_ if the queue is empty (all) * `empty()`: returns _True_ if the queue is empty (all)
* `full()`: returns _True_ if the queue occupancy limits are met (source)
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
* `clear()`: drop all data in queue (all)
* `wait()`: wait for idle (source) * `wait()`: wait for idle (source)
* `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink) * `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
@@ -232,6 +257,13 @@ To send data into a design with an `RgmiiSource`, call `send()` or `send_nowait(
# wait for operation to complete (optional) # wait for operation to complete (optional)
await rgmii_source.wait() await rgmii_source.wait()
It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of the `GmiiFrame` object, and then awaiting the event. The frame, with simulation time fields set, will be returned in the event data. Example:
frame = GmiiFrame.from_payload(b'test data', tx_complete=Event())
await rgmii_source.send(frame)
await frame.tx_complete.wait()
print(frame.tx_complete.data.sim_time_sfd)
To receive data with an `RgmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data. To receive data with an `RgmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data.
data = await rgmii_sink.recv() data = await rgmii_sink.recv()
@@ -261,6 +293,7 @@ The `RgmiiPhy` class provides a model of an RGMII PHY chip. It wraps instances
* _reset_: reset signal (optional) * _reset_: reset signal (optional)
* _enable_: clock enable (optional) * _enable_: clock enable (optional)
* _mii_select_: MII mode select (optional) * _mii_select_: MII mode select (optional)
* _reset_active_level_: reset active level (optional, default `True`)
#### Attributes: #### Attributes:
@@ -276,7 +309,9 @@ The `RgmiiPhy` class provides a model of an RGMII PHY chip. It wraps instances
* `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink) * `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink)
* `count()`: returns the number of items in the queue (all) * `count()`: returns the number of items in the queue (all)
* `empty()`: returns _True_ if the queue is empty (all) * `empty()`: returns _True_ if the queue is empty (all)
* `full()`: returns _True_ if the queue occupancy limits are met (source)
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
* `clear()`: drop all data in queue (all)
* `wait()`: wait for idle (source) * `wait()`: wait for idle (source)
* `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink) * `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
@@ -311,6 +346,13 @@ To send data into a design with an `XgmiiSource`, call `send()` or `send_nowait(
# wait for operation to complete (optional) # wait for operation to complete (optional)
await xgmii_source.wait() await xgmii_source.wait()
It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of the `XgmiiFrame` object, and then awaiting the event. The frame, with simulation time fields set, will be returned in the event data. Example:
frame = XgmiiFrame.from_payload(b'test data', tx_complete=Event())
await xgmii_source.send(frame)
await frame.tx_complete.wait()
print(frame.tx_complete.data.sim_time_sfd)
To receive data with an `XgmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data. To receive data with an `XgmiiSink`, call `recv()` or `recv_nowait()`. Optionally call `wait()` to wait for new receive data.
data = await xgmii_sink.recv() data = await xgmii_sink.recv()
@@ -327,6 +369,7 @@ To receive data with an `XgmiiSink`, call `recv()` or `recv_nowait()`. Optional
* _clock_: clock signal * _clock_: clock signal
* _reset_: reset signal (optional) * _reset_: reset signal (optional)
* _enable_: clock enable (optional) * _enable_: clock enable (optional)
* _reset_active_level_: reset active level (optional, default `True`)
#### Attributes: #### Attributes:
@@ -341,7 +384,9 @@ To receive data with an `XgmiiSink`, call `recv()` or `recv_nowait()`. Optional
* `recv_nowait()`: receive a frame as an `XgmiiFrame` (non-blocking) (sink) * `recv_nowait()`: receive a frame as an `XgmiiFrame` (non-blocking) (sink)
* `count()`: returns the number of items in the queue (all) * `count()`: returns the number of items in the queue (all)
* `empty()`: returns _True_ if the queue is empty (all) * `empty()`: returns _True_ if the queue is empty (all)
* `full()`: returns _True_ if the queue occupancy limits are met (source)
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
* `clear()`: drop all data in queue (all)
* `wait()`: wait for idle (source) * `wait()`: wait for idle (source)
* `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink) * `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
@@ -379,8 +424,11 @@ Attributes:
* `data`: bytearray * `data`: bytearray
* `ctrl`: control field, optional; list, each entry qualifies the corresponding entry in `data` as an XGMII control character. * `ctrl`: control field, optional; list, each entry qualifies the corresponding entry in `data` as an XGMII control character.
* `rx_sim_time`: simulation time when packet was received by sink. * `sim_time_start`: simulation time of first transfer cycle of frame.
* `rx_start_lane`: byte lane that the frame start control character was received in. * `sim_time_sfd`: simulation time at which the SFD was transferred.
* `sim_time_end`: simulation time of last transfer cycle of frame.
* `start_lane`: byte lane in which the start control character was transferred.
* `tx_complete`: event or callable triggered when frame is transmitted.
Methods: Methods:
@@ -429,7 +477,8 @@ Once the clock is instantiated, it will generate a continuous stream of monotoni
* _pps_: pulse-per-second signal (optional) * _pps_: pulse-per-second signal (optional)
* _clock_: clock * _clock_: clock
* _reset_: reset (optional) * _reset_: reset (optional)
* _period_ns_: clock period (nanoseconds) * _reset_active_level_: reset active level (optional, default `True`)
* _period_ns_: clock period (nanoseconds, default `6.4`)
#### Attributes: #### Attributes:

View File

@@ -25,9 +25,9 @@ THE SOFTWARE.
import logging import logging
import struct import struct
import zlib import zlib
from collections import deque
import cocotb import cocotb
from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Timer, First, Event from cocotb.triggers import RisingEdge, Timer, First, Event
from cocotb.utils import get_sim_time, get_sim_steps from cocotb.utils import get_sim_time, get_sim_steps
@@ -37,32 +37,41 @@ from .reset import Reset
class GmiiFrame: class GmiiFrame:
def __init__(self, data=None, error=None): def __init__(self, data=None, error=None, tx_complete=None):
self.data = bytearray() self.data = bytearray()
self.error = None self.error = None
self.rx_sim_time = None self.sim_time_start = None
self.sim_time_sfd = None
self.sim_time_end = None
self.tx_complete = None
if type(data) is GmiiFrame: if type(data) is GmiiFrame:
self.data = bytearray(data.data) self.data = bytearray(data.data)
self.error = data.error self.error = data.error
self.rx_sim_time = data.rx_sim_time self.sim_time_start = data.sim_time_start
self.sim_time_sfd = data.sim_time_sfd
self.sim_time_end = data.sim_time_end
self.tx_complete = data.tx_complete
else: else:
self.data = bytearray(data) self.data = bytearray(data)
self.error = error self.error = error
if tx_complete is not None:
self.tx_complete = tx_complete
@classmethod @classmethod
def from_payload(cls, payload, min_len=60): def from_payload(cls, payload, min_len=60, tx_complete=None):
payload = bytearray(payload) payload = bytearray(payload)
if len(payload) < min_len: if len(payload) < min_len:
payload.extend(bytearray(min_len-len(payload))) payload.extend(bytearray(min_len-len(payload)))
payload.extend(struct.pack('<L', zlib.crc32(payload))) payload.extend(struct.pack('<L', zlib.crc32(payload)))
return cls.from_raw_payload(payload) return cls.from_raw_payload(payload, tx_complete=tx_complete)
@classmethod @classmethod
def from_raw_payload(cls, payload): def from_raw_payload(cls, payload, tx_complete=None):
data = bytearray(ETH_PREAMBLE) data = bytearray(ETH_PREAMBLE)
data.extend(payload) data.extend(payload)
return cls(data) return cls(data, tx_complete=tx_complete)
def get_preamble_len(self): def get_preamble_len(self):
return self.data.index(EthPre.SFD)+1 return self.data.index(EthPre.SFD)+1
@@ -91,9 +100,15 @@ class GmiiFrame:
self.error = [0]*n self.error = [0]*n
def compact(self): def compact(self):
if not any(self.error): if self.error is not None and not any(self.error):
self.error = None self.error = None
def handle_tx_complete(self):
if isinstance(self.tx_complete, Event):
self.tx_complete.set(self)
elif callable(self.tx_complete):
self.tx_complete(self)
def __eq__(self, other): def __eq__(self, other):
if type(other) is GmiiFrame: if type(other) is GmiiFrame:
return self.data == other.data return self.data == other.data
@@ -102,7 +117,9 @@ class GmiiFrame:
return ( return (
f"{type(self).__name__}(data={self.data!r}, " f"{type(self).__name__}(data={self.data!r}, "
f"error={self.error!r}, " f"error={self.error!r}, "
f"rx_sim_time={self.rx_sim_time!r})" f"sim_time_start={self.sim_time_start!r}, "
f"sim_time_sfd={self.sim_time_sfd!r}, "
f"sim_time_end={self.sim_time_end!r})"
) )
def __len__(self): def __len__(self):
@@ -117,7 +134,7 @@ class GmiiFrame:
class GmiiSource(Reset): class GmiiSource(Reset):
def __init__(self, data, er, dv, clock, reset=None, enable=None, mii_select=None, *args, **kwargs): def __init__(self, data, er, dv, clock, reset=None, enable=None, mii_select=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.er = er self.er = er
@@ -135,7 +152,11 @@ class GmiiSource(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.dequeue_event = Event()
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.ifg = 12 self.ifg = 12
self.mii_mode = False self.mii_mode = False
@@ -143,6 +164,9 @@ class GmiiSource(Reset):
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
self.queue_occupancy_limit_bytes = -1
self.queue_occupancy_limit_frames = -1
self.width = 8 self.width = 8
self.byte_width = 1 self.byte_width = 1
@@ -156,29 +180,56 @@ class GmiiSource(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def send(self, frame): async def send(self, frame):
self.send_nowait(frame) while self.full():
self.dequeue_event.clear()
def send_nowait(self, frame): await self.dequeue_event.wait()
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
def send_nowait(self, frame):
if self.full():
raise QueueFull()
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def full(self):
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
return True
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
def clear(self):
while not self.queue.empty():
frame = self.queue.get_nowait()
frame.sim_time_end = None
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self): async def wait(self):
while not self.idle(): await self.idle_event.wait()
await RisingEdge(self.clock)
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -186,10 +237,6 @@ class GmiiSource(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
self.active = False self.active = False
self.data <= 0 self.data <= 0
@@ -197,6 +244,18 @@ class GmiiSource(Reset):
self.er <= 0 self.er <= 0
self.dv <= 0 self.dv <= 0
if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
self.current_frame.handle_tx_complete()
self.current_frame = None
if self.queue.empty():
self.idle_event.set()
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
ifg_cnt = 0 ifg_cnt = 0
@@ -210,11 +269,16 @@ class GmiiSource(Reset):
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
elif frame is None and self.queue: elif frame is None and not self.queue.empty():
# send frame # send frame
frame = self.queue.popleft() frame = self.queue.get_nowait()
self.dequeue_event.set()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
self.current_frame = frame
frame.sim_time_start = get_sim_time()
frame.sim_time_sfd = None
frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
@@ -235,25 +299,32 @@ class GmiiSource(Reset):
self.active = True self.active = True
if frame is not None: if frame is not None:
self.data <= frame.data.pop(0) d = frame.data.pop(0)
if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD):
frame.sim_time_sfd = get_sim_time()
self.data <= d
if self.er is not None: if self.er is not None:
self.er <= frame.error.pop(0) self.er <= frame.error.pop(0)
self.dv <= 1 self.dv <= 1
if not frame.data: if not frame.data:
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None
else: else:
self.data <= 0 self.data <= 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er <= 0
self.dv <= 0 self.dv <= 0
self.active = False self.active = False
self.idle_event.set()
class GmiiSink(Reset): class GmiiSink(Reset):
def __init__(self, data, er, dv, clock, reset=None, enable=None, mii_select=None, *args, **kwargs): def __init__(self, data, er, dv, clock, reset=None, enable=None, mii_select=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.er = er self.er = er
@@ -271,8 +342,8 @@ class GmiiSink(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.sync = Event() self.active_event = Event()
self.mii_mode = False self.mii_mode = False
@@ -290,39 +361,48 @@ class GmiiSink(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def recv(self, compact=True): def _recv(self, frame, compact=True):
while self.empty(): if self.queue.empty():
self.sync.clear() self.active_event.clear()
await self.sync.wait()
return self.recv_nowait(compact)
def recv_nowait(self, compact=True):
if self.queue:
frame = self.queue.popleft()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
if compact:
frame.compact()
return frame return frame
return None
async def recv(self, compact=True):
frame = await self.queue.get()
return self._recv(frame, compact)
def recv_nowait(self, compact=True):
frame = self.queue.get_nowait()
return self._recv(frame, compact)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def idle(self): def idle(self):
return not self.active return not self.active
def clear(self):
while not self.queue.empty():
self.queue.get_nowait()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self, timeout=0, timeout_unit=None): async def wait(self, timeout=0, timeout_unit=None):
if not self.empty(): if not self.empty():
return return
self.sync.clear()
if timeout: if timeout:
await First(self.sync.wait(), Timer(timeout, timeout_unit)) await First(self.active_event.wait(), Timer(timeout, timeout_unit))
else: else:
await self.sync.wait() await self.active_event.wait()
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -330,13 +410,13 @@ class GmiiSink(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
@@ -353,7 +433,7 @@ class GmiiSink(Reset):
if dv_val: if dv_val:
# start of frame # start of frame
frame = GmiiFrame(bytearray(), []) frame = GmiiFrame(bytearray(), [])
frame.rx_sim_time = get_sim_time() frame.sim_time_start = get_sim_time()
else: else:
if not dv_val: if not dv_val:
# end of frame # end of frame
@@ -383,24 +463,28 @@ class GmiiSink(Reset):
frame.error = error frame.error = error
frame.compact() frame.compact()
frame.sim_time_end = get_sim_time()
self.log.info("RX frame: %s", frame) self.log.info("RX frame: %s", frame)
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame) self.queue.put_nowait(frame)
self.sync.set() self.active_event.set()
frame = None frame = None
if frame is not None: if frame is not None:
if frame.sim_time_sfd is None and d_val in (EthPre.SFD, 0xD):
frame.sim_time_sfd = get_sim_time()
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
class GmiiPhy: class GmiiPhy:
def __init__(self, txd, tx_er, tx_en, tx_clk, gtx_clk, rxd, rx_er, rx_dv, rx_clk, def __init__(self, txd, tx_er, tx_en, tx_clk, gtx_clk, rxd, rx_er, rx_dv, rx_clk,
reset=None, speed=1000e6, *args, **kwargs): reset=None, reset_active_level=True, speed=1000e6, *args, **kwargs):
self.gtx_clk = gtx_clk self.gtx_clk = gtx_clk
self.tx_clk = tx_clk self.tx_clk = tx_clk
@@ -408,8 +492,8 @@ class GmiiPhy:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.tx = GmiiSink(txd, tx_er, tx_en, tx_clk, reset) self.tx = GmiiSink(txd, tx_er, tx_en, tx_clk, reset, reset_active_level=reset_active_level)
self.rx = GmiiSource(rxd, rx_er, rx_dv, rx_clk, reset) self.rx = GmiiSource(rxd, rx_er, rx_dv, rx_clk, reset_active_level=reset_active_level)
self.rx_clk.setimmediatevalue(0) self.rx_clk.setimmediatevalue(0)

View File

@@ -23,9 +23,9 @@ THE SOFTWARE.
""" """
import logging import logging
from collections import deque
import cocotb import cocotb
from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Timer, First, Event from cocotb.triggers import RisingEdge, Timer, First, Event
from cocotb.utils import get_sim_time, get_sim_steps from cocotb.utils import get_sim_time, get_sim_steps
@@ -37,7 +37,7 @@ from .reset import Reset
class MiiSource(Reset): class MiiSource(Reset):
def __init__(self, data, er, dv, clock, reset=None, enable=None, *args, **kwargs): def __init__(self, data, er, dv, clock, reset=None, enable=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.er = er self.er = er
@@ -54,13 +54,20 @@ class MiiSource(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.dequeue_event = Event()
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.ifg = 12 self.ifg = 12
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
self.queue_occupancy_limit_bytes = -1
self.queue_occupancy_limit_frames = -1
self.width = 4 self.width = 4
self.byte_width = 1 self.byte_width = 1
@@ -74,29 +81,56 @@ class MiiSource(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def send(self, frame): async def send(self, frame):
self.send_nowait(frame) while self.full():
self.dequeue_event.clear()
def send_nowait(self, frame): await self.dequeue_event.wait()
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
def send_nowait(self, frame):
if self.full():
raise QueueFull()
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def full(self):
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
return True
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
def clear(self):
while not self.queue.empty():
frame = self.queue.get_nowait()
frame.sim_time_end = None
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self): async def wait(self):
while not self.idle(): await self.idle_event.wait()
await RisingEdge(self.clock)
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -104,10 +138,6 @@ class MiiSource(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
self.active = False self.active = False
self.data <= 0 self.data <= 0
@@ -115,6 +145,18 @@ class MiiSource(Reset):
self.er <= 0 self.er <= 0
self.dv <= 0 self.dv <= 0
if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
self.current_frame.handle_tx_complete()
self.current_frame = None
if self.queue.empty():
self.idle_event.set()
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
ifg_cnt = 0 ifg_cnt = 0
@@ -128,11 +170,16 @@ class MiiSource(Reset):
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
elif frame is None and self.queue: elif frame is None and not self.queue.empty():
# send frame # send frame
frame = self.queue.popleft() frame = self.queue.get_nowait()
self.dequeue_event.set()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
self.current_frame = frame
frame.sim_time_start = get_sim_time()
frame.sim_time_sfd = None
frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
@@ -149,25 +196,32 @@ class MiiSource(Reset):
self.active = True self.active = True
if frame is not None: if frame is not None:
self.data <= frame.data.pop(0) d = frame.data.pop(0)
if frame.sim_time_sfd is None and d == 0xD:
frame.sim_time_sfd = get_sim_time()
self.data <= d
if self.er is not None: if self.er is not None:
self.er <= frame.error.pop(0) self.er <= frame.error.pop(0)
self.dv <= 1 self.dv <= 1
if not frame.data: if not frame.data:
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None
else: else:
self.data <= 0 self.data <= 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er <= 0
self.dv <= 0 self.dv <= 0
self.active = False self.active = False
self.idle_event.set()
class MiiSink(Reset): class MiiSink(Reset):
def __init__(self, data, er, dv, clock, reset=None, enable=None, *args, **kwargs): def __init__(self, data, er, dv, clock, reset=None, enable=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.er = er self.er = er
@@ -184,8 +238,8 @@ class MiiSink(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.sync = Event() self.active_event = Event()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -201,39 +255,48 @@ class MiiSink(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def recv(self, compact=True): def _recv(self, frame, compact=True):
while self.empty(): if self.queue.empty():
self.sync.clear() self.active_event.clear()
await self.sync.wait()
return self.recv_nowait(compact)
def recv_nowait(self, compact=True):
if self.queue:
frame = self.queue.popleft()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
if compact:
frame.compact()
return frame return frame
return None
async def recv(self, compact=True):
frame = await self.queue.get()
return self._recv(frame, compact)
def recv_nowait(self, compact=True):
frame = self.queue.get_nowait()
return self._recv(frame, compact)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def idle(self): def idle(self):
return not self.active return not self.active
def clear(self):
while not self.queue.empty():
self.queue.get_nowait()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self, timeout=0, timeout_unit=None): async def wait(self, timeout=0, timeout_unit=None):
if not self.empty(): if not self.empty():
return return
self.sync.clear()
if timeout: if timeout:
await First(self.sync.wait(), Timer(timeout, timeout_unit)) await First(self.active_event.wait(), Timer(timeout, timeout_unit))
else: else:
await self.sync.wait() await self.active_event.wait()
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -241,13 +304,13 @@ class MiiSink(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
@@ -264,7 +327,7 @@ class MiiSink(Reset):
if dv_val: if dv_val:
# start of frame # start of frame
frame = GmiiFrame(bytearray(), []) frame = GmiiFrame(bytearray(), [])
frame.rx_sim_time = get_sim_time() frame.sim_time_start = get_sim_time()
else: else:
if not dv_val: if not dv_val:
# end of frame # end of frame
@@ -289,30 +352,36 @@ class MiiSink(Reset):
frame.error = error frame.error = error
frame.compact() frame.compact()
frame.sim_time_end = get_sim_time()
self.log.info("RX frame: %s", frame) self.log.info("RX frame: %s", frame)
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame) self.queue.put_nowait(frame)
self.sync.set() self.active_event.set()
frame = None frame = None
if frame is not None: if frame is not None:
if frame.sim_time_sfd is None and d_val == 0xD:
frame.sim_time_sfd = get_sim_time()
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
class MiiPhy: class MiiPhy:
def __init__(self, txd, tx_er, tx_en, tx_clk, rxd, rx_er, rx_dv, rx_clk, reset=None, speed=100e6, *args, **kwargs): def __init__(self, txd, tx_er, tx_en, tx_clk, rxd, rx_er, rx_dv, rx_clk, reset=None,
reset_active_level=True, speed=100e6, *args, **kwargs):
self.tx_clk = tx_clk self.tx_clk = tx_clk
self.rx_clk = rx_clk self.rx_clk = rx_clk
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.tx = MiiSink(txd, tx_er, tx_en, tx_clk, reset) self.tx = MiiSink(txd, tx_er, tx_en, tx_clk, reset, reset_active_level=reset_active_level)
self.rx = MiiSource(rxd, rx_er, rx_dv, rx_clk, reset) self.rx = MiiSource(rxd, rx_er, rx_dv, rx_clk, reset, reset_active_level=reset_active_level)
self.tx_clk.setimmediatevalue(0) self.tx_clk.setimmediatevalue(0)
self.rx_clk.setimmediatevalue(0) self.rx_clk.setimmediatevalue(0)

View File

@@ -43,6 +43,7 @@ class PtpClock(Reset):
pps=None, pps=None,
clock=None, clock=None,
reset=None, reset=None,
reset_active_level=True,
period_ns=6.4, period_ns=6.4,
*args, **kwargs): *args, **kwargs):
@@ -90,7 +91,7 @@ class PtpClock(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
def set_period(self, ns, fns): def set_period(self, ns, fns):
self.period_ns = int(ns) self.period_ns = int(ns)
@@ -185,10 +186,6 @@ class PtpClock(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
self.ts_96_s = 0 self.ts_96_s = 0
self.ts_96_ns = 0 self.ts_96_ns = 0
@@ -204,6 +201,10 @@ class PtpClock(Reset):
self.ts_step <= 0 self.ts_step <= 0
if self.pps is not None: if self.pps is not None:
self.pps <= 0 self.pps <= 0
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
async def _run(self): async def _run(self):
while True: while True:

View File

@@ -27,13 +27,13 @@ from cocotb.triggers import RisingEdge, FallingEdge
class Reset: class Reset:
def _init_reset(self, reset_signal=None, active_high=True): def _init_reset(self, reset_signal=None, active_level=True):
self._local_reset = False self._local_reset = False
self._ext_reset = False self._ext_reset = False
self._reset_state = True self._reset_state = True
if reset_signal is not None: if reset_signal is not None:
cocotb.fork(self._run_reset(reset_signal, active_high)) cocotb.fork(self._run_reset(reset_signal, bool(active_level)))
self._update_reset() self._update_reset()
@@ -42,7 +42,7 @@ class Reset:
self.assert_reset(True) self.assert_reset(True)
self.assert_reset(False) self.assert_reset(False)
else: else:
self._local_reset = val self._local_reset = bool(val)
self._update_reset() self._update_reset()
def _update_reset(self): def _update_reset(self):
@@ -54,13 +54,13 @@ class Reset:
def _handle_reset(self, state): def _handle_reset(self, state):
pass pass
async def _run_reset(self, reset_signal, active_high): async def _run_reset(self, reset_signal, active_level):
while True: while True:
if bool(reset_signal.value): if bool(reset_signal.value):
await FallingEdge(reset_signal) await FallingEdge(reset_signal)
self._ext_reset = not active_high self._ext_reset = not active_level
self._update_reset() self._update_reset()
else: else:
await RisingEdge(reset_signal) await RisingEdge(reset_signal)
self._ext_reset = active_high self._ext_reset = active_level
self._update_reset() self._update_reset()

View File

@@ -23,9 +23,9 @@ THE SOFTWARE.
""" """
import logging import logging
from collections import deque
import cocotb import cocotb
from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, FallingEdge, Timer, First, Event from cocotb.triggers import RisingEdge, FallingEdge, Timer, First, Event
from cocotb.utils import get_sim_time, get_sim_steps from cocotb.utils import get_sim_time, get_sim_steps
@@ -37,7 +37,9 @@ from .reset import Reset
class RgmiiSource(Reset): class RgmiiSource(Reset):
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None, *args, **kwargs): def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None,
reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.ctrl = ctrl self.ctrl = ctrl
@@ -54,7 +56,11 @@ class RgmiiSource(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.dequeue_event = Event()
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.ifg = 12 self.ifg = 12
self.mii_mode = False self.mii_mode = False
@@ -62,6 +68,9 @@ class RgmiiSource(Reset):
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
self.queue_occupancy_limit_bytes = -1
self.queue_occupancy_limit_frames = -1
self.width = 8 self.width = 8
self.byte_width = 1 self.byte_width = 1
@@ -72,29 +81,56 @@ class RgmiiSource(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def send(self, frame): async def send(self, frame):
self.send_nowait(frame) while self.full():
self.dequeue_event.clear()
def send_nowait(self, frame): await self.dequeue_event.wait()
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
def send_nowait(self, frame):
if self.full():
raise QueueFull()
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def full(self):
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
return True
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
def clear(self):
while not self.queue.empty():
frame = self.queue.get_nowait()
frame.sim_time_end = None
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self): async def wait(self):
while not self.idle(): await self.idle_event.wait()
await RisingEdge(self.clock)
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -102,15 +138,23 @@ class RgmiiSource(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
self.active = False self.active = False
self.data <= 0 self.data <= 0
self.ctrl <= 0 self.ctrl <= 0
if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
self.current_frame.handle_tx_complete()
self.current_frame = None
if self.queue.empty():
self.idle_event.set()
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
ifg_cnt = 0 ifg_cnt = 0
@@ -122,7 +166,6 @@ class RgmiiSource(Reset):
while True: while True:
await RisingEdge(self.clock) await RisingEdge(self.clock)
if not self.mii_mode:
# send high nibble after rising edge, leading in to falling edge # send high nibble after rising edge, leading in to falling edge
self.data <= d >> 4 self.data <= d >> 4
self.ctrl <= en ^ er self.ctrl <= en ^ er
@@ -132,11 +175,16 @@ class RgmiiSource(Reset):
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
elif frame is None and self.queue: elif frame is None and not self.queue.empty():
# send frame # send frame
frame = self.queue.popleft() frame = self.queue.get_nowait()
self.dequeue_event.set()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
self.current_frame = frame
frame.sim_time_start = get_sim_time()
frame.sim_time_sfd = None
frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
@@ -147,8 +195,8 @@ class RgmiiSource(Reset):
mii_data = [] mii_data = []
mii_error = [] mii_error = []
for b, e in zip(frame.data, frame.error): for b, e in zip(frame.data, frame.error):
mii_data.append(b & 0x0F) mii_data.append((b & 0x0F)*0x11)
mii_data.append(b >> 4) mii_data.append((b >> 4)*0x11)
mii_error.append(e) mii_error.append(e)
mii_error.append(e) mii_error.append(e)
frame.data = mii_data frame.data = mii_data
@@ -161,14 +209,21 @@ class RgmiiSource(Reset):
er = frame.error.pop(0) er = frame.error.pop(0)
en = 1 en = 1
if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD, 0xDD):
frame.sim_time_sfd = get_sim_time()
if not frame.data: if not frame.data:
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None
else: else:
d = 0 d = 0
er = 0 er = 0
en = 0 en = 0
self.active = False self.active = False
self.idle_event.set()
await FallingEdge(self.clock) await FallingEdge(self.clock)
@@ -179,7 +234,9 @@ class RgmiiSource(Reset):
class RgmiiSink(Reset): class RgmiiSink(Reset):
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None, *args, **kwargs): def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None,
reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.ctrl = ctrl self.ctrl = ctrl
@@ -196,8 +253,8 @@ class RgmiiSink(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.sync = Event() self.active_event = Event()
self.mii_mode = False self.mii_mode = False
@@ -212,39 +269,48 @@ class RgmiiSink(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def recv(self, compact=True): def _recv(self, frame, compact=True):
while self.empty(): if self.queue.empty():
self.sync.clear() self.active_event.clear()
await self.sync.wait()
return self.recv_nowait(compact)
def recv_nowait(self, compact=True):
if self.queue:
frame = self.queue.popleft()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
if compact:
frame.compact()
return frame return frame
return None
async def recv(self, compact=True):
frame = await self.queue.get()
return self._recv(frame, compact)
def recv_nowait(self, compact=True):
frame = self.queue.get_nowait()
return self._recv(frame, compact)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def idle(self): def idle(self):
return not self.active return not self.active
def clear(self):
while not self.queue.empty():
self.queue.get_nowait()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self, timeout=0, timeout_unit=None): async def wait(self, timeout=0, timeout_unit=None):
if not self.empty(): if not self.empty():
return return
self.sync.clear()
if timeout: if timeout:
await First(self.sync.wait(), Timer(timeout, timeout_unit)) await First(self.active_event.wait(), Timer(timeout, timeout_unit))
else: else:
await self.sync.wait() await self.active_event.wait()
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -252,13 +318,13 @@ class RgmiiSink(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
@@ -285,7 +351,7 @@ class RgmiiSink(Reset):
if dv_val: if dv_val:
# start of frame # start of frame
frame = GmiiFrame(bytearray(), []) frame = GmiiFrame(bytearray(), [])
frame.rx_sim_time = get_sim_time() frame.sim_time_start = get_sim_time()
else: else:
if not dv_val: if not dv_val:
# end of frame # end of frame
@@ -315,30 +381,36 @@ class RgmiiSink(Reset):
frame.error = error frame.error = error
frame.compact() frame.compact()
frame.sim_time_end = get_sim_time()
self.log.info("RX frame: %s", frame) self.log.info("RX frame: %s", frame)
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame) self.queue.put_nowait(frame)
self.sync.set() self.active_event.set()
frame = None frame = None
if frame is not None: if frame is not None:
if frame.sim_time_sfd is None and d_val in (EthPre.SFD, 0xD, 0xDD):
frame.sim_time_sfd = get_sim_time()
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
class RgmiiPhy: class RgmiiPhy:
def __init__(self, txd, tx_ctl, tx_clk, rxd, rx_ctl, rx_clk, reset=None, speed=1000e6, *args, **kwargs): def __init__(self, txd, tx_ctl, tx_clk, rxd, rx_ctl, rx_clk, reset=None,
reset_active_level=True, speed=1000e6, *args, **kwargs):
self.tx_clk = tx_clk self.tx_clk = tx_clk
self.rx_clk = rx_clk self.rx_clk = rx_clk
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.tx = RgmiiSink(txd, tx_ctl, tx_clk, reset) self.tx = RgmiiSink(txd, tx_ctl, tx_clk, reset, reset_active_level=reset_active_level)
self.rx = RgmiiSource(rxd, rx_ctl, rx_clk, reset) self.rx = RgmiiSource(rxd, rx_ctl, rx_clk, reset, reset_active_level=reset_active_level)
self.rx_clk.setimmediatevalue(0) self.rx_clk.setimmediatevalue(0)

View File

@@ -1 +1 @@
__version__ = "0.1.4" __version__ = "0.1.10"

View File

@@ -25,9 +25,9 @@ THE SOFTWARE.
import logging import logging
import struct import struct
import zlib import zlib
from collections import deque
import cocotb import cocotb
from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Timer, First, Event from cocotb.triggers import RisingEdge, Timer, First, Event
from cocotb.utils import get_sim_time from cocotb.utils import get_sim_time
@@ -37,34 +37,43 @@ from .reset import Reset
class XgmiiFrame: class XgmiiFrame:
def __init__(self, data=None, ctrl=None): def __init__(self, data=None, ctrl=None, tx_complete=None):
self.data = bytearray() self.data = bytearray()
self.ctrl = None self.ctrl = None
self.rx_sim_time = None self.sim_time_start = None
self.rx_start_lane = None self.sim_time_sfd = None
self.sim_time_end = None
self.start_lane = None
self.tx_complete = None
if type(data) is XgmiiFrame: if type(data) is XgmiiFrame:
self.data = bytearray(data.data) self.data = bytearray(data.data)
self.ctrl = data.ctrl self.ctrl = data.ctrl
self.rx_sim_time = data.rx_sim_time self.sim_time_start = data.sim_time_start
self.rx_start_lane = data.rx_start_lane self.sim_time_sfd = data.sim_time_sfd
self.sim_time_end = data.sim_time_end
self.start_lane = data.start_lane
self.tx_complete = data.tx_complete
else: else:
self.data = bytearray(data) self.data = bytearray(data)
self.ctrl = ctrl self.ctrl = ctrl
if tx_complete is not None:
self.tx_complete = tx_complete
@classmethod @classmethod
def from_payload(cls, payload, min_len=60): def from_payload(cls, payload, min_len=60, tx_complete=None):
payload = bytearray(payload) payload = bytearray(payload)
if len(payload) < min_len: if len(payload) < min_len:
payload.extend(bytearray(min_len-len(payload))) payload.extend(bytearray(min_len-len(payload)))
payload.extend(struct.pack('<L', zlib.crc32(payload))) payload.extend(struct.pack('<L', zlib.crc32(payload)))
return cls.from_raw_payload(payload) return cls.from_raw_payload(payload, tx_complete=tx_complete)
@classmethod @classmethod
def from_raw_payload(cls, payload): def from_raw_payload(cls, payload, tx_complete=None):
data = bytearray(ETH_PREAMBLE) data = bytearray(ETH_PREAMBLE)
data.extend(payload) data.extend(payload)
return cls(data) return cls(data, tx_complete=tx_complete)
def get_preamble_len(self): def get_preamble_len(self):
return self.data.index(EthPre.SFD)+1 return self.data.index(EthPre.SFD)+1
@@ -93,9 +102,15 @@ class XgmiiFrame:
self.ctrl = [0]*n self.ctrl = [0]*n
def compact(self): def compact(self):
if not any(self.ctrl): if self.ctrl is not None and not any(self.ctrl):
self.ctrl = None self.ctrl = None
def handle_tx_complete(self):
if isinstance(self.tx_complete, Event):
self.tx_complete.set(self)
elif callable(self.tx_complete):
self.tx_complete(self)
def __eq__(self, other): def __eq__(self, other):
if type(other) is XgmiiFrame: if type(other) is XgmiiFrame:
return self.data == other.data return self.data == other.data
@@ -104,8 +119,10 @@ class XgmiiFrame:
return ( return (
f"{type(self).__name__}(data={self.data!r}, " f"{type(self).__name__}(data={self.data!r}, "
f"ctrl={self.ctrl!r}, " f"ctrl={self.ctrl!r}, "
f"rx_sim_time={self.rx_sim_time!r}, " f"sim_time_start={self.sim_time_start!r}, "
f"rx_start_lane={self.rx_start_lane!r})" f"sim_time_sfd={self.sim_time_sfd!r}, "
f"sim_time_end={self.sim_time_end!r}, "
f"start_lane={self.start_lane!r})"
) )
def __len__(self): def __len__(self):
@@ -120,7 +137,7 @@ class XgmiiFrame:
class XgmiiSource(Reset): class XgmiiSource(Reset):
def __init__(self, data, ctrl, clock, reset=None, enable=None, *args, **kwargs): def __init__(self, data, ctrl, clock, reset=None, enable=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.ctrl = ctrl self.ctrl = ctrl
@@ -136,7 +153,11 @@ class XgmiiSource(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.dequeue_event = Event()
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.enable_dic = True self.enable_dic = True
self.ifg = 12 self.ifg = 12
@@ -145,6 +166,9 @@ class XgmiiSource(Reset):
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
self.queue_occupancy_limit_bytes = -1
self.queue_occupancy_limit_frames = -1
self.width = len(self.data) self.width = len(self.data)
self.byte_width = len(self.ctrl) self.byte_width = len(self.ctrl)
@@ -162,29 +186,56 @@ class XgmiiSource(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def send(self, frame): async def send(self, frame):
self.send_nowait(frame) while self.full():
self.dequeue_event.clear()
def send_nowait(self, frame): await self.dequeue_event.wait()
frame = XgmiiFrame(frame) frame = XgmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
def send_nowait(self, frame):
if self.full():
raise QueueFull()
frame = XgmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def full(self):
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
return True
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
def clear(self):
while not self.queue.empty():
frame = self.queue.get_nowait()
frame.sim_time_end = None
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self): async def wait(self):
while not self.idle(): await self.idle_event.wait()
await RisingEdge(self.clock)
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -192,15 +243,23 @@ class XgmiiSource(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
self.active = False self.active = False
self.data <= 0 self.data <= 0
self.ctrl <= 0 self.ctrl <= 0
if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
self.current_frame.handle_tx_complete()
self.current_frame = None
if self.queue.empty():
self.idle_event.set()
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
ifg_cnt = 0 ifg_cnt = 0
@@ -221,13 +280,19 @@ class XgmiiSource(Reset):
elif frame is None: elif frame is None:
# idle # idle
if self.queue: if not self.queue.empty():
# send frame # send frame
frame = self.queue.popleft() frame = self.queue.get_nowait()
self.dequeue_event.set()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
self.current_frame = frame
frame.sim_time_start = get_sim_time()
frame.sim_time_sfd = None
frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
frame.start_lane = 0
assert frame.data[0] == EthPre.PRE assert frame.data[0] == EthPre.PRE
assert frame.ctrl[0] == 0 assert frame.ctrl[0] == 0
frame.data[0] = XgmiiCtrl.START frame.data[0] = XgmiiCtrl.START
@@ -243,6 +308,7 @@ class XgmiiSource(Reset):
if self.byte_width > 4 and (ifg_cnt > min_ifg or self.force_offset_start): if self.byte_width > 4 and (ifg_cnt > min_ifg or self.force_offset_start):
ifg_cnt = ifg_cnt-4 ifg_cnt = ifg_cnt-4
frame.start_lane = 4
frame.data = bytearray([XgmiiCtrl.IDLE]*4)+frame.data frame.data = bytearray([XgmiiCtrl.IDLE]*4)+frame.data
frame.ctrl = [1]*4+frame.ctrl frame.ctrl = [1]*4+frame.ctrl
@@ -261,12 +327,18 @@ class XgmiiSource(Reset):
for k in range(self.byte_width): for k in range(self.byte_width):
if frame is not None: if frame is not None:
d_val |= frame.data.pop(0) << k*8 d = frame.data.pop(0)
if frame.sim_time_sfd is None and d == EthPre.SFD:
frame.sim_time_sfd = get_sim_time()
d_val |= d << k*8
c_val |= frame.ctrl.pop(0) << k c_val |= frame.ctrl.pop(0) << k
if not frame.data: if not frame.data:
ifg_cnt = max(self.ifg - (self.byte_width-k), 0) ifg_cnt = max(self.ifg - (self.byte_width-k), 0)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None
else: else:
d_val |= XgmiiCtrl.IDLE << k*8 d_val |= XgmiiCtrl.IDLE << k*8
c_val |= 1 << k c_val |= 1 << k
@@ -277,11 +349,12 @@ class XgmiiSource(Reset):
self.data <= self.idle_d self.data <= self.idle_d
self.ctrl <= self.idle_c self.ctrl <= self.idle_c
self.active = False self.active = False
self.idle_event.set()
class XgmiiSink(Reset): class XgmiiSink(Reset):
def __init__(self, data, ctrl, clock, reset=None, enable=None, *args, **kwargs): def __init__(self, data, ctrl, clock, reset=None, enable=None, reset_active_level=True, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.{data._path}") self.log = logging.getLogger(f"cocotb.{data._path}")
self.data = data self.data = data
self.ctrl = ctrl self.ctrl = ctrl
@@ -297,8 +370,8 @@ class XgmiiSink(Reset):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.active = False self.active = False
self.queue = deque() self.queue = Queue()
self.sync = Event() self.active_event = Event()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -310,39 +383,48 @@ class XgmiiSink(Reset):
self._run_cr = None self._run_cr = None
self._init_reset(reset) self._init_reset(reset, reset_active_level)
async def recv(self, compact=True): def _recv(self, frame, compact=True):
while self.empty(): if self.queue.empty():
self.sync.clear() self.active_event.clear()
await self.sync.wait()
return self.recv_nowait(compact)
def recv_nowait(self, compact=True):
if self.queue:
frame = self.queue.popleft()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
if compact:
frame.compact()
return frame return frame
return None
async def recv(self, compact=True):
frame = await self.queue.get()
return self._recv(frame, compact)
def recv_nowait(self, compact=True):
frame = self.queue.get_nowait()
return self._recv(frame, compact)
def count(self): def count(self):
return len(self.queue) return self.queue.qsize()
def empty(self): def empty(self):
return not self.queue return self.queue.empty()
def idle(self): def idle(self):
return not self.active return not self.active
def clear(self):
while not self.queue.empty():
self.queue.get_nowait()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
async def wait(self, timeout=0, timeout_unit=None): async def wait(self, timeout=0, timeout_unit=None):
if not self.empty(): if not self.empty():
return return
self.sync.clear()
if timeout: if timeout:
await First(self.sync.wait(), Timer(timeout, timeout_unit)) await First(self.active_event.wait(), Timer(timeout, timeout_unit))
else: else:
await self.sync.wait() await self.active_event.wait()
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -350,13 +432,13 @@ class XgmiiSink(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
@@ -373,8 +455,8 @@ class XgmiiSink(Reset):
if c_val and d_val == XgmiiCtrl.START: if c_val and d_val == XgmiiCtrl.START:
# start # start
frame = XgmiiFrame(bytearray([EthPre.PRE]), [0]) frame = XgmiiFrame(bytearray([EthPre.PRE]), [0])
frame.rx_sim_time = get_sim_time() frame.sim_time_start = get_sim_time()
frame.rx_start_lane = offset frame.start_lane = offset
else: else:
if c_val: if c_val:
# got a control character; terminate frame reception # got a control character; terminate frame reception
@@ -384,15 +466,19 @@ class XgmiiSink(Reset):
frame.ctrl.append(c_val) frame.ctrl.append(c_val)
frame.compact() frame.compact()
frame.sim_time_end = get_sim_time()
self.log.info("RX frame: %s", frame) self.log.info("RX frame: %s", frame)
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
self.queue.append(frame) self.queue.put_nowait(frame)
self.sync.set() self.active_event.set()
frame = None frame = None
else: else:
if frame.sim_time_sfd is None and d_val == EthPre.SFD:
frame.sim_time_sfd = get_sim_time()
frame.data.append(d_val) frame.data.append(d_val)
frame.ctrl.append(c_val) frame.ctrl.append(c_val)

View File

@@ -65,7 +65,6 @@ deps =
cocotb-test cocotb-test
coverage coverage
pytest-cov pytest-cov
git+https://github.com/cocotb/cocotb.git@e892a3ea48#egg=cocotb
commands = commands =
pytest --cov=cocotbext --cov=tests --cov-branch -n auto pytest --cov=cocotbext --cov=tests --cov-branch -n auto

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -144,7 +144,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_gmii(request): def test_gmii(request):
@@ -160,8 +159,8 @@ def test_gmii(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -154,7 +154,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_gmii_phy(request): def test_gmii_phy(request):
@@ -170,8 +169,8 @@ def test_gmii_phy(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -141,7 +141,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_mii(request): def test_mii(request):
@@ -157,8 +156,8 @@ def test_mii(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -145,7 +145,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_mii_phy(request): def test_mii_phy(request):
@@ -161,8 +160,8 @@ def test_mii_phy(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -294,7 +294,6 @@ async def run_drift_adjustment(dut):
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_ptp_clock(request): def test_ptp_clock(request):
@@ -310,8 +309,8 @@ def test_ptp_clock(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -141,7 +141,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_rgmii(request): def test_rgmii(request):
@@ -157,8 +156,8 @@ def test_rgmii(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -31,8 +31,6 @@ TOPLEVEL = $(DUT)
MODULE = $(DUT) MODULE = $(DUT)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
SIM_BUILD ?= sim_build_$(MODULE)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -48,6 +46,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -57,9 +57,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -158,7 +158,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
def test_rgmii_phy(request): def test_rgmii_phy(request):
@@ -174,8 +173,8 @@ def test_rgmii_phy(request):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],

View File

@@ -35,8 +35,6 @@ VERILOG_SOURCES += $(DUT).v
export PARAM_DATA_WIDTH ?= 64 export PARAM_DATA_WIDTH ?= 64
export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 ) export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
@@ -58,6 +56,8 @@ else ifeq ($(SIM), verilator)
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v: iverilog_dump.v:
echo 'module iverilog_dump();' > $@ echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@ echo 'initial begin' >> $@
@@ -67,9 +67,5 @@ iverilog_dump.v:
echo 'endmodule' >> $@ echo 'endmodule' >> $@
clean:: clean::
@rm -rf sim_build_*
@rm -rf iverilog_dump.v @rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst @rm -rf dump.fst $(TOPLEVEL).fst
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -150,7 +150,7 @@ async def run_test_alignment(dut, payload_data=None, ifg=12, enable_dic=True,
assert rx_frame.check_fcs() assert rx_frame.check_fcs()
assert rx_frame.ctrl is None assert rx_frame.ctrl is None
start_lane.append(rx_frame.rx_start_lane) start_lane.append(rx_frame.start_lane)
tb.log.info("length: %d", length) tb.log.info("length: %d", length)
tb.log.info("start_lane: %s", start_lane) tb.log.info("start_lane: %s", start_lane)
@@ -229,7 +229,6 @@ if cocotb.SIM_NAME:
# cocotb-test # cocotb-test
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
@pytest.mark.parametrize("data_width", [32, 64]) @pytest.mark.parametrize("data_width", [32, 64])
@@ -249,8 +248,8 @@ def test_xgmii(request, data_width):
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, sim_build = os.path.join(tests_dir, "sim_build",
"sim_build_"+request.node.name.replace('[', '-').replace(']', '')) request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run( cocotb_test.simulator.run(
python_search=[tests_dir], python_search=[tests_dir],