Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50a60b649e | ||
|
|
bf19679007 | ||
|
|
6835e921f8 | ||
|
|
1b180a1d64 | ||
|
|
63b60341ed | ||
|
|
f92bbaaa70 | ||
|
|
a73c0b734e | ||
|
|
aa97848450 | ||
|
|
1bd01ae879 | ||
|
|
cfbc80c0cb | ||
|
|
1d5688778a | ||
|
|
71d7c7e9d2 | ||
|
|
30bc6f68a1 | ||
|
|
16eaea6967 | ||
|
|
a1b88dfbf1 | ||
|
|
15ff9f9907 | ||
|
|
cda8910ccf | ||
|
|
7404c5cf5f | ||
|
|
2e0502fc6e | ||
|
|
5eeffc0c68 | ||
|
|
a3df4ed1f9 | ||
|
|
318f48785a | ||
|
|
4b768e267d | ||
|
|
a8a1bbde30 | ||
|
|
e7a3850dd2 | ||
|
|
d97208d3c8 | ||
|
|
0456e4af60 | ||
|
|
c550a7315e | ||
|
|
e858721dcc | ||
|
|
19306c9b55 | ||
|
|
4dd82f1499 | ||
|
|
c3207d68dc | ||
|
|
7a5473ab39 | ||
|
|
af8fda0250 | ||
|
|
58b22a2bd4 | ||
|
|
fb553347d7 |
264
README.md
264
README.md
@@ -3,6 +3,7 @@
|
|||||||
[](https://github.com/alexforencich/cocotbext-eth/actions/)
|
[](https://github.com/alexforencich/cocotbext-eth/actions/)
|
||||||
[](https://codecov.io/gh/alexforencich/cocotbext-eth)
|
[](https://codecov.io/gh/alexforencich/cocotbext-eth)
|
||||||
[](https://pypi.org/project/cocotbext-eth)
|
[](https://pypi.org/project/cocotbext-eth)
|
||||||
|
[](https://pepy.tech/project/cocotbext-eth)
|
||||||
|
|
||||||
GitHub repository: https://github.com/alexforencich/cocotbext-eth
|
GitHub repository: https://github.com/alexforencich/cocotbext-eth
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ See the `tests` directory and [verilog-ethernet](https://github.com/alexforencic
|
|||||||
|
|
||||||
### GMII
|
### GMII
|
||||||
|
|
||||||
The `GmiiSource` and `GmiiSink` classes can be used to drive, receive, and monitor GMII traffic. The `GmiiSource` drives GMII traffic into a design. The `GmiiSink` receives GMII traffic, including monitoring internal interfaces.
|
The `GmiiSource` and `GmiiSink` classes can be used to drive, receive, and monitor GMII traffic. The `GmiiSource` drives GMII traffic into a design. The `GmiiSink` receives GMII traffic, including monitoring internal interfaces. The `GmiiPhy` class is a wrapper around `GmiiSource` and `GmiiSink` that also provides clocking and rate-switching to emulate a GMII PHY chip.
|
||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
@@ -40,15 +41,34 @@ To use these modules, import the one you need and connect it to the DUT:
|
|||||||
gmii_source = GmiiSource(dut.rxd, dut.rx_er, dut.rx_en, dut.clk, dut.rst)
|
gmii_source = GmiiSource(dut.rxd, dut.rx_er, dut.rx_en, dut.clk, dut.rst)
|
||||||
gmii_sink = GmiiSink(dut.txd, dut.tx_er, dut.tx_en, dut.clk, dut.rst)
|
gmii_sink = GmiiSink(dut.txd, dut.tx_er, dut.tx_en, dut.clk, dut.rst)
|
||||||
|
|
||||||
To send data into a design with an `GmiiSource`, call `send()`. Accepted data types are iterables that can be converted to bytearray or `GmiiFrame` objects. Call `wait()` to wait for the transmit operation to complete. Example:
|
To send data into a design with a `GmiiSource`, call `send()` or `send_nowait()`. Accepted data types are iterables that can be converted to bytearray or `GmiiFrame` objects. Optionally, call `wait()` to wait for the transmit operation to complete. Example:
|
||||||
|
|
||||||
gmii_source.send(GmiiFrame.from_payload(b'test data'))
|
await gmii_source.send(GmiiFrame.from_payload(b'test data'))
|
||||||
|
# wait for operation to complete (optional)
|
||||||
await gmii_source.wait()
|
await gmii_source.wait()
|
||||||
|
|
||||||
To receive data with a `GmiiSink`, call `recv()`. Call `wait()` to wait for new receive data.
|
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:
|
||||||
|
|
||||||
await gmii_sink.wait()
|
frame = GmiiFrame.from_payload(b'test data', tx_complete=Event())
|
||||||
data = gmii_sink.recv()
|
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.
|
||||||
|
|
||||||
|
data = await gmii_sink.recv()
|
||||||
|
|
||||||
|
The `GmiiPhy` class provides a model of a GMII PHY chip. It wraps instances of `GmiiSource` (`rx`) and `GmiiSink` (`tx`), provides the necessary clocking components, and provides the `set_speed()` method to change the link speed. `set_speed()` changes the `tx_clk` and `rx_clk` frequencies, switches between `gtx_clk` and `tx_clk`, and selects the appropriate mode (MII or GMII) on the source and sink instances. In general, the `GmiiPhy` class is intended to be used for integration tests where the design expects to be directly connected to an external GMII PHY chip and contains all of the necessary IO and clocking logic. Example:
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, GmiiPhy
|
||||||
|
|
||||||
|
gmii_phy = GmiiPhy(dut.txd, dut.tx_er, dut.tx_en, dut.tx_clk, dut.gtx_clk,
|
||||||
|
dut.rxd, dut.rx_er, dut.rx_en, dut.rx_clk, dut.rst, speed=1000e6)
|
||||||
|
|
||||||
|
gmii_phy.set_speed(100e6)
|
||||||
|
|
||||||
|
await gmii_phy.rx.send(GmiiFrame.from_payload(b'test RX data'))
|
||||||
|
tx_data = await gmii_phy.tx.recv()
|
||||||
|
|
||||||
#### Signals
|
#### Signals
|
||||||
|
|
||||||
@@ -65,22 +85,41 @@ To receive data with a `GmiiSink`, call `recv()`. Call `wait()` to wait for new
|
|||||||
* _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:
|
||||||
|
|
||||||
* _queue_occupancy_bytes_: number of bytes in queue
|
* _queue_occupancy_bytes_: number of bytes in queue
|
||||||
* _queue_occupancy_frames_: number of frames in queue
|
* _queue_occupancy_frames_: number of frames in queue
|
||||||
|
* _mii_mode_: control MII mode when _mii_select_ signal is not connected
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
* `send(frame)`: send _frame_ (source)
|
* `send(frame)`: send _frame_ (blocking) (source)
|
||||||
* `recv()`: receive a frame as a `GmiiFrame` (sink)
|
* `send_nowait(frame)`: send _frame_ (non-blocking) (source)
|
||||||
|
* `recv()`: receive a frame as a `GmiiFrame` (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)
|
||||||
* `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)
|
||||||
|
|
||||||
|
#### GMII timing diagram
|
||||||
|
|
||||||
|
Example transfer via GMII at 1 Gbps:
|
||||||
|
|
||||||
|
__ __ __ __ _ __ __ __ __
|
||||||
|
tx_clk __/ \__/ \__/ \__/ \__/ ... _/ \__/ \__/ \__/ \__
|
||||||
|
_____ _____ _____ _ _ _____ _____
|
||||||
|
tx_d[7:0] XXXXXXXXX_55__X_55__X_55__X_ ... _X_72__X_fb__XXXXXXXXXXXX
|
||||||
|
|
||||||
|
tx_er ____________________________ ... _________________________
|
||||||
|
___________________ _____________
|
||||||
|
tx_en ________/ ... \___________
|
||||||
|
|
||||||
|
|
||||||
#### GmiiFrame object
|
#### GmiiFrame object
|
||||||
|
|
||||||
The `GmiiFrame` object is a container for a frame to be transferred via GMII. The `data` field contains the packet data in the form of a list of bytes. `error` contains the `er` signal level state associated with each byte as a list of ints.
|
The `GmiiFrame` object is a container for a frame to be transferred via GMII. The `data` field contains the packet data in the form of a list of bytes. `error` contains the `er` signal level state associated with each byte as a list of ints.
|
||||||
@@ -89,7 +128,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:
|
||||||
|
|
||||||
@@ -103,9 +146,99 @@ Methods:
|
|||||||
* `normalize()`: pack `error` to the same length as `data`, replicating last element if necessary, initialize to list of `0` if not specified.
|
* `normalize()`: pack `error` to the same length as `data`, replicating last element if necessary, initialize to list of `0` if not specified.
|
||||||
* `compact()`: remove `error` if all zero
|
* `compact()`: remove `error` if all zero
|
||||||
|
|
||||||
|
### MII
|
||||||
|
|
||||||
|
The `MiiSource` and `MiiSink` classes can be used to drive, receive, and monitor MII traffic. The `MiiSource` drives MII traffic into a design. The `MiiSink` receives MII traffic, including monitoring internal interfaces. The `MiiPhy` class is a wrapper around `MiiSource` and `MiiSink` that also provides clocking and rate-switching to emulate an MII PHY chip.
|
||||||
|
|
||||||
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
|
from cocotbext.eth import MiiSource, MiiSink
|
||||||
|
|
||||||
|
mii_source = MiiSource(dut.rxd, dut.rx_er, dut.rx_en, dut.clk, dut.rst)
|
||||||
|
mii_sink = MiiSink(dut.txd, dut.tx_er, dut.tx_en, dut.clk, dut.rst)
|
||||||
|
|
||||||
|
All signals must be passed separately into these classes.
|
||||||
|
|
||||||
|
To send data into a design with an `MiiSource`, call `send()` or `send_nowait()`. Accepted data types are iterables that can be converted to bytearray or `GmiiFrame` objects. Optionally, call `wait()` to wait for the transmit operation to complete. Example:
|
||||||
|
|
||||||
|
await mii_source.send(GmiiFrame.from_payload(b'test data'))
|
||||||
|
# wait for operation to complete (optional)
|
||||||
|
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.
|
||||||
|
|
||||||
|
data = await mii_sink.recv()
|
||||||
|
|
||||||
|
The `MiiPhy` class provides a model of an MII PHY chip. It wraps instances of `MiiSource` (`rx`) and `MiiSink` (`tx`), provides the necessary clocking components, and provides the `set_speed()` method to change the link speed. `set_speed()` changes the `tx_clk` and `rx_clk` frequencies. In general, the `MiiPhy` class is intended to be used for integration tests where the design expects to be directly connected to an external MII PHY chip and contains all of the necessary IO and clocking logic. Example:
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, MiiPhy
|
||||||
|
|
||||||
|
mii_phy = MiiPhy(dut.txd, dut.tx_er, dut.tx_en, dut.tx_clk,
|
||||||
|
dut.rxd, dut.rx_er, dut.rx_en, dut.rx_clk, dut.rst, speed=100e6)
|
||||||
|
|
||||||
|
mii_phy.set_speed(10e6)
|
||||||
|
|
||||||
|
await mii_phy.rx.send(GmiiFrame.from_payload(b'test RX data'))
|
||||||
|
tx_data = await mii_phy.tx.recv()
|
||||||
|
|
||||||
|
#### Signals
|
||||||
|
|
||||||
|
* `txd`, `rxd`: data
|
||||||
|
* `tx_er`, `rx_er`: error (when asserted with `tx_en` or `rx_dv`)
|
||||||
|
* `tx_en`, `rx_dv`: data valid
|
||||||
|
|
||||||
|
#### Constructor parameters:
|
||||||
|
|
||||||
|
* _data_: data signal (txd, rxd, etc.)
|
||||||
|
* _er_: error signal (tx_er, rx_er, etc.) (optional)
|
||||||
|
* _dv_: data valid signal (tx_en, rx_dv, etc.)
|
||||||
|
* _clock_: clock signal
|
||||||
|
* _reset_: reset signal (optional)
|
||||||
|
* _enable_: clock enable (optional)
|
||||||
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
|
|
||||||
|
#### Attributes:
|
||||||
|
|
||||||
|
* _queue_occupancy_bytes_: number of bytes in queue
|
||||||
|
* _queue_occupancy_frames_: number of frames in queue
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
* `send(frame)`: send _frame_ (blocking) (source)
|
||||||
|
* `send_nowait(frame)`: send _frame_ (non-blocking) (source)
|
||||||
|
* `recv()`: receive a frame as a `GmiiFrame` (blocking) (sink)
|
||||||
|
* `recv_nowait()`: receive a frame as a `GmiiFrame` (non-blocking) (sink)
|
||||||
|
* `count()`: returns the number of items in the queue (all)
|
||||||
|
* `empty()`: returns _True_ if the queue is empty (all)
|
||||||
|
* `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(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
|
||||||
|
|
||||||
|
#### MII timing diagram
|
||||||
|
|
||||||
|
Example transfer via MII at 100 Mbps:
|
||||||
|
|
||||||
|
_ _ _ _ _ _ _ _ _ _
|
||||||
|
tx_clk _/ \_/ \_/ \_/ \_/ \_/ ... _/ \_/ \_/ \_/ \_
|
||||||
|
___ ___ ___ ___ _ _ ___ ___
|
||||||
|
tx_d[3:0] XXXXXX_5_X_5_X_5_X_5_X_ ... _X_f_X_b_XXXXXXXX
|
||||||
|
|
||||||
|
tx_er _______________________ ... _________________
|
||||||
|
_________________ _________
|
||||||
|
tx_en _____/ ... \_______
|
||||||
|
|
||||||
|
|
||||||
### RGMII
|
### RGMII
|
||||||
|
|
||||||
The `RgmiiSource` and `RgmiiSink` classes can be used to drive, receive, and monitor RGMII traffic. The `RgmiiSource` drives RGMII traffic into a design. The `RgmiiSink` receives RGMII traffic, including monitoring internal interfaces.
|
The `RgmiiSource` and `RgmiiSink` classes can be used to drive, receive, and monitor RGMII traffic. The `RgmiiSource` drives RGMII traffic into a design. The `RgmiiSink` receives RGMII traffic, including monitoring internal interfaces. The `RgmiiPhy` class is a wrapper around `RgmiiSource` and `RgmiiSink` that also provides clocking and rate-switching to emulate an RGMII PHY chip.
|
||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
@@ -116,15 +249,34 @@ To use these modules, import the one you need and connect it to the DUT:
|
|||||||
|
|
||||||
All signals must be passed separately into these classes.
|
All signals must be passed separately into these classes.
|
||||||
|
|
||||||
To send data into a design with an `RgmiiSource`, call `send()`. Accepted data types are iterables that can be converted to bytearray or `GmiiFrame` objects. Call `wait()` to wait for the transmit operation to complete. Example:
|
To send data into a design with an `RgmiiSource`, call `send()` or `send_nowait()`. Accepted data types are iterables that can be converted to bytearray or `GmiiFrame` objects. Optionally, call `wait()` to wait for the transmit operation to complete. Example:
|
||||||
|
|
||||||
rgmii_source.send(GmiiFrame.from_payload(b'test data'))
|
await rgmii_source.send(GmiiFrame.from_payload(b'test data'))
|
||||||
|
# wait for operation to complete (optional)
|
||||||
await rgmii_source.wait()
|
await rgmii_source.wait()
|
||||||
|
|
||||||
To receive data with an `RgmiiSink`, call `recv()`. Call `wait()` to wait for new receive data.
|
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:
|
||||||
|
|
||||||
await rgmii_sink.wait()
|
frame = GmiiFrame.from_payload(b'test data', tx_complete=Event())
|
||||||
data = rgmii_sink.recv()
|
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.
|
||||||
|
|
||||||
|
data = await rgmii_sink.recv()
|
||||||
|
|
||||||
|
The `RgmiiPhy` class provides a model of an RGMII PHY chip. It wraps instances of `RgmiiSource` (`rx`) and `RgmiiSink` (`tx`), provides the necessary clocking components, and provides the `set_speed()` method to change the link speed. `set_speed()` changes the `rx_clk` frequency and selects the appropriate mode (SDR or DDR) on the source and sink instances. In general, the `RgmiiPhy` class is intended to be used for integration tests where the design expects to be directly connected to an external RGMII PHY chip and contains all of the necessary IO and clocking logic. Example:
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, RgmiiPhy
|
||||||
|
|
||||||
|
rgmii_phy = RgmiiPhy(dut.txd, dut.tx_ctl, dut.tx_clk,
|
||||||
|
dut.rxd, dut.rx_ctl, dut.rx_clk, dut.rst, speed=1000e6)
|
||||||
|
|
||||||
|
rgmii_phy.set_speed(100e6)
|
||||||
|
|
||||||
|
await rgmii_phy.rx.send(GmiiFrame.from_payload(b'test RX data'))
|
||||||
|
tx_data = await rgmii_phy.tx.recv()
|
||||||
|
|
||||||
#### Signals
|
#### Signals
|
||||||
|
|
||||||
@@ -139,22 +291,39 @@ To receive data with an `RgmiiSink`, call `recv()`. Call `wait()` to wait for n
|
|||||||
* _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:
|
||||||
|
|
||||||
* _queue_occupancy_bytes_: number of bytes in queue
|
* _queue_occupancy_bytes_: number of bytes in queue
|
||||||
* _queue_occupancy_frames_: number of frames in queue
|
* _queue_occupancy_frames_: number of frames in queue
|
||||||
|
* _mii_mode_: control MII mode when _mii_select_ signal is not connected
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
* `send(frame)`: send _frame_ (source)
|
* `send(frame)`: send _frame_ (blocking) (source)
|
||||||
* `recv()`: receive a frame as a `GmiiFrame` (sink)
|
* `send_nowait(frame)`: send _frame_ (non-blocking) (source)
|
||||||
|
* `recv()`: receive a frame as a `GmiiFrame` (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)
|
||||||
* `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)
|
||||||
|
|
||||||
|
#### RGMII timing diagram
|
||||||
|
|
||||||
|
Example transfer via RGMII at 1 Gbps:
|
||||||
|
|
||||||
|
___ ___ ___ _ ___ ___
|
||||||
|
tx_clk _/ \___/ \___/ \___/ ... _/ \___/ \___
|
||||||
|
___ ___ ___ ___ ___ ___ ___
|
||||||
|
tx_d[3:0] XXXXXXXX_5_X_5_X_5_X_5_X_5_ ... _f_X_b_XXXXXXXXXX
|
||||||
|
___________________ _______
|
||||||
|
tx_ctl _______/ ... \_________
|
||||||
|
|
||||||
|
|
||||||
### XGMII
|
### XGMII
|
||||||
|
|
||||||
The `XgmiiSource` and `XgmiiSink` classes can be used to drive, receive, and monitor XGMII traffic. The `XgmiiSource` drives XGMII traffic into a design. The `XgmiiSink` receives XGMII traffic, including monitoring internal interfaces. The modules are capable of operating with XGMII interface widths of 32 or 64 bits.
|
The `XgmiiSource` and `XgmiiSink` classes can be used to drive, receive, and monitor XGMII traffic. The `XgmiiSource` drives XGMII traffic into a design. The `XgmiiSink` receives XGMII traffic, including monitoring internal interfaces. The modules are capable of operating with XGMII interface widths of 32 or 64 bits.
|
||||||
@@ -168,15 +337,22 @@ To use these modules, import the one you need and connect it to the DUT:
|
|||||||
|
|
||||||
All signals must be passed separately into these classes.
|
All signals must be passed separately into these classes.
|
||||||
|
|
||||||
To send data into a design with an `XgmiiSource`, call `send()`. Accepted data types are iterables that can be converted to bytearray or `XgmiiFrame` objects. Call `wait()` to wait for the transmit operation to complete. Example:
|
To send data into a design with an `XgmiiSource`, call `send()` or `send_nowait()`. Accepted data types are iterables that can be converted to bytearray or `XgmiiFrame` objects. Optionally, call `wait()` to wait for the transmit operation to complete. Example:
|
||||||
|
|
||||||
xgmii_source.send(XgmiiFrame.from_payload(b'test data'))
|
await xgmii_source.send(XgmiiFrame.from_payload(b'test data'))
|
||||||
|
# wait for operation to complete (optional)
|
||||||
await xgmii_source.wait()
|
await xgmii_source.wait()
|
||||||
|
|
||||||
To receive data with an `XgmiiSink`, call `recv()`. Call `wait()` to wait for new receive data.
|
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:
|
||||||
|
|
||||||
await xgmii_sink.wait()
|
frame = XgmiiFrame.from_payload(b'test data', tx_complete=Event())
|
||||||
data = xgmii_sink.recv()
|
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.
|
||||||
|
|
||||||
|
data = await xgmii_sink.recv()
|
||||||
|
|
||||||
#### Signals
|
#### Signals
|
||||||
|
|
||||||
@@ -190,6 +366,7 @@ To receive data with an `XgmiiSink`, call `recv()`. Call `wait()` to wait for n
|
|||||||
* _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:
|
||||||
|
|
||||||
@@ -198,14 +375,43 @@ To receive data with an `XgmiiSink`, call `recv()`. Call `wait()` to wait for n
|
|||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
* `send(frame)`: send _frame_ (source)
|
* `send(frame)`: send _frame_ (blocking) (source)
|
||||||
* `recv()`: receive a frame as an `XgmiiFrame` (sink)
|
* `send_nowait(frame)`: send _frame_ (non-blocking) (source)
|
||||||
|
* `recv()`: receive a frame as an `XgmiiFrame` (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)
|
||||||
* `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)
|
||||||
|
|
||||||
|
#### XGMII timing diagram
|
||||||
|
|
||||||
|
Example transfer via 64-bit XGMII:
|
||||||
|
|
||||||
|
__ __ __ __ __ _ __ __
|
||||||
|
tx_clk __/ \__/ \__/ \__/ \__/ \__/ ... _/ \__/ \__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[63:56] __X_07__X_d5__X_51__X_01__X_09__X_ ... _X_fb__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[55:48] __X_07__X_55__X_5a__X_00__X_08__X_ ... _X_72__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[47:40] __X_07__X_55__X_d5__X_00__X_07__X_ ... _X_0d__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[39:32] __X_07__X_55__X_d4__X_80__X_06__X_ ... _X_37__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[31:24] __X_07__X_55__X_d3__X_55__X_05__X_ ... _X_2d__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[23:16] __X_07__X_55__X_d2__X_54__X_04__X_ ... _X_2c__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[15:8] __X_07__X_55__X_d1__X_53__X_03__X_ ... _X_2b__X_07__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txd[7:0] __X_07__X_fb__X_da__X_52__X_02__X_ ... _X_2a__X_fd__
|
||||||
|
__ _____ _____ _____ _____ _____ _ _ _____ _____
|
||||||
|
txc[7:0] __X_ff__X_01__X_00__X_00__X_00__X_ ... _X_00__X_ff__
|
||||||
|
|
||||||
|
|
||||||
#### XgmiiFrame object
|
#### XgmiiFrame object
|
||||||
|
|
||||||
The `XgmiiFrame` object is a container for a frame to be transferred via XGMII. The `data` field contains the packet data in the form of a list of bytes. `ctrl` contains the control signal level state associated with each byte as a list of ints. When `ctrl` is high, the corresponding `data` byte is interpreted as an XGMII control character.
|
The `XgmiiFrame` object is a container for a frame to be transferred via XGMII. The `data` field contains the packet data in the form of a list of bytes. `ctrl` contains the control signal level state associated with each byte as a list of ints. When `ctrl` is high, the corresponding `data` byte is interpreted as an XGMII control character.
|
||||||
@@ -214,8 +420,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:
|
||||||
|
|
||||||
@@ -264,7 +473,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:
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
from .gmii import GmiiFrame, GmiiSource, GmiiSink
|
from .gmii import GmiiFrame, GmiiSource, GmiiSink, GmiiPhy
|
||||||
from .rgmii import RgmiiSource, RgmiiSink
|
from .mii import MiiSource, MiiSink, MiiPhy
|
||||||
|
from .rgmii import RgmiiSource, RgmiiSink, RgmiiPhy
|
||||||
from .xgmii import XgmiiFrame, XgmiiSource, XgmiiSink
|
from .xgmii import XgmiiFrame, XgmiiSource, XgmiiSink
|
||||||
|
|
||||||
from .ptp import PtpClock
|
from .ptp import PtpClock
|
||||||
|
|||||||
@@ -28,40 +28,50 @@ import zlib
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, ReadOnly, 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, get_sim_steps
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import EthPre, ETH_PREAMBLE
|
from .constants import EthPre, ETH_PREAMBLE
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class GmiiFrame(object):
|
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
|
||||||
@@ -93,15 +103,23 @@ class GmiiFrame(object):
|
|||||||
if not any(self.error):
|
if 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
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
f"{type(self).__name__}(data={repr(self.data)}, "
|
f"{type(self).__name__}(data={self.data!r}, "
|
||||||
f"error={repr(self.error)}, "
|
f"error={self.error!r}, "
|
||||||
f"rx_sim_time={repr(self.rx_sim_time)})"
|
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):
|
||||||
@@ -110,10 +128,13 @@ class GmiiFrame(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.data.__iter__()
|
return self.data.__iter__()
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return bytes(self.data)
|
||||||
|
|
||||||
class GmiiSource(object):
|
|
||||||
|
|
||||||
def __init__(self, data, er, dv, clock, reset=None, enable=None, mii_select=None, *args, **kwargs):
|
class GmiiSource(Reset):
|
||||||
|
|
||||||
|
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
|
||||||
@@ -134,6 +155,7 @@ class GmiiSource(object):
|
|||||||
self.queue = deque()
|
self.queue = deque()
|
||||||
|
|
||||||
self.ifg = 12
|
self.ifg = 12
|
||||||
|
self.mii_mode = False
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
@@ -141,8 +163,6 @@ class GmiiSource(object):
|
|||||||
self.width = 8
|
self.width = 8
|
||||||
self.byte_width = 1
|
self.byte_width = 1
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert len(self.data) == 8
|
assert len(self.data) == 8
|
||||||
self.data.setimmediatevalue(0)
|
self.data.setimmediatevalue(0)
|
||||||
if self.er is not None:
|
if self.er is not None:
|
||||||
@@ -151,9 +171,14 @@ class GmiiSource(object):
|
|||||||
assert len(self.dv) == 1
|
assert len(self.dv) == 1
|
||||||
self.dv.setimmediatevalue(0)
|
self.dv.setimmediatevalue(0)
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def send(self, frame):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def send(self, frame):
|
||||||
|
self.send_nowait(frame)
|
||||||
|
|
||||||
|
def send_nowait(self, frame):
|
||||||
frame = GmiiFrame(frame)
|
frame = GmiiFrame(frame)
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
@@ -168,29 +193,38 @@ class GmiiSource(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return self.empty() and not self.active
|
return self.empty() and not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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.data <= 0
|
||||||
|
if self.er is not None:
|
||||||
|
self.er <= 0
|
||||||
|
self.dv <= 0
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
ifg_cnt = 0
|
ifg_cnt = 0
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = None
|
|
||||||
ifg_cnt = 0
|
|
||||||
self.active = False
|
|
||||||
self.data <= 0
|
|
||||||
if self.er is not None:
|
|
||||||
self.er <= 0
|
|
||||||
self.dv <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.enable is None or self.enable.value:
|
if self.enable is None or self.enable.value:
|
||||||
@@ -203,10 +237,16 @@ class GmiiSource(object):
|
|||||||
frame = self.queue.popleft()
|
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
|
||||||
|
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()
|
||||||
|
|
||||||
if self.mii_select is not None and self.mii_select.value:
|
if self.mii_select is not None:
|
||||||
|
self.mii_mode = bool(self.mii_select.value.integer)
|
||||||
|
|
||||||
|
if self.mii_mode:
|
||||||
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):
|
||||||
@@ -220,13 +260,18 @@ class GmiiSource(object):
|
|||||||
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
|
||||||
else:
|
else:
|
||||||
self.data <= 0
|
self.data <= 0
|
||||||
@@ -236,9 +281,9 @@ class GmiiSource(object):
|
|||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
|
||||||
class GmiiSink(object):
|
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
|
||||||
@@ -259,23 +304,31 @@ class GmiiSink(object):
|
|||||||
self.queue = deque()
|
self.queue = deque()
|
||||||
self.sync = Event()
|
self.sync = Event()
|
||||||
|
|
||||||
|
self.mii_mode = False
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
self.width = 8
|
self.width = 8
|
||||||
self.byte_width = 1
|
self.byte_width = 1
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert len(self.data) == 8
|
assert len(self.data) == 8
|
||||||
if self.er is not None:
|
if self.er is not None:
|
||||||
assert len(self.er) == 1
|
assert len(self.er) == 1
|
||||||
if self.dv is not None:
|
if self.dv is not None:
|
||||||
assert len(self.dv) == 1
|
assert len(self.dv) == 1
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def recv(self):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def recv(self, compact=True):
|
||||||
|
while self.empty():
|
||||||
|
self.sync.clear()
|
||||||
|
await self.sync.wait()
|
||||||
|
return self.recv_nowait(compact)
|
||||||
|
|
||||||
|
def recv_nowait(self, compact=True):
|
||||||
if self.queue:
|
if self.queue:
|
||||||
frame = self.queue.popleft()
|
frame = self.queue.popleft()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
@@ -292,6 +345,11 @@ class GmiiSink(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.active
|
return not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.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
|
||||||
@@ -301,18 +359,25 @@ class GmiiSink(object):
|
|||||||
else:
|
else:
|
||||||
await self.sync.wait()
|
await self.sync.wait()
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = None
|
|
||||||
self.active = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.enable is None or self.enable.value:
|
if self.enable is None or self.enable.value:
|
||||||
d_val = self.data.value.integer
|
d_val = self.data.value.integer
|
||||||
@@ -323,12 +388,15 @@ class GmiiSink(object):
|
|||||||
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
|
||||||
|
|
||||||
if self.mii_select is not None and self.mii_select.value:
|
if self.mii_select is not None:
|
||||||
|
self.mii_mode = bool(self.mii_select.value.integer)
|
||||||
|
|
||||||
|
if self.mii_mode:
|
||||||
odd = True
|
odd = True
|
||||||
sync = False
|
sync = False
|
||||||
b = 0
|
b = 0
|
||||||
@@ -350,6 +418,7 @@ class GmiiSink(object):
|
|||||||
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)
|
||||||
@@ -361,7 +430,62 @@ class GmiiSink(object):
|
|||||||
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)
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
class GmiiPhy:
|
||||||
|
def __init__(self, txd, tx_er, tx_en, tx_clk, gtx_clk, rxd, rx_er, rx_dv, rx_clk,
|
||||||
|
reset=None, reset_active_level=True, speed=1000e6, *args, **kwargs):
|
||||||
|
|
||||||
|
self.gtx_clk = gtx_clk
|
||||||
|
self.tx_clk = tx_clk
|
||||||
|
self.rx_clk = rx_clk
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
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_active_level=reset_active_level)
|
||||||
|
|
||||||
|
self.rx_clk.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._clock_cr = None
|
||||||
|
self.set_speed(speed)
|
||||||
|
|
||||||
|
def set_speed(self, speed):
|
||||||
|
if speed in (10e6, 100e6, 1000e6):
|
||||||
|
self.speed = speed
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid speed selection")
|
||||||
|
|
||||||
|
if self._clock_cr is not None:
|
||||||
|
self._clock_cr.kill()
|
||||||
|
|
||||||
|
if self.speed == 1000e6:
|
||||||
|
self._clock_cr = cocotb.fork(self._run_clocks(8*1e9/self.speed))
|
||||||
|
self.tx.mii_mode = False
|
||||||
|
self.rx.mii_mode = False
|
||||||
|
self.tx.clock = self.gtx_clk
|
||||||
|
else:
|
||||||
|
self._clock_cr = cocotb.fork(self._run_clocks(4*1e9/self.speed))
|
||||||
|
self.tx.mii_mode = True
|
||||||
|
self.rx.mii_mode = True
|
||||||
|
self.tx.clock = self.tx_clk
|
||||||
|
|
||||||
|
self.tx.assert_reset()
|
||||||
|
self.rx.assert_reset()
|
||||||
|
|
||||||
|
async def _run_clocks(self, period):
|
||||||
|
half_period = get_sim_steps(period / 2.0, 'ns')
|
||||||
|
t = Timer(half_period)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await t
|
||||||
|
self.rx_clk <= 1
|
||||||
|
self.tx_clk <= 1
|
||||||
|
await t
|
||||||
|
self.rx_clk <= 0
|
||||||
|
self.tx_clk <= 0
|
||||||
|
|||||||
368
cocotbext/eth/mii.py
Normal file
368
cocotbext/eth/mii.py
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.triggers import RisingEdge, Timer, First, Event
|
||||||
|
from cocotb.utils import get_sim_time, get_sim_steps
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
from .gmii import GmiiFrame
|
||||||
|
from .constants import EthPre
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
|
class MiiSource(Reset):
|
||||||
|
|
||||||
|
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.data = data
|
||||||
|
self.er = er
|
||||||
|
self.dv = dv
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
self.enable = enable
|
||||||
|
|
||||||
|
self.log.info("MII source")
|
||||||
|
self.log.info("cocotbext-eth version %s", __version__)
|
||||||
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
|
self.log.info("https://github.com/alexforencich/cocotbext-eth")
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
self.queue = deque()
|
||||||
|
|
||||||
|
self.ifg = 12
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
self.width = 4
|
||||||
|
self.byte_width = 1
|
||||||
|
|
||||||
|
assert len(self.data) == 4
|
||||||
|
self.data.setimmediatevalue(0)
|
||||||
|
if self.er is not None:
|
||||||
|
assert len(self.er) == 1
|
||||||
|
self.er.setimmediatevalue(0)
|
||||||
|
assert len(self.dv) == 1
|
||||||
|
self.dv.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def send(self, frame):
|
||||||
|
self.send_nowait(frame)
|
||||||
|
|
||||||
|
def send_nowait(self, frame):
|
||||||
|
frame = GmiiFrame(frame)
|
||||||
|
self.queue_occupancy_bytes += len(frame)
|
||||||
|
self.queue_occupancy_frames += 1
|
||||||
|
self.queue.append(frame)
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return len(self.queue)
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
return not self.queue
|
||||||
|
|
||||||
|
def idle(self):
|
||||||
|
return self.empty() and not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
while not self.idle():
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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.data <= 0
|
||||||
|
if self.er is not None:
|
||||||
|
self.er <= 0
|
||||||
|
self.dv <= 0
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
frame = None
|
||||||
|
ifg_cnt = 0
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
if self.enable is None or self.enable.value:
|
||||||
|
if ifg_cnt > 0:
|
||||||
|
# in IFG
|
||||||
|
ifg_cnt -= 1
|
||||||
|
|
||||||
|
elif frame is None and self.queue:
|
||||||
|
# send frame
|
||||||
|
frame = self.queue.popleft()
|
||||||
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
|
self.queue_occupancy_frames -= 1
|
||||||
|
frame.sim_time_start = get_sim_time()
|
||||||
|
frame.sim_time_sfd = None
|
||||||
|
frame.sim_time_end = None
|
||||||
|
self.log.info("TX frame: %s", frame)
|
||||||
|
frame.normalize()
|
||||||
|
|
||||||
|
mii_data = []
|
||||||
|
mii_error = []
|
||||||
|
for b, e in zip(frame.data, frame.error):
|
||||||
|
mii_data.append(b & 0x0F)
|
||||||
|
mii_data.append(b >> 4)
|
||||||
|
mii_error.append(e)
|
||||||
|
mii_error.append(e)
|
||||||
|
frame.data = mii_data
|
||||||
|
frame.error = mii_error
|
||||||
|
|
||||||
|
self.active = True
|
||||||
|
|
||||||
|
if frame is not None:
|
||||||
|
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:
|
||||||
|
self.er <= frame.error.pop(0)
|
||||||
|
self.dv <= 1
|
||||||
|
|
||||||
|
if not frame.data:
|
||||||
|
ifg_cnt = max(self.ifg, 1)
|
||||||
|
frame.sim_time_end = get_sim_time()
|
||||||
|
frame.handle_tx_complete()
|
||||||
|
frame = None
|
||||||
|
else:
|
||||||
|
self.data <= 0
|
||||||
|
if self.er is not None:
|
||||||
|
self.er <= 0
|
||||||
|
self.dv <= 0
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
|
||||||
|
class MiiSink(Reset):
|
||||||
|
|
||||||
|
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.data = data
|
||||||
|
self.er = er
|
||||||
|
self.dv = dv
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
self.enable = enable
|
||||||
|
|
||||||
|
self.log.info("MII sink")
|
||||||
|
self.log.info("cocotbext-eth version %s", __version__)
|
||||||
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
|
self.log.info("https://github.com/alexforencich/cocotbext-eth")
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
self.queue = deque()
|
||||||
|
self.sync = Event()
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
self.width = 4
|
||||||
|
self.byte_width = 1
|
||||||
|
|
||||||
|
assert len(self.data) == 4
|
||||||
|
if self.er is not None:
|
||||||
|
assert len(self.er) == 1
|
||||||
|
if self.dv is not None:
|
||||||
|
assert len(self.dv) == 1
|
||||||
|
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def recv(self, compact=True):
|
||||||
|
while self.empty():
|
||||||
|
self.sync.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_frames -= 1
|
||||||
|
return frame
|
||||||
|
return None
|
||||||
|
|
||||||
|
def count(self):
|
||||||
|
return len(self.queue)
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
return not self.queue
|
||||||
|
|
||||||
|
def idle(self):
|
||||||
|
return not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
|
async def wait(self, timeout=0, timeout_unit=None):
|
||||||
|
if not self.empty():
|
||||||
|
return
|
||||||
|
self.sync.clear()
|
||||||
|
if timeout:
|
||||||
|
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
||||||
|
else:
|
||||||
|
await self.sync.wait()
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
frame = None
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
if self.enable is None or self.enable.value:
|
||||||
|
d_val = self.data.value.integer
|
||||||
|
dv_val = self.dv.value.integer
|
||||||
|
er_val = 0 if self.er is None else self.er.value.integer
|
||||||
|
|
||||||
|
if frame is None:
|
||||||
|
if dv_val:
|
||||||
|
# start of frame
|
||||||
|
frame = GmiiFrame(bytearray(), [])
|
||||||
|
frame.sim_time_start = get_sim_time()
|
||||||
|
else:
|
||||||
|
if not dv_val:
|
||||||
|
# end of frame
|
||||||
|
odd = True
|
||||||
|
sync = False
|
||||||
|
b = 0
|
||||||
|
be = 0
|
||||||
|
data = bytearray()
|
||||||
|
error = []
|
||||||
|
for n, e in zip(frame.data, frame.error):
|
||||||
|
odd = not odd
|
||||||
|
b = (n & 0x0F) << 4 | b >> 4
|
||||||
|
be |= e
|
||||||
|
if not sync and b == EthPre.SFD:
|
||||||
|
odd = True
|
||||||
|
sync = True
|
||||||
|
if odd:
|
||||||
|
data.append(b)
|
||||||
|
error.append(be)
|
||||||
|
be = 0
|
||||||
|
frame.data = data
|
||||||
|
frame.error = error
|
||||||
|
|
||||||
|
frame.compact()
|
||||||
|
frame.sim_time_end = get_sim_time()
|
||||||
|
self.log.info("RX frame: %s", frame)
|
||||||
|
|
||||||
|
self.queue_occupancy_bytes += len(frame)
|
||||||
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
|
self.queue.append(frame)
|
||||||
|
self.sync.set()
|
||||||
|
|
||||||
|
frame = 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.error.append(er_val)
|
||||||
|
|
||||||
|
|
||||||
|
class MiiPhy:
|
||||||
|
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.rx_clk = rx_clk
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
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, reset_active_level=reset_active_level)
|
||||||
|
|
||||||
|
self.tx_clk.setimmediatevalue(0)
|
||||||
|
self.rx_clk.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._clock_cr = None
|
||||||
|
self.set_speed(speed)
|
||||||
|
|
||||||
|
def set_speed(self, speed):
|
||||||
|
if speed in (10e6, 100e6):
|
||||||
|
self.speed = speed
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid speed selection")
|
||||||
|
|
||||||
|
if self._clock_cr is not None:
|
||||||
|
self._clock_cr.kill()
|
||||||
|
|
||||||
|
self._clock_cr = cocotb.fork(self._run_clocks(4*1e9/self.speed))
|
||||||
|
|
||||||
|
async def _run_clocks(self, period):
|
||||||
|
half_period = get_sim_steps(period / 2.0, 'ns')
|
||||||
|
t = Timer(half_period)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await t
|
||||||
|
self.tx_clk <= 1
|
||||||
|
self.rx_clk <= 1
|
||||||
|
await t
|
||||||
|
self.tx_clk <= 0
|
||||||
|
self.rx_clk <= 0
|
||||||
@@ -27,12 +27,13 @@ import math
|
|||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, ReadOnly
|
from cocotb.triggers import RisingEdge
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class PtpClock(object):
|
class PtpClock(Reset):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -42,6 +43,7 @@ class PtpClock(object):
|
|||||||
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):
|
||||||
|
|
||||||
@@ -87,7 +89,9 @@ class PtpClock(object):
|
|||||||
if self.pps is not None:
|
if self.pps is not None:
|
||||||
self.pps.setimmediatevalue(0)
|
self.pps.setimmediatevalue(0)
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -176,28 +180,34 @@ class PtpClock(object):
|
|||||||
def get_ts_64_s(self):
|
def get_ts_64_s(self):
|
||||||
return self.get_ts_64()*1e-9
|
return self.get_ts_64()*1e-9
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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_ns = 0
|
||||||
|
self.ts_96_fns = 0
|
||||||
|
self.ts_64_ns = 0
|
||||||
|
self.ts_64_fns = 0
|
||||||
|
self.drift_cnt = 0
|
||||||
|
if self.ts_96 is not None:
|
||||||
|
self.ts_96 <= 0
|
||||||
|
if self.ts_64 is not None:
|
||||||
|
self.ts_64 <= 0
|
||||||
|
if self.ts_step is not None:
|
||||||
|
self.ts_step <= 0
|
||||||
|
if self.pps is not None:
|
||||||
|
self.pps <= 0
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
self.ts_96_s = 0
|
|
||||||
self.ts_96_ns = 0
|
|
||||||
self.ts_96_fns = 0
|
|
||||||
self.ts_64_ns = 0
|
|
||||||
self.ts_64_fns = 0
|
|
||||||
self.drift_cnt = 0
|
|
||||||
if self.ts_96 is not None:
|
|
||||||
self.ts_96 <= 0
|
|
||||||
if self.ts_64 is not None:
|
|
||||||
self.ts_64 <= 0
|
|
||||||
if self.ts_step is not None:
|
|
||||||
self.ts_step <= 0
|
|
||||||
if self.pps is not None:
|
|
||||||
self.pps <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.ts_step is not None:
|
if self.ts_step is not None:
|
||||||
|
|||||||
66
cocotbext/eth/reset.py
Normal file
66
cocotbext/eth/reset.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.triggers import RisingEdge, FallingEdge
|
||||||
|
|
||||||
|
|
||||||
|
class Reset:
|
||||||
|
def _init_reset(self, reset_signal=None, active_level=True):
|
||||||
|
self._local_reset = False
|
||||||
|
self._ext_reset = False
|
||||||
|
self._reset_state = True
|
||||||
|
|
||||||
|
if reset_signal is not None:
|
||||||
|
cocotb.fork(self._run_reset(reset_signal, bool(active_level)))
|
||||||
|
|
||||||
|
self._update_reset()
|
||||||
|
|
||||||
|
def assert_reset(self, val=None):
|
||||||
|
if val is None:
|
||||||
|
self.assert_reset(True)
|
||||||
|
self.assert_reset(False)
|
||||||
|
else:
|
||||||
|
self._local_reset = bool(val)
|
||||||
|
self._update_reset()
|
||||||
|
|
||||||
|
def _update_reset(self):
|
||||||
|
new_state = self._local_reset or self._ext_reset
|
||||||
|
if self._reset_state != new_state:
|
||||||
|
self._reset_state = new_state
|
||||||
|
self._handle_reset(new_state)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _run_reset(self, reset_signal, active_level):
|
||||||
|
while True:
|
||||||
|
if bool(reset_signal.value):
|
||||||
|
await FallingEdge(reset_signal)
|
||||||
|
self._ext_reset = not active_level
|
||||||
|
self._update_reset()
|
||||||
|
else:
|
||||||
|
await RisingEdge(reset_signal)
|
||||||
|
self._ext_reset = active_level
|
||||||
|
self._update_reset()
|
||||||
@@ -26,17 +26,20 @@ import logging
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, FallingEdge, ReadOnly, Timer, First, Event
|
from cocotb.triggers import RisingEdge, FallingEdge, Timer, First, Event
|
||||||
from cocotb.utils import get_sim_time
|
from cocotb.utils import get_sim_time, get_sim_steps
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .gmii import GmiiFrame
|
from .gmii import GmiiFrame
|
||||||
from .constants import EthPre
|
from .constants import EthPre
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class RgmiiSource(object):
|
class RgmiiSource(Reset):
|
||||||
|
|
||||||
|
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None,
|
||||||
|
reset_active_level=True, *args, **kwargs):
|
||||||
|
|
||||||
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None, *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
|
||||||
@@ -56,6 +59,7 @@ class RgmiiSource(object):
|
|||||||
self.queue = deque()
|
self.queue = deque()
|
||||||
|
|
||||||
self.ifg = 12
|
self.ifg = 12
|
||||||
|
self.mii_mode = False
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
@@ -63,16 +67,19 @@ class RgmiiSource(object):
|
|||||||
self.width = 8
|
self.width = 8
|
||||||
self.byte_width = 1
|
self.byte_width = 1
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert len(self.data) == 4
|
assert len(self.data) == 4
|
||||||
self.data.setimmediatevalue(0)
|
self.data.setimmediatevalue(0)
|
||||||
assert len(self.ctrl) == 1
|
assert len(self.ctrl) == 1
|
||||||
self.ctrl.setimmediatevalue(0)
|
self.ctrl.setimmediatevalue(0)
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def send(self, frame):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def send(self, frame):
|
||||||
|
self.send_nowait(frame)
|
||||||
|
|
||||||
|
def send_nowait(self, frame):
|
||||||
frame = GmiiFrame(frame)
|
frame = GmiiFrame(frame)
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
@@ -87,10 +94,30 @@ class RgmiiSource(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return self.empty() and not self.active
|
return self.empty() and not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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.data <= 0
|
||||||
|
self.ctrl <= 0
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
ifg_cnt = 0
|
ifg_cnt = 0
|
||||||
@@ -100,23 +127,11 @@ class RgmiiSource(object):
|
|||||||
en = 0
|
en = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = None
|
|
||||||
ifg_cnt = 0
|
|
||||||
self.active = False
|
|
||||||
self.data <= 0
|
|
||||||
self.ctrl <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.mii_select is None or not self.mii_select.value:
|
# 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
|
|
||||||
|
|
||||||
if self.enable is None or self.enable.value:
|
if self.enable is None or self.enable.value:
|
||||||
if ifg_cnt > 0:
|
if ifg_cnt > 0:
|
||||||
@@ -128,15 +143,21 @@ class RgmiiSource(object):
|
|||||||
frame = self.queue.popleft()
|
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
|
||||||
|
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()
|
||||||
|
|
||||||
if self.mii_select is not None and self.mii_select.value:
|
if self.mii_select is not None:
|
||||||
|
self.mii_mode = bool(self.mii_select.value.integer)
|
||||||
|
|
||||||
|
if self.mii_mode:
|
||||||
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
|
||||||
@@ -149,8 +170,13 @@ class RgmiiSource(object):
|
|||||||
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
|
||||||
else:
|
else:
|
||||||
d = 0
|
d = 0
|
||||||
@@ -165,9 +191,11 @@ class RgmiiSource(object):
|
|||||||
self.ctrl <= en
|
self.ctrl <= en
|
||||||
|
|
||||||
|
|
||||||
class RgmiiSink(object):
|
class RgmiiSink(Reset):
|
||||||
|
|
||||||
|
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None,
|
||||||
|
reset_active_level=True, *args, **kwargs):
|
||||||
|
|
||||||
def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None, *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
|
||||||
@@ -187,20 +215,28 @@ class RgmiiSink(object):
|
|||||||
self.queue = deque()
|
self.queue = deque()
|
||||||
self.sync = Event()
|
self.sync = Event()
|
||||||
|
|
||||||
|
self.mii_mode = False
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
self.width = 8
|
self.width = 8
|
||||||
self.byte_width = 1
|
self.byte_width = 1
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert len(self.data) == 4
|
assert len(self.data) == 4
|
||||||
assert len(self.ctrl) == 1
|
assert len(self.ctrl) == 1
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def recv(self):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def recv(self, compact=True):
|
||||||
|
while self.empty():
|
||||||
|
self.sync.clear()
|
||||||
|
await self.sync.wait()
|
||||||
|
return self.recv_nowait(compact)
|
||||||
|
|
||||||
|
def recv_nowait(self, compact=True):
|
||||||
if self.queue:
|
if self.queue:
|
||||||
frame = self.queue.popleft()
|
frame = self.queue.popleft()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
@@ -217,6 +253,11 @@ class RgmiiSink(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.active
|
return not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.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
|
||||||
@@ -226,6 +267,19 @@ class RgmiiSink(object):
|
|||||||
else:
|
else:
|
||||||
await self.sync.wait()
|
await self.sync.wait()
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
@@ -234,15 +288,15 @@ class RgmiiSink(object):
|
|||||||
er_val = 0
|
er_val = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
# capture low nibble on rising edge
|
||||||
await RisingEdge(self.clock)
|
d_val = self.data.value.integer
|
||||||
frame = None
|
dv_val = self.ctrl.value.integer
|
||||||
self.active = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
# capture high nibble after rising edge, leading in to falling edge
|
await FallingEdge(self.clock)
|
||||||
|
|
||||||
|
# capture high nibble on falling edge
|
||||||
d_val |= self.data.value.integer << 4
|
d_val |= self.data.value.integer << 4
|
||||||
er_val = dv_val ^ self.ctrl.value.integer
|
er_val = dv_val ^ self.ctrl.value.integer
|
||||||
|
|
||||||
@@ -252,12 +306,15 @@ class RgmiiSink(object):
|
|||||||
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
|
||||||
|
|
||||||
if self.mii_select is not None and self.mii_select.value:
|
if self.mii_select is not None:
|
||||||
|
self.mii_mode = bool(self.mii_select.value.integer)
|
||||||
|
|
||||||
|
if self.mii_mode:
|
||||||
odd = True
|
odd = True
|
||||||
sync = False
|
sync = False
|
||||||
b = 0
|
b = 0
|
||||||
@@ -279,6 +336,7 @@ class RgmiiSink(object):
|
|||||||
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)
|
||||||
@@ -290,14 +348,54 @@ class RgmiiSink(object):
|
|||||||
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)
|
||||||
|
|
||||||
await FallingEdge(self.clock)
|
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
# capture low nibble after falling edge, leading in to rising edge
|
class RgmiiPhy:
|
||||||
d_val = self.data.value.integer
|
def __init__(self, txd, tx_ctl, tx_clk, rxd, rx_ctl, rx_clk, reset=None,
|
||||||
dv_val = self.ctrl.value.integer
|
reset_active_level=True, speed=1000e6, *args, **kwargs):
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
self.tx_clk = tx_clk
|
||||||
|
self.rx_clk = rx_clk
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.tx = RgmiiSink(txd, tx_ctl, tx_clk, reset, reset_active_level=reset_active_level)
|
||||||
|
self.rx = RgmiiSource(rxd, rx_ctl, rx_clk, reset, reset_active_level=reset_active_level)
|
||||||
|
|
||||||
|
self.rx_clk.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._clock_cr = None
|
||||||
|
self.set_speed(speed)
|
||||||
|
|
||||||
|
def set_speed(self, speed):
|
||||||
|
if speed in (10e6, 100e6, 1000e6):
|
||||||
|
self.speed = speed
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid speed selection")
|
||||||
|
|
||||||
|
if self._clock_cr is not None:
|
||||||
|
self._clock_cr.kill()
|
||||||
|
|
||||||
|
if self.speed == 1000e6:
|
||||||
|
self._clock_cr = cocotb.fork(self._run_clock(8*1e9/self.speed))
|
||||||
|
self.tx.mii_mode = False
|
||||||
|
self.rx.mii_mode = False
|
||||||
|
else:
|
||||||
|
self._clock_cr = cocotb.fork(self._run_clock(4*1e9/self.speed))
|
||||||
|
self.tx.mii_mode = True
|
||||||
|
self.rx.mii_mode = True
|
||||||
|
|
||||||
|
async def _run_clock(self, period):
|
||||||
|
half_period = get_sim_steps(period / 2.0, 'ns')
|
||||||
|
t = Timer(half_period)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await t
|
||||||
|
self.rx_clk <= 1
|
||||||
|
await t
|
||||||
|
self.rx_clk <= 0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.0"
|
__version__ = "0.1.6"
|
||||||
|
|||||||
@@ -28,42 +28,52 @@ import zlib
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, ReadOnly, 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
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import EthPre, ETH_PREAMBLE, XgmiiCtrl
|
from .constants import EthPre, ETH_PREAMBLE, XgmiiCtrl
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class XgmiiFrame(object):
|
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
|
||||||
@@ -95,16 +105,24 @@ class XgmiiFrame(object):
|
|||||||
if not any(self.ctrl):
|
if 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
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
f"{type(self).__name__}(data={repr(self.data)}, "
|
f"{type(self).__name__}(data={self.data!r}, "
|
||||||
f"ctrl={repr(self.ctrl)}, "
|
f"ctrl={self.ctrl!r}, "
|
||||||
f"rx_sim_time={repr(self.rx_sim_time)}, "
|
f"sim_time_start={self.sim_time_start!r}, "
|
||||||
f"rx_start_lane={repr(self.rx_start_lane)})"
|
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):
|
||||||
@@ -113,10 +131,13 @@ class XgmiiFrame(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.data.__iter__()
|
return self.data.__iter__()
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return bytes(self.data)
|
||||||
|
|
||||||
class XgmiiSource(object):
|
|
||||||
|
|
||||||
def __init__(self, data, ctrl, clock, reset=None, enable=None, *args, **kwargs):
|
class XgmiiSource(Reset):
|
||||||
|
|
||||||
|
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
|
||||||
@@ -144,8 +165,6 @@ class XgmiiSource(object):
|
|||||||
self.width = len(self.data)
|
self.width = len(self.data)
|
||||||
self.byte_width = len(self.ctrl)
|
self.byte_width = len(self.ctrl)
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert self.width == self.byte_width * 8
|
assert self.width == self.byte_width * 8
|
||||||
|
|
||||||
self.idle_d = 0
|
self.idle_d = 0
|
||||||
@@ -158,9 +177,14 @@ class XgmiiSource(object):
|
|||||||
self.data.setimmediatevalue(0)
|
self.data.setimmediatevalue(0)
|
||||||
self.ctrl.setimmediatevalue(0)
|
self.ctrl.setimmediatevalue(0)
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def send(self, frame):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def send(self, frame):
|
||||||
|
self.send_nowait(frame)
|
||||||
|
|
||||||
|
def send_nowait(self, frame):
|
||||||
frame = XgmiiFrame(frame)
|
frame = XgmiiFrame(frame)
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
@@ -175,10 +199,30 @@ class XgmiiSource(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return self.empty() and not self.active
|
return self.empty() and not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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.data <= 0
|
||||||
|
self.ctrl <= 0
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
ifg_cnt = 0
|
ifg_cnt = 0
|
||||||
@@ -186,18 +230,6 @@ class XgmiiSource(object):
|
|||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = None
|
|
||||||
ifg_cnt = 0
|
|
||||||
deficit_idle_cnt = 0
|
|
||||||
self.active = False
|
|
||||||
self.data <= 0
|
|
||||||
self.ctrl <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.enable is None or self.enable.value:
|
if self.enable is None or self.enable.value:
|
||||||
@@ -216,8 +248,12 @@ class XgmiiSource(object):
|
|||||||
frame = self.queue.popleft()
|
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
|
||||||
|
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
|
||||||
@@ -233,6 +269,7 @@ class XgmiiSource(object):
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -251,11 +288,16 @@ class XgmiiSource(object):
|
|||||||
|
|
||||||
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
|
||||||
else:
|
else:
|
||||||
d_val |= XgmiiCtrl.IDLE << k*8
|
d_val |= XgmiiCtrl.IDLE << k*8
|
||||||
@@ -269,9 +311,9 @@ class XgmiiSource(object):
|
|||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
|
||||||
class XgmiiSink(object):
|
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
|
||||||
@@ -296,13 +338,19 @@ class XgmiiSink(object):
|
|||||||
self.width = len(self.data)
|
self.width = len(self.data)
|
||||||
self.byte_width = len(self.ctrl)
|
self.byte_width = len(self.ctrl)
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
assert self.width == self.byte_width * 8
|
assert self.width == self.byte_width * 8
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
self._run_cr = None
|
||||||
|
|
||||||
def recv(self):
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def recv(self, compact=True):
|
||||||
|
while self.empty():
|
||||||
|
self.sync.clear()
|
||||||
|
await self.sync.wait()
|
||||||
|
return self.recv_nowait(compact)
|
||||||
|
|
||||||
|
def recv_nowait(self, compact=True):
|
||||||
if self.queue:
|
if self.queue:
|
||||||
frame = self.queue.popleft()
|
frame = self.queue.popleft()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
@@ -319,6 +367,11 @@ class XgmiiSink(object):
|
|||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.active
|
return not self.active
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.queue.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
|
||||||
@@ -328,18 +381,25 @@ class XgmiiSink(object):
|
|||||||
else:
|
else:
|
||||||
await self.sync.wait()
|
await self.sync.wait()
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
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
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = None
|
|
||||||
self.active = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.enable is None or self.enable.value:
|
if self.enable is None or self.enable.value:
|
||||||
for offset in range(self.byte_width):
|
for offset in range(self.byte_width):
|
||||||
@@ -350,8 +410,8 @@ class XgmiiSink(object):
|
|||||||
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
|
||||||
@@ -361,6 +421,7 @@ class XgmiiSink(object):
|
|||||||
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)
|
||||||
@@ -371,7 +432,8 @@ class XgmiiSink(object):
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from cocotb.regression import TestFactory
|
|||||||
from cocotbext.eth import GmiiFrame, GmiiSource, GmiiSink
|
from cocotbext.eth import GmiiFrame, GmiiSource, GmiiSink
|
||||||
|
|
||||||
|
|
||||||
class TB(object):
|
class TB:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -103,11 +103,10 @@ async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_
|
|||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
test_frame = GmiiFrame.from_payload(test_data)
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
tb.source.send(test_frame)
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
await tb.sink.wait()
|
rx_frame = await tb.sink.recv()
|
||||||
rx_frame = tb.sink.recv()
|
|
||||||
|
|
||||||
assert rx_frame.get_payload() == test_data
|
assert rx_frame.get_payload() == test_data
|
||||||
assert rx_frame.check_fcs()
|
assert rx_frame.check_fcs()
|
||||||
@@ -145,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):
|
||||||
@@ -161,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],
|
||||||
|
|||||||
61
tests/gmii_phy/Makefile
Normal file
61
tests/gmii_phy/Makefile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_gmii_phy
|
||||||
|
TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(DUT)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
|
COMPILE_ARGS += -s iverilog_dump
|
||||||
|
endif
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
COMPILE_ARGS += --trace-fst
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
|
iverilog_dump.v:
|
||||||
|
echo 'module iverilog_dump();' > $@
|
||||||
|
echo 'initial begin' >> $@
|
||||||
|
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||||
|
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||||
|
echo 'end' >> $@
|
||||||
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
|
clean::
|
||||||
|
@rm -rf iverilog_dump.v
|
||||||
|
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||||
183
tests/gmii_phy/test_gmii_phy.py
Normal file
183
tests/gmii_phy/test_gmii_phy.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, GmiiSource, GmiiSink, GmiiPhy
|
||||||
|
|
||||||
|
|
||||||
|
class TB:
|
||||||
|
def __init__(self, dut, speed=1000e6):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
cocotb.fork(Clock(dut.phy_gtx_clk, 8, units="ns").start())
|
||||||
|
|
||||||
|
self.gmii_phy = GmiiPhy(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk, dut.phy_gtx_clk,
|
||||||
|
dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst, speed=speed)
|
||||||
|
|
||||||
|
if speed == 1000e6:
|
||||||
|
self.source = GmiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_gtx_clk, dut.phy_rst)
|
||||||
|
self.source.mii_mode = False
|
||||||
|
self.sink = GmiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst)
|
||||||
|
self.sink.mii_mode = False
|
||||||
|
else:
|
||||||
|
self.source = GmiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk, dut.phy_rst)
|
||||||
|
self.source.mii_mode = True
|
||||||
|
self.sink = GmiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst)
|
||||||
|
self.sink.mii_mode = True
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.dut.phy_rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 1
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 0
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.gmii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.gmii_phy.tx.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.gmii_phy.tx.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.gmii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.gmii_phy.rx.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
def size_list():
|
||||||
|
return list(range(60, 128)) + [512, 1514] + [60]*10
|
||||||
|
|
||||||
|
|
||||||
|
def incrementing_payload(length):
|
||||||
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_en():
|
||||||
|
return itertools.cycle([0, 0, 0, 1])
|
||||||
|
|
||||||
|
|
||||||
|
if cocotb.SIM_NAME:
|
||||||
|
|
||||||
|
for test in [run_test_tx, run_test_rx]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("speed", [1000e6, 100e6, 10e6])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
# cocotb-test
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_gmii_phy(request):
|
||||||
|
dut = "test_gmii_phy"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(tests_dir, f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
46
tests/gmii_phy/test_gmii_phy.v
Normal file
46
tests/gmii_phy/test_gmii_phy.v
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GMII PHY test
|
||||||
|
*/
|
||||||
|
module test_gmii_phy
|
||||||
|
(
|
||||||
|
inout wire phy_rst,
|
||||||
|
inout wire [7:0] phy_txd,
|
||||||
|
inout wire phy_tx_er,
|
||||||
|
inout wire phy_tx_en,
|
||||||
|
inout wire phy_tx_clk,
|
||||||
|
inout wire phy_gtx_clk,
|
||||||
|
inout wire [7:0] phy_rxd,
|
||||||
|
inout wire phy_rx_er,
|
||||||
|
inout wire phy_rx_dv,
|
||||||
|
inout wire phy_rx_clk
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
61
tests/mii/Makefile
Normal file
61
tests/mii/Makefile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_mii
|
||||||
|
TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(DUT)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
|
COMPILE_ARGS += -s iverilog_dump
|
||||||
|
endif
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
COMPILE_ARGS += --trace-fst
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
|
iverilog_dump.v:
|
||||||
|
echo 'module iverilog_dump();' > $@
|
||||||
|
echo 'initial begin' >> $@
|
||||||
|
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||||
|
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||||
|
echo 'end' >> $@
|
||||||
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
|
clean::
|
||||||
|
@rm -rf iverilog_dump.v
|
||||||
|
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||||
170
tests/mii/test_mii.py
Normal file
170
tests/mii/test_mii.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, MiiSource, MiiSink
|
||||||
|
|
||||||
|
|
||||||
|
class TB:
|
||||||
|
def __init__(self, dut):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
self._enable_generator = None
|
||||||
|
self._enable_cr = None
|
||||||
|
|
||||||
|
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
|
self.source = MiiSource(dut.mii_d, dut.mii_er, dut.mii_en,
|
||||||
|
dut.clk, dut.rst, dut.mii_clk_en)
|
||||||
|
self.sink = MiiSink(dut.mii_d, dut.mii_er, dut.mii_en,
|
||||||
|
dut.clk, dut.rst, dut.mii_clk_en)
|
||||||
|
|
||||||
|
dut.mii_clk_en.setimmediatevalue(1)
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.dut.rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst <= 1
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst <= 0
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
|
||||||
|
def set_enable_generator(self, generator=None):
|
||||||
|
if self._enable_cr is not None:
|
||||||
|
self._enable_cr.kill()
|
||||||
|
self._enable_cr = None
|
||||||
|
|
||||||
|
self._enable_generator = generator
|
||||||
|
|
||||||
|
if self._enable_generator is not None:
|
||||||
|
self._enable_cr = cocotb.fork(self._run_enable())
|
||||||
|
|
||||||
|
def clear_enable_generator(self):
|
||||||
|
self.set_enable_generator(None)
|
||||||
|
|
||||||
|
async def _run_enable(self):
|
||||||
|
for val in self._enable_generator:
|
||||||
|
self.dut.mii_clk_en <= val
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
if enable_gen is not None:
|
||||||
|
tb.set_enable_generator(enable_gen())
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
def size_list():
|
||||||
|
return list(range(60, 128)) + [512, 1514, 9214] + [60]*10
|
||||||
|
|
||||||
|
|
||||||
|
def incrementing_payload(length):
|
||||||
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_en():
|
||||||
|
return itertools.cycle([0, 0, 0, 1])
|
||||||
|
|
||||||
|
|
||||||
|
if cocotb.SIM_NAME:
|
||||||
|
|
||||||
|
factory = TestFactory(run_test)
|
||||||
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("ifg", [12, 0])
|
||||||
|
factory.add_option("enable_gen", [None, cycle_en])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
# cocotb-test
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mii(request):
|
||||||
|
dut = "test_mii"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(tests_dir, f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
46
tests/mii/test_mii.v
Normal file
46
tests/mii/test_mii.v
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MII test
|
||||||
|
*/
|
||||||
|
module test_mii #
|
||||||
|
(
|
||||||
|
parameter DATA_WIDTH = 4
|
||||||
|
)
|
||||||
|
(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
inout wire [DATA_WIDTH-1:0] mii_d,
|
||||||
|
inout wire mii_er,
|
||||||
|
inout wire mii_en,
|
||||||
|
inout wire mii_clk_en
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
61
tests/mii_phy/Makefile
Normal file
61
tests/mii_phy/Makefile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_mii_phy
|
||||||
|
TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(DUT)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
|
COMPILE_ARGS += -s iverilog_dump
|
||||||
|
endif
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
COMPILE_ARGS += --trace-fst
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
|
iverilog_dump.v:
|
||||||
|
echo 'module iverilog_dump();' > $@
|
||||||
|
echo 'initial begin' >> $@
|
||||||
|
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||||
|
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||||
|
echo 'end' >> $@
|
||||||
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
|
clean::
|
||||||
|
@rm -rf iverilog_dump.v
|
||||||
|
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||||
174
tests/mii_phy/test_mii_phy.py
Normal file
174
tests/mii_phy/test_mii_phy.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, MiiSource, MiiSink, MiiPhy
|
||||||
|
|
||||||
|
|
||||||
|
class TB:
|
||||||
|
def __init__(self, dut, speed=100e6):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
self.mii_phy = MiiPhy(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en, dut.phy_tx_clk,
|
||||||
|
dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst, speed=speed)
|
||||||
|
|
||||||
|
self.source = MiiSource(dut.phy_txd, dut.phy_tx_er, dut.phy_tx_en,
|
||||||
|
dut.phy_tx_clk, dut.phy_rst)
|
||||||
|
self.sink = MiiSink(dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv,
|
||||||
|
dut.phy_rx_clk, dut.phy_rst)
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.dut.phy_rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 1
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 0
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.mii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.mii_phy.tx.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.mii_phy.tx.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=100e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.mii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.mii_phy.rx.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
def size_list():
|
||||||
|
return list(range(60, 128)) + [512, 1514] + [60]*10
|
||||||
|
|
||||||
|
|
||||||
|
def incrementing_payload(length):
|
||||||
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_en():
|
||||||
|
return itertools.cycle([0, 0, 0, 1])
|
||||||
|
|
||||||
|
|
||||||
|
if cocotb.SIM_NAME:
|
||||||
|
|
||||||
|
for test in [run_test_tx, run_test_rx]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("speed", [100e6, 10e6])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
# cocotb-test
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mii_phy(request):
|
||||||
|
dut = "test_mii_phy"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(tests_dir, f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
45
tests/mii_phy/test_mii_phy.v
Normal file
45
tests/mii_phy/test_mii_phy.v
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MII PHY test
|
||||||
|
*/
|
||||||
|
module test_mii_phy
|
||||||
|
(
|
||||||
|
inout wire phy_rst,
|
||||||
|
inout wire [3:0] phy_txd,
|
||||||
|
inout wire phy_tx_er,
|
||||||
|
inout wire phy_tx_en,
|
||||||
|
inout wire phy_tx_clk,
|
||||||
|
inout wire [3:0] phy_rxd,
|
||||||
|
inout wire phy_rx_er,
|
||||||
|
inout wire phy_rx_dv,
|
||||||
|
inout wire phy_rx_clk
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from cocotb.utils import get_sim_time
|
|||||||
from cocotbext.eth import PtpClock
|
from cocotbext.eth import PtpClock
|
||||||
|
|
||||||
|
|
||||||
class TB(object):
|
class TB:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -164,6 +164,8 @@ async def run_seconds_increment(dut):
|
|||||||
tb.ptp_clock.set_ts_64(999990000*2**16)
|
tb.ptp_clock.set_ts_64(999990000*2**16)
|
||||||
|
|
||||||
await RisingEdge(dut.clk)
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
start_time = get_sim_time('sec')
|
start_time = get_sim_time('sec')
|
||||||
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9)
|
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9)
|
||||||
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9
|
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9
|
||||||
@@ -292,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):
|
||||||
@@ -308,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],
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from cocotb.regression import TestFactory
|
|||||||
from cocotbext.eth import GmiiFrame, RgmiiSource, RgmiiSink
|
from cocotbext.eth import GmiiFrame, RgmiiSource, RgmiiSink
|
||||||
|
|
||||||
|
|
||||||
class TB(object):
|
class TB:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -101,11 +101,10 @@ async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_
|
|||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
test_frame = GmiiFrame.from_payload(test_data)
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
tb.source.send(test_frame)
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
await tb.sink.wait()
|
rx_frame = await tb.sink.recv()
|
||||||
rx_frame = tb.sink.recv()
|
|
||||||
|
|
||||||
assert rx_frame.get_payload() == test_data
|
assert rx_frame.get_payload() == test_data
|
||||||
assert rx_frame.check_fcs()
|
assert rx_frame.check_fcs()
|
||||||
@@ -142,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):
|
||||||
@@ -158,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],
|
||||||
|
|||||||
61
tests/rgmii_phy/Makefile
Normal file
61
tests/rgmii_phy/Makefile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Copyright (c) 2020 Alex Forencich
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_rgmii_phy
|
||||||
|
TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(DUT)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
VERILOG_SOURCES += iverilog_dump.v
|
||||||
|
COMPILE_ARGS += -s iverilog_dump
|
||||||
|
endif
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||||
|
|
||||||
|
ifeq ($(WAVES), 1)
|
||||||
|
COMPILE_ARGS += --trace-fst
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
|
iverilog_dump.v:
|
||||||
|
echo 'module iverilog_dump();' > $@
|
||||||
|
echo 'initial begin' >> $@
|
||||||
|
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
||||||
|
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
||||||
|
echo 'end' >> $@
|
||||||
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
|
clean::
|
||||||
|
@rm -rf iverilog_dump.v
|
||||||
|
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||||
187
tests/rgmii_phy/test_rgmii_phy.py
Normal file
187
tests/rgmii_phy/test_rgmii_phy.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.eth import GmiiFrame, RgmiiSource, RgmiiSink, RgmiiPhy
|
||||||
|
|
||||||
|
|
||||||
|
class TB:
|
||||||
|
def __init__(self, dut, speed=1000e6):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if speed == 1000e6:
|
||||||
|
cocotb.fork(Clock(dut.phy_tx_clk, 8, units="ns").start())
|
||||||
|
elif speed == 100e6:
|
||||||
|
cocotb.fork(Clock(dut.phy_tx_clk, 40, units="ns").start())
|
||||||
|
elif speed == 10e6:
|
||||||
|
cocotb.fork(Clock(dut.phy_tx_clk, 400, units="ns").start())
|
||||||
|
|
||||||
|
self.rgmii_phy = RgmiiPhy(dut.phy_txd, dut.phy_tx_ctl, dut.phy_tx_clk,
|
||||||
|
dut.phy_rxd, dut.phy_rx_ctl, dut.phy_rx_clk, dut.phy_rst, speed=speed)
|
||||||
|
|
||||||
|
self.source = RgmiiSource(dut.phy_txd, dut.phy_tx_ctl, dut.phy_tx_clk, dut.phy_rst)
|
||||||
|
self.sink = RgmiiSink(dut.phy_rxd, dut.phy_rx_ctl, dut.phy_rx_clk, dut.phy_rst)
|
||||||
|
|
||||||
|
if speed == 1000e6:
|
||||||
|
self.source.mii_mode = False
|
||||||
|
self.sink.mii_mode = False
|
||||||
|
else:
|
||||||
|
self.source.mii_mode = True
|
||||||
|
self.sink.mii_mode = True
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
self.dut.phy_rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 1
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
self.dut.phy_rst <= 0
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
await RisingEdge(self.dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_tx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.rgmii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.rgmii_phy.tx.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.rgmii_phy.tx.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
await RisingEdge(dut.phy_tx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_rx(dut, payload_lengths=None, payload_data=None, ifg=12, speed=1000e6):
|
||||||
|
|
||||||
|
tb = TB(dut, speed)
|
||||||
|
|
||||||
|
tb.rgmii_phy.rx.ifg = ifg
|
||||||
|
tb.source.ifg = ifg
|
||||||
|
|
||||||
|
await tb.reset()
|
||||||
|
|
||||||
|
test_frames = [payload_data(x) for x in payload_lengths()]
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
test_frame = GmiiFrame.from_payload(test_data)
|
||||||
|
await tb.rgmii_phy.rx.send(test_frame)
|
||||||
|
|
||||||
|
for test_data in test_frames:
|
||||||
|
rx_frame = await tb.sink.recv()
|
||||||
|
|
||||||
|
assert rx_frame.get_payload() == test_data
|
||||||
|
assert rx_frame.check_fcs()
|
||||||
|
assert rx_frame.error is None
|
||||||
|
|
||||||
|
assert tb.sink.empty()
|
||||||
|
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
await RisingEdge(dut.phy_rx_clk)
|
||||||
|
|
||||||
|
|
||||||
|
def size_list():
|
||||||
|
return list(range(60, 128)) + [512, 1514] + [60]*10
|
||||||
|
|
||||||
|
|
||||||
|
def incrementing_payload(length):
|
||||||
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_en():
|
||||||
|
return itertools.cycle([0, 0, 0, 1])
|
||||||
|
|
||||||
|
|
||||||
|
if cocotb.SIM_NAME:
|
||||||
|
|
||||||
|
for test in [run_test_tx, run_test_rx]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
factory.add_option("payload_data", [incrementing_payload])
|
||||||
|
factory.add_option("speed", [1000e6, 100e6, 10e6])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
# cocotb-test
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgmii_phy(request):
|
||||||
|
dut = "test_rgmii_phy"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(tests_dir, f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
43
tests/rgmii_phy/test_rgmii_phy.v
Normal file
43
tests/rgmii_phy/test_rgmii_phy.v
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2020 Alex Forencich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RGMII PHY test
|
||||||
|
*/
|
||||||
|
module test_rgmii_phy
|
||||||
|
(
|
||||||
|
inout wire phy_rst,
|
||||||
|
inout wire [3:0] phy_txd,
|
||||||
|
inout wire phy_tx_ctl,
|
||||||
|
inout wire phy_tx_clk,
|
||||||
|
inout wire [3:0] phy_rxd,
|
||||||
|
inout wire phy_rx_ctl,
|
||||||
|
inout wire phy_rx_clk
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from cocotb.regression import TestFactory
|
|||||||
from cocotbext.eth import XgmiiFrame, XgmiiSource, XgmiiSink
|
from cocotbext.eth import XgmiiFrame, XgmiiSource, XgmiiSink
|
||||||
|
|
||||||
|
|
||||||
class TB(object):
|
class TB:
|
||||||
def __init__(self, dut):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -103,11 +103,10 @@ async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_
|
|||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
test_frame = XgmiiFrame.from_payload(test_data)
|
test_frame = XgmiiFrame.from_payload(test_data)
|
||||||
tb.source.send(test_frame)
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
await tb.sink.wait()
|
rx_frame = await tb.sink.recv()
|
||||||
rx_frame = tb.sink.recv()
|
|
||||||
|
|
||||||
assert rx_frame.get_payload() == test_data
|
assert rx_frame.get_payload() == test_data
|
||||||
assert rx_frame.check_fcs()
|
assert rx_frame.check_fcs()
|
||||||
@@ -142,17 +141,16 @@ async def run_test_alignment(dut, payload_data=None, ifg=12, enable_dic=True,
|
|||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
test_frame = XgmiiFrame.from_payload(test_data)
|
test_frame = XgmiiFrame.from_payload(test_data)
|
||||||
tb.source.send(test_frame)
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
for test_data in test_frames:
|
for test_data in test_frames:
|
||||||
await tb.sink.wait()
|
rx_frame = await tb.sink.recv()
|
||||||
rx_frame = tb.sink.recv()
|
|
||||||
|
|
||||||
assert rx_frame.get_payload() == test_data
|
assert rx_frame.get_payload() == test_data
|
||||||
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)
|
||||||
@@ -231,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])
|
||||||
@@ -251,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],
|
||||||
|
|||||||
Reference in New Issue
Block a user