58 Commits

Author SHA1 Message Date
c2b5934cc9 Cocotb 2.0 changes
Some checks failed
build / Build distributions (push) Successful in 8s
build / deploy (push) Has been skipped
build / Build distributions (release) Successful in 8s
build / deploy (release) Successful in 7s
Regression Tests / Python 3.10 (push) Has been cancelled
Regression Tests / Python 3.11 (push) Has been cancelled
Regression Tests / Python 3.12 (push) Has been cancelled
Regression Tests / Python 3.13 (push) Has been cancelled
Regression Tests / Python 3.8 (push) Has been cancelled
Regression Tests / Python 3.9 (push) Has been cancelled
value_change, kill -> cancel
2026-02-08 13:48:23 -08:00
bebafc3f73 add auto deploy
Some checks failed
build / Build distributions (push) Successful in 8s
build / deploy (push) Has been skipped
build / Build distributions (release) Successful in 8s
build / deploy (release) Successful in 7s
Regression Tests / Python 3.10 (push) Has been cancelled
Regression Tests / Python 3.11 (push) Has been cancelled
Regression Tests / Python 3.12 (push) Has been cancelled
Regression Tests / Python 3.13 (push) Has been cancelled
Regression Tests / Python 3.8 (push) Has been cancelled
Regression Tests / Python 3.9 (push) Has been cancelled
2026-02-08 13:24:19 -08:00
Alex Forencich
c6872e6951 Update copyright dates
Some checks failed
Regression Tests / Python 3.10 (push) Has been cancelled
Regression Tests / Python 3.11 (push) Has been cancelled
Regression Tests / Python 3.12 (push) Has been cancelled
Regression Tests / Python 3.13 (push) Has been cancelled
Regression Tests / Python 3.8 (push) Has been cancelled
Regression Tests / Python 3.9 (push) Has been cancelled
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-07 16:36:42 -07:00
Alex Forencich
e9232d6bc9 Update CI packages
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-07 15:45:55 -07:00
Alex Forencich
71be7f50d5 Rework reset logic to better handle X/Z
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-07 13:21:40 -07:00
Alex Forencich
7077200912 Testbench cleanup for cocotb 2.0
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 22:54:37 -07:00
Alex Forencich
2e356c3fc5 Cast to int instead of using .integer
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 22:43:04 -07:00
Alex Forencich
c25e2e6b23 Explicitly check for None
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 22:42:08 -07:00
Alex Forencich
bdebdd4897 Update readme
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 18:28:37 -07:00
Alex Forencich
3f7a0ad919 Update setup.cfg
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 18:28:29 -07:00
Alex Forencich
b0c3671ca0 Update setup.cfg
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 18:23:40 -07:00
Alex Forencich
73df05c706 Update CI
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2025-09-06 17:54:53 -07:00
Alex Forencich
bc8dbb039c Remove extraneous scaleb(-9) in set_ts_tod_ns so that the seconds value is set correctly
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2024-02-09 15:11:32 -08:00
Alex Forencich
afb7d2bd10 Bump to dev version
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 12:08:09 -08:00
Alex Forencich
3b75a9a1a7 Release v0.1.22
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 11:56:35 -08:00
Alex Forencich
7d8d214b57 Improve PTP model resolution by using Decimal types and wider internal fns accumulators
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 01:28:25 -08:00
Alex Forencich
c44f928bea Rename PTP clock timestamp signals to tod and rel
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 01:28:04 -08:00
Alex Forencich
357dd26aae Use num/denom for PTP clock drift
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 00:41:02 -08:00
Alex Forencich
6131079494 Rename long-description-content-type in setup.cfg
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-11-07 00:13:56 -08:00
Alex Forencich
9079ca34f2 Parametrize EthMac test
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-05-26 17:18:16 -07:00
Alex Forencich
08fd179dac Fix logging when using from_entity
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-05-26 17:02:27 -07:00
Alex Forencich
1fa122e4ee Add Python 3.11 to regression tests
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-05-26 16:48:24 -07:00
Alex Forencich
4b13e61dc8 Remove recursively-expanded macros for module parameters in makefiles
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-05-26 16:47:53 -07:00
Alex Forencich
72f7290488 Use bytes instead of bytearrays
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-02 23:56:01 -08:00
Alex Forencich
6d1afe0904 Rework parameter handling in makefiles
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-29 19:54:10 -08:00
Alex Forencich
d61362e79b Bump to dev version
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 18:58:18 -08:00
Alex Forencich
2bfe8a0e50 Release v0.1.20
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 18:51:37 -08:00
Alex Forencich
9c88b0440e Update package versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 18:28:32 -08:00
Alex Forencich
37b23c358b Put sources and sinks to sleep when idle
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-24 17:42:05 -08:00
Alex Forencich
dd35d734f9 Put sources and sinks to sleep based on clock enables
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-24 17:41:36 -08:00
Alex Forencich
45ee1193cb Remove deprecated assignments
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-24 17:41:12 -08:00
Alex Forencich
5caafbb9e7 Update package versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-24 12:54:08 -08:00
Alex Forencich
2d4450e048 Fix path issue so latest coverage works
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 20:58:13 -08:00
Alex Forencich
c5d28182c4 Update github actions versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:38:05 -08:00
Alex Forencich
79991205b5 Fix tox config and lock package versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:37:51 -08:00
Alex Forencich
a22123649c Python 3.6 is EOL; remove from CI tests
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-18 17:53:30 -08:00
Alex Forencich
b5ba332ecc Specify min package versions 2021-12-27 17:17:40 -08:00
Alex Forencich
eb62e43fd1 Specify min tox and venv versions 2021-12-27 17:17:15 -08:00
Alex Forencich
5c4ef258ac Skip missing interpreters 2021-12-27 17:16:20 -08:00
Alex Forencich
6c5845fad3 Test on Python 3.10 2021-12-27 17:15:11 -08:00
Alex Forencich
32f6e449c0 Use start_soon instead of fork 2021-12-08 21:45:58 -08:00
Alex Forencich
2af7852006 Cache clock edge event objects 2021-12-03 19:06:43 -08:00
Alex Forencich
3325568406 Bump to dev version 2021-11-07 13:19:10 -08:00
Alex Forencich
6a35c31b4b Release v0.1.18 2021-11-07 12:40:42 -08:00
Alex Forencich
73fe54705f Remove deprecated assignments 2021-11-07 01:21:39 -08:00
Alex Forencich
448451e274 Add BASE-R related mappings 2021-10-15 02:00:03 -07:00
Alex Forencich
21c2c05c57 Normalize names 2021-10-14 19:13:51 -07:00
Alex Forencich
ab84a3b100 Add cocotb framework classifier 2021-08-31 14:43:03 -07:00
Alex Forencich
0f3060b9ba Bump to dev version 2021-08-31 01:04:20 -07:00
Alex Forencich
2ee51890f5 Release v0.1.16 2021-08-31 00:24:16 -07:00
Alex Forencich
fbdf4149b3 Update readme 2021-08-31 00:23:36 -07:00
Alex Forencich
03156ff759 Support PTP timestamp tags in MAC model 2021-08-31 00:23:07 -07:00
Alex Forencich
8956de42b5 Bump to dev version 2021-04-12 18:43:42 -07:00
Alex Forencich
de18e62024 Release v0.1.14 2021-04-12 18:18:26 -07:00
Alex Forencich
14738c1dae Print model configuration 2021-04-12 18:18:01 -07:00
Alex Forencich
3d43812c7b Rename byte_width to byte_lanes 2021-04-12 15:17:02 -07:00
Alex Forencich
008d903bb9 Send data without using pop 2021-04-12 15:10:52 -07:00
Alex Forencich
0bd66da868 Bump to dev version 2021-04-01 15:29:03 -07:00
45 changed files with 1235 additions and 914 deletions

63
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: build
on:
push:
branches:
- master
- 'dev/**'
pull_request:
branches: [ master ]
release:
types:
- published
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
name: Build distributions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
name: Install Python
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install build
- name: Build sdist
run: python -m build
- uses: actions/upload-artifact@v3
with:
name: dist
path: |
dist/*.tar.gz
dist/*.whl
#-------------------------------------------------------------------------------
deploy:
needs:
- build
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
# Only publish when a Gitea Release is created.
if: gitea.event_name == 'release'
steps:
- uses: actions/download-artifact@v3
with:
name: dist
path: dist
- run: python3 -m pip install twine --user --break-system-packages
- run: python3 -m pip install -U packaging --user --break-system-packages
- run: TWINE_PASSWORD=${{ secrets.PYPI_PAT }} TWINE_USERNAME=bslathi19 python -m twine upload --repository-url ${{ vars.CI_API_URL }} dist/*

View File

@@ -5,17 +5,17 @@ on: [push, pull_request]
jobs: jobs:
build: build:
name: Python ${{matrix.python-version}} name: Python ${{matrix.python-version}}
runs-on: ubuntu-20.04 runs-on: ubuntu-24.04
strategy: strategy:
matrix: matrix:
python-version: [3.6, 3.7, 3.8, 3.9] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

127
README.md
View File

@@ -1,6 +1,6 @@
# Ethernet interface modules for Cocotb # Ethernet interface modules for Cocotb
[![Build Status](https://github.com/alexforencich/cocotbext-eth/workflows/Regression%20Tests/badge.svg?branch=master)](https://github.com/alexforencich/cocotbext-eth/actions/) [![Regression Tests](https://github.com/alexforencich/cocotbext-eth/actions/workflows/regression-tests.yml/badge.svg)](https://github.com/alexforencich/cocotbext-eth/actions/workflows/regression-tests.yml)
[![codecov](https://codecov.io/gh/alexforencich/cocotbext-eth/branch/master/graph/badge.svg)](https://codecov.io/gh/alexforencich/cocotbext-eth) [![codecov](https://codecov.io/gh/alexforencich/cocotbext-eth/branch/master/graph/badge.svg)](https://codecov.io/gh/alexforencich/cocotbext-eth)
[![PyPI version](https://badge.fury.io/py/cocotbext-eth.svg)](https://pypi.org/project/cocotbext-eth) [![PyPI version](https://badge.fury.io/py/cocotbext-eth.svg)](https://pypi.org/project/cocotbext-eth)
[![Downloads](https://pepy.tech/badge/cocotbext-eth)](https://pepy.tech/project/cocotbext-eth) [![Downloads](https://pepy.tech/badge/cocotbext-eth)](https://pepy.tech/project/cocotbext-eth)
@@ -30,7 +30,7 @@ Installation for active development:
## Documentation and usage examples ## Documentation and usage examples
See the `tests` directory, [verilog-ethernet](https://github.com/alexforencich/verilog-ethernet), and [corundum](https://github.com/corundum/corundum) for complete testbenches using these modules. See the `tests` directory, [taxi](https://github.com/fpganinja/taxi), [verilog-ethernet](https://github.com/alexforencich/verilog-ethernet), and [corundum](https://github.com/corundum/corundum) for complete testbenches using these modules.
### GMII ### GMII
@@ -467,6 +467,7 @@ To use these modules, import the one you need and connect it to the DUT:
tx_bus=AxiStreamBus.from_prefix(dut, "tx_axis"), tx_bus=AxiStreamBus.from_prefix(dut, "tx_axis"),
tx_ptp_time=dut.tx_ptp_time, tx_ptp_time=dut.tx_ptp_time,
tx_ptp_ts=dut.tx_ptp_ts, tx_ptp_ts=dut.tx_ptp_ts,
tx_ptp_ts_tag=dut.tx_ptp_ts_tag,
tx_ptp_ts_valid=dut.tx_ptp_ts_valid, tx_ptp_ts_valid=dut.tx_ptp_ts_valid,
rx_clk=dut.rx_clk, rx_clk=dut.rx_clk,
rx_rst=dut.rx_rst, rx_rst=dut.rx_rst,
@@ -492,6 +493,8 @@ To receive data, call `recv()` or `recv_nowait()`. Optionally call `wait()` to
data = await mac.tx.recv() data = await mac.tx.recv()
PTP timestamping requires free-running PTP clocks driving the PTP time inputs, synchronous with the corresponding MAC clocks. The values of these fields are then captured when the frame SFD is transferred and returned either on tuser (for received frames) or on a separate streaming interface (for transmitted frames). Additionally, on the transmit path, a tag value from tuser is returned along with the timestamp.
#### Signals #### Signals
* `tdata`: payload data, must be a multiple of 8 bits * `tdata`: payload data, must be a multiple of 8 bits
@@ -499,9 +502,10 @@ To receive data, call `recv()` or `recv_nowait()`. Optionally call `wait()` to
* `tready`: indicates sink is ready for data (tx only) * `tready`: indicates sink is ready for data (tx only)
* `tlast`: marks the last cycle of a frame * `tlast`: marks the last cycle of a frame
* `tkeep`: qualifies data byte, data bus width must be evenly divisible by `tkeep` signal width * `tkeep`: qualifies data byte, data bus width must be evenly divisible by `tkeep` signal width
* `tuser`: user data, carries frame error mark and captured receive PTP timestamp * `tuser`: user data, carries frame error mark and captured receive PTP timestamp (RX) or PTP timestamp tag (TX)
* `ptp_time`: PTP time input from PHC, captured into `ptp_timestamp` field coincident with transfer of frame SFD and output on `ptp_ts` * `ptp_time`: PTP time input from PHC, captured into `ptp_timestamp` field coincident with transfer of frame SFD and output on `ptp_ts` (TX) or `tuser` (RX)
* `ptp_ts`: captured transmit PTP timestamp * `ptp_ts`: captured transmit PTP timestamp
* `ptp_ts_tag`: captured transmit PTP timestamp tag
* `ptp_ts_valid`: qualifies captured transmit PTP timestamp * `ptp_ts_valid`: qualifies captured transmit PTP timestamp
#### Constructor parameters (`EthMacRx` and `EthMacTx`): #### Constructor parameters (`EthMacRx` and `EthMacTx`):
@@ -510,7 +514,8 @@ To receive data, call `recv()` or `recv_nowait()`. Optionally call `wait()` to
* _clock_: clock signal * _clock_: clock signal
* _reset_: reset signal (optional) * _reset_: reset signal (optional)
* _ptp_time_: PTP time input from PHC (optional) * _ptp_time_: PTP time input from PHC (optional)
* _ptp_ts_: PTP timestamp output (optional) (tx) * _ptp_ts_: PTP timestamp (optional) (tx)
* _ptp_ts_tag_: PTP timestamp tag (optional) (tx)
* _ptp_ts_valid_: PTP timestamp valid (optional) (tx) * _ptp_ts_valid_: PTP timestamp valid (optional) (tx)
* _reset_active_level_: reset active level (optional, default `True`) * _reset_active_level_: reset active level (optional, default `True`)
* _ifg_: IFG size in byte times (optional, default `12`) * _ifg_: IFG size in byte times (optional, default `12`)
@@ -522,7 +527,8 @@ To receive data, call `recv()` or `recv_nowait()`. Optionally call `wait()` to
* _tx_clk_: transmit clock * _tx_clk_: transmit clock
* _tx_rst_: transmit reset (optional) * _tx_rst_: transmit reset (optional)
* _tx_ptp_time_: transmit PTP time input from PHC (optional) * _tx_ptp_time_: transmit PTP time input from PHC (optional)
* _tx_ptp_ts_: transmit PTP timestamp output (optional) * _tx_ptp_ts_: transmit PTP timestamp (optional)
* _tx_ptp_ts_tag_: transmit PTP timestamp tag (optional)
* _tx_ptp_ts_valid_: transmit PTP timestamp valid (optional) * _tx_ptp_ts_valid_: transmit PTP timestamp valid (optional)
* _rx_bus_: `AxiStreamBus` object containing receive AXI stream interface signals * _rx_bus_: `AxiStreamBus` object containing receive AXI stream interface signals
* _rx_clk_: receive clock * _rx_clk_: receive clock
@@ -565,6 +571,7 @@ Attributes:
* `sim_time_start`: simulation time of first transfer cycle of frame. * `sim_time_start`: simulation time of first transfer cycle of frame.
* `sim_time_sfd`: simulation time at which the SFD was transferred. * `sim_time_sfd`: simulation time at which the SFD was transferred.
* `sim_time_end`: simulation time of last transfer cycle of frame. * `sim_time_end`: simulation time of last transfer cycle of frame.
* `ptp_tag`: PTP timestamp tag for transmitted frames.
* `ptp_timestamp`: captured value of `ptp_time` at frame SFD * `ptp_timestamp`: captured value of `ptp_time` at frame SFD
* `tx_complete`: event or callable triggered when frame is transmitted. * `tx_complete`: event or callable triggered when frame is transmitted.
@@ -578,15 +585,15 @@ Methods:
### PTP clock ### PTP clock
The `PtpClock` class implements a PTP hardware clock that produces IEEE 1588 format 96 and 64 bit PTP timestamps. The `PtpClock` class implements a PTP hardware clock that produces IEEE 1588 format 96-bit time-of-day and 64-bit relative PTP timestamps.
To use this module, import it and connect it to the DUT: To use this module, import it and connect it to the DUT:
from cocotbext.eth import PtpClock from cocotbext.eth import PtpClock
ptp_clock = PtpClock( ptp_clock = PtpClock(
ts_96=dut.ts_96, ts_tod=dut.ts_tod,
ts_64=dut.ts_64, ts_rel=dut.ts_rel,
ts_step=dut.ts_step, ts_step=dut.ts_step,
pps=dut.pps, pps=dut.pps,
clock=dut.clk, clock=dut.clk,
@@ -596,17 +603,21 @@ To use this module, import it and connect it to the DUT:
Once the clock is instantiated, it will generate a continuous stream of monotonically increasing PTP timestamps on every clock edge. Once the clock is instantiated, it will generate a continuous stream of monotonically increasing PTP timestamps on every clock edge.
Internally, the `PtpClock` module uses 32-bit fractional ns fields for higher frequency resolution. Only the upper 16 bits are returned in the timestamps, but the full fns value can be accessed with the _ts_tod_fns_ and _ts_rel_fns_ attributes.
All APIs that handle fractional values use the `Decimal` type for maximum precision, as the combination of timestamp range and resolution is usually too much for normal floating point numbers to handle without significant loss of precision.
#### Signals #### Signals
* `ts_96`: 96-bit timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns) * `ts_tod`: 96-bit time-of-day timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns)
* `ts_64`: 64-bit timestamp (48 bit ns, 16 bit fractional ns) * `ts_rel`: 64-bit relative timestamp (48 bit ns, 16 bit fractional ns)
* `ts_step`: step output, pulsed when non-monotonic step occurs * `ts_step`: step output, pulsed when non-monotonic step occurs
* `pps`: pulse-per-second output, pulsed when ts_96 seconds field increments * `pps`: pulse-per-second output, pulsed when ts_tod seconds field increments
#### Constructor parameters: #### Constructor parameters:
* _ts_96_: 96-bit timestamp signal (optional) * _ts_tod_: 96-bit time-of-day timestamp signal (optional)
* _ts_64_: 64-bit timestamp signal (optional) * _ts_rel_: 64-bit relative timestamp signal (optional)
* _ts_step_: timestamp step signal (optional) * _ts_step_: timestamp step signal (optional)
* _pps_: pulse-per-second signal (optional) * _pps_: pulse-per-second signal (optional)
* _clock_: clock * _clock_: clock
@@ -616,74 +627,84 @@ Once the clock is instantiated, it will generate a continuous stream of monotoni
#### Attributes: #### Attributes:
* _ts_96_s_: current 96-bit timestamp seconds field * _ts_tod_s_: current 96-bit ToD timestamp seconds field
* _ts_96_ns_: current 96-bit timestamp ns field * _ts_tod_ns_: current 96-bit ToD timestamp ns field
* _ts_96_fns_: current 96-bit timestamp fractional ns field * _ts_tod_fns_: current 96-bit ToD timestamp fractional ns field
* _ts_64_ns_: current 64-bit timestamp ns field * _ts_rel_ns_: current 64-bit relative timestamp ns field
* _ts_64_fns_: current 64-bit timestamp fractional ns field * _ts_rel_fns_: current 64-bit relative timestamp fractional ns field
#### Methods #### Methods
* `set_period(ns, fns)`: set clock period from separate fields * `set_period(ns, fns)`: set clock period from separate fields
* `set_drift(ns, fns, rate)`: set clock drift from separate fields * `set_drift(num, denom)`: set clock drift from separate fields
* `set_period_ns(t)`: set clock period in ns (float) * `set_period_ns(t)`: set clock period and drift in ns (Decimal)
* `get_period_ns()`: return current clock period in ns (float) * `get_period_ns()`: return current clock period in ns (Decimal)
* `set_ts_96(ts_s, ts_ns=None, ts_fns=None)`: set 96-bit timestamp from integer or from separate fields * `set_ts_tod(ts_s, ts_ns, ts_fns)`: set 96-bit ToD timestamp from separate fields
* `set_ts_96_ns(t)`: set 96-bit timestamp from ns (float) * `set_ts_tod_96(ts)`: set 96-bit ToD timestamp from integer
* `set_ts_96_s(t)`: set 96-bit timestamp from seconds (float) * `set_ts_tod_ns(t)`: set 96-bit ToD timestamp from ns (Decimal)
* `get_ts_96()`: return current 96-bit timestamp as an integer * `set_ts_tod_s(t)`: set 96-bit ToD timestamp from seconds (Decimal)
* `get_ts_96_ns()`: return current 96-bit timestamp in ns (float) * `set_ts_tod_sim_time()`: set 96-bit ToD timestamp from sim time
* `get_ts_96_s()`: return current 96-bit timestamp in seconds (float) * `get_ts_tod()`: return current 96-bit ToD timestamp as separate fields
* `set_ts_64(ts_ns, ts_fns=None)`: set 64-bit timestamp from integer or from separate fields * `get_ts_tod_96()`: return current 96-bit ToD timestamp as an integer
* `set_ts_64_ns(t)`: set 64-bit timestamp from ns (float) * `get_ts_tod_ns()`: return current 96-bit ToD timestamp in ns (Decimal)
* `set_ts_64_s(t)`: set 64-bit timestamp from seconds (float) * `get_ts_tod_s()`: return current 96-bit ToD timestamp in seconds (Decimal)
* `get_ts_64()`: return current 64-bit timestamp as an integer * `set_ts_rel(ts_ns, ts_fns)`: set 64-bit relative timestamp from separate fields
* `get_ts_64_ns()`: return current 64-bit timestamp in ns (float) * `set_ts_rel_64(ts)`: set 64-bit relative timestamp from integer
* `get_ts_64_s()`: return current 64-bit timestamp in seconds (float) * `set_ts_rel_ns(t)`: set 64-bit relative timestamp from ns (Decimal)
* `set_ts_rel_s(t)`: set 64-bit relative timestamp from seconds (Decimal)
* `set_ts_rel_sim_time()`: set 64-bit relative timestamp from sim time
* `get_ts_rel()`: return current 64-bit relative timestamp as separate fields
* `get_ts_rel_64()`: return current 64-bit relative timestamp as an integer
* `get_ts_rel_ns()`: return current 64-bit relative timestamp in ns (Decimal)
* `get_ts_rel_s()`: return current 64-bit relative timestamp in seconds (Decimal)
### PTP clock (sim time) ### PTP clock (sim time)
The `PtpClockSimTime` class implements a PTP hardware clock that produces IEEE 1588 format 96 and 64 bit PTP timestamps, derived from the current simulation time. This module can be used in place of `PtpClock` so that captured PTP timestamps can be easily compared to captured simulation time. The `PtpClockSimTime` class implements a PTP hardware clock that produces IEEE 1588 format 96-bit time-of-day and 64-bit relative PTP timestamps, derived from the current simulation time. This module can be used in place of `PtpClock` so that captured PTP timestamps can be easily compared to captured simulation time.
To use this module, import it and connect it to the DUT: To use this module, import it and connect it to the DUT:
from cocotbext.eth import PtpClockSimTime from cocotbext.eth import PtpClockSimTime
ptp_clock = PtpClockSimTime( ptp_clock = PtpClockSimTime(
ts_96=dut.ts_96, ts_tod=dut.ts_tod,
ts_64=dut.ts_64, ts_rel=dut.ts_rel,
pps=dut.pps, pps=dut.pps,
clock=dut.clk clock=dut.clk
) )
Once the clock is instantiated, it will generate a continuous stream of monotonically increasing PTP timestamps on every clock edge. Once the clock is instantiated, it will generate a continuous stream of monotonically increasing PTP timestamps on every clock edge.
All APIs that handle fractional values use the `Decimal` type for maximum precision, as the combination of timestamp range and resolution is usually too much for normal floating point numbers to handle without significant loss of precision.
#### Signals #### Signals
* `ts_96`: 96-bit timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns) * `ts_tod`: 96-bit time-of-day timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns)
* `ts_64`: 64-bit timestamp (48 bit ns, 16 bit fractional ns) * `ts_rel`: 64-bit relative timestamp (48 bit ns, 16 bit fractional ns)
* `pps`: pulse-per-second output, pulsed when ts_96 seconds field increments * `pps`: pulse-per-second output, pulsed when ts_tod seconds field increments
#### Constructor parameters: #### Constructor parameters:
* _ts_96_: 96-bit timestamp signal (optional) * _ts_tod_: 96-bit time-of-day timestamp signal (optional)
* _ts_64_: 64-bit timestamp signal (optional) * _ts_rel_: 64-bit relative timestamp signal (optional)
* _pps_: pulse-per-second signal (optional) * _pps_: pulse-per-second signal (optional)
* _clock_: clock * _clock_: clock
#### Attributes: #### Attributes:
* _ts_96_s_: current 96-bit timestamp seconds field * _ts_tod_s_: current 96-bit ToD timestamp seconds field
* _ts_96_ns_: current 96-bit timestamp ns field * _ts_tod_ns_: current 96-bit ToD timestamp ns field
* _ts_96_fns_: current 96-bit timestamp fractional ns field * _ts_tod_fns_: current 96-bit ToD timestamp fractional ns field
* _ts_64_ns_: current 64-bit timestamp ns field * _ts_rel_ns_: current 64-bit relative timestamp ns field
* _ts_64_fns_: current 64-bit timestamp fractional ns field * _ts_rel_fns_: current 64-bit relative timestamp fractional ns field
#### Methods #### Methods
* `get_ts_96()`: return current 96-bit timestamp as an integer * `get_ts_tod()`: return current 96-bit ToD timestamp as separate fields
* `get_ts_96_ns()`: return current 96-bit timestamp in ns (float) * `get_ts_tod_96()`: return current 96-bit ToD timestamp as an integer
* `get_ts_96_s()`: return current 96-bit timestamp in seconds (float) * `get_ts_tod_ns()`: return current 96-bit ToD timestamp in ns (Decimal)
* `get_ts_64()`: return current 64-bit timestamp as an integer * `get_ts_tod_s()`: return current 96-bit ToD timestamp in seconds (Decimal)
* `get_ts_64_ns()`: return current 64-bit timestamp in ns (float) * `get_ts_rel()`: return current 64-bit relative timestamp as separate fields
* `get_ts_64_s()`: return current 64-bit timestamp in seconds (float) * `get_ts_rel_96()`: return current 64-bit relative timestamp as an integer
* `get_ts_rel_ns()`: return current 64-bit relative timestamp in ns (Decimal)
* `get_ts_rel_s()`: return current 64-bit relative timestamp in seconds (Decimal)

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -42,12 +42,12 @@ class XgmiiCtrl(enum.IntEnum):
TERM = 0xfd TERM = 0xfd
ERROR = 0xfe ERROR = 0xfe
SEQ_OS = 0x9c SEQ_OS = 0x9c
RES0 = 0x1c RES_0 = 0x1c
RES1 = 0x3c RES_1 = 0x3c
RES2 = 0x7c RES_2 = 0x7c
RES3 = 0xbc RES_3 = 0xbc
RES4 = 0xdc RES_4 = 0xdc
RES5 = 0xf7 RES_5 = 0xf7
SIG_OS = 0x5c SIG_OS = 0x5c
@@ -93,3 +93,41 @@ class BaseRBlockType(enum.IntEnum):
TERM_5 = 0xd2 # C7 C6 D4 D3 D2 D1 D0 BT TERM_5 = 0xd2 # C7 C6 D4 D3 D2 D1 D0 BT
TERM_6 = 0xe1 # C7 D5 D4 D3 D2 D1 D0 BT TERM_6 = 0xe1 # C7 D5 D4 D3 D2 D1 D0 BT
TERM_7 = 0xff # D6 D5 D4 D3 D2 D1 D0 BT TERM_7 = 0xff # D6 D5 D4 D3 D2 D1 D0 BT
xgmii_ctrl_to_baser_mapping = {
XgmiiCtrl.IDLE: BaseRCtrl.IDLE,
XgmiiCtrl.LPI: BaseRCtrl.LPI,
XgmiiCtrl.ERROR: BaseRCtrl.ERROR,
XgmiiCtrl.RES_0: BaseRCtrl.RES_0,
XgmiiCtrl.RES_1: BaseRCtrl.RES_1,
XgmiiCtrl.RES_2: BaseRCtrl.RES_2,
XgmiiCtrl.RES_3: BaseRCtrl.RES_3,
XgmiiCtrl.RES_4: BaseRCtrl.RES_4,
XgmiiCtrl.RES_5: BaseRCtrl.RES_5,
}
baser_ctrl_to_xgmii_mapping = {
BaseRCtrl.IDLE: XgmiiCtrl.IDLE,
BaseRCtrl.LPI: XgmiiCtrl.LPI,
BaseRCtrl.ERROR: XgmiiCtrl.ERROR,
BaseRCtrl.RES_0: XgmiiCtrl.RES_0,
BaseRCtrl.RES_1: XgmiiCtrl.RES_1,
BaseRCtrl.RES_2: XgmiiCtrl.RES_2,
BaseRCtrl.RES_3: XgmiiCtrl.RES_3,
BaseRCtrl.RES_4: XgmiiCtrl.RES_4,
BaseRCtrl.RES_5: XgmiiCtrl.RES_5,
}
block_type_term_lane_mapping = {
BaseRBlockType.TERM_0: 0,
BaseRBlockType.TERM_1: 1,
BaseRBlockType.TERM_2: 2,
BaseRBlockType.TERM_3: 3,
BaseRBlockType.TERM_4: 4,
BaseRBlockType.TERM_5: 5,
BaseRBlockType.TERM_6: 6,
BaseRBlockType.TERM_7: 7,
}

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2021 Alex Forencich Copyright (c) 2021-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -43,23 +43,25 @@ AxiStreamBus, AxiStreamTransaction, AxiStreamSource, AxiStreamSink, AxiStreamMon
class EthMacFrame: class EthMacFrame:
def __init__(self, data=None, tx_complete=None): def __init__(self, data=b'', tx_complete=None):
self.data = bytearray() self.data = b''
self.sim_time_start = None self.sim_time_start = None
self.sim_time_sfd = None self.sim_time_sfd = None
self.sim_time_end = None self.sim_time_end = None
self.ptp_timestamp = None self.ptp_timestamp = None
self.ptp_tag = None
self.tx_complete = None self.tx_complete = None
if type(data) is EthMacFrame: if type(data) is EthMacFrame:
self.data = bytearray(data.data) self.data = bytes(data.data)
self.sim_time_start = data.sim_time_start self.sim_time_start = data.sim_time_start
self.sim_time_sfd = data.sim_time_sfd self.sim_time_sfd = data.sim_time_sfd
self.sim_time_end = data.sim_time_end self.sim_time_end = data.sim_time_end
self.ptp_timestamp = data.ptp_timestamp self.ptp_timestamp = data.ptp_timestamp
self.ptp_tag = data.ptp_tag
self.tx_complete = data.tx_complete self.tx_complete = data.tx_complete
else: else:
self.data = bytearray(data) self.data = bytes(data)
if tx_complete is not None: if tx_complete is not None:
self.tx_complete = tx_complete self.tx_complete = tx_complete
@@ -104,7 +106,8 @@ class EthMacFrame:
f"sim_time_start={self.sim_time_start!r}, " f"sim_time_start={self.sim_time_start!r}, "
f"sim_time_sfd={self.sim_time_sfd!r}, " f"sim_time_sfd={self.sim_time_sfd!r}, "
f"sim_time_end={self.sim_time_end!r}, " f"sim_time_end={self.sim_time_end!r}, "
f"ptp_timestamp={self.ptp_timestamp!r})" f"ptp_timestamp={self.ptp_timestamp!r}, "
f"ptp_tag={self.ptp_tag!r})"
) )
def __len__(self): def __len__(self):
@@ -118,7 +121,7 @@ class EthMacFrame:
class EthMacTx(Reset): class EthMacTx(Reset):
def __init__(self, bus, clock, reset=None, ptp_time=None, ptp_ts=None, ptp_ts_valid=None, def __init__(self, bus, clock, reset=None, ptp_time=None, ptp_ts=None, ptp_ts_tag=None, ptp_ts_valid=None,
reset_active_level=True, ifg=12, speed=1000e6, *args, **kwargs): reset_active_level=True, ifg=12, speed=1000e6, *args, **kwargs):
self.bus = bus self.bus = bus
@@ -126,14 +129,18 @@ class EthMacTx(Reset):
self.reset = reset self.reset = reset
self.ptp_time = ptp_time self.ptp_time = ptp_time
self.ptp_ts = ptp_ts self.ptp_ts = ptp_ts
self.ptp_ts_tag = ptp_ts_tag
self.ptp_ts_valid = ptp_ts_valid self.ptp_ts_valid = ptp_ts_valid
self.ifg = ifg self.ifg = ifg
self.speed = speed self.speed = speed
if bus._name:
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
else:
self.log = logging.getLogger(f"cocotb.{bus._entity._name}")
self.log.info("Ethernet MAC TX model") self.log.info("Ethernet MAC TX model")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2021-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -172,6 +179,10 @@ class EthMacTx(Reset):
self.log.info(" tuser width: %d bits", len(self.bus.tuser)) self.log.info(" tuser width: %d bits", len(self.bus.tuser))
else: else:
self.log.info(" tuser: not present") self.log.info(" tuser: not present")
if self.ptp_time is not None:
self.log.info(" ptp_time width: %d bits", len(self.ptp_time))
else:
self.log.info(" ptp_time: not present")
if self.bus.tready is None: if self.bus.tready is None:
raise ValueError("tready is required") raise ValueError("tready is required")
@@ -183,9 +194,11 @@ class EthMacTx(Reset):
raise ValueError(f"Bus does not evenly divide into byte lanes " raise ValueError(f"Bus does not evenly divide into byte lanes "
f"({self.byte_lanes} * {self.byte_size} != {self.width})") f"({self.byte_lanes} * {self.byte_size} != {self.width})")
if self.ptp_ts: if self.ptp_ts is not None:
self.ptp_ts.setimmediatevalue(0) self.ptp_ts.setimmediatevalue(0)
if self.ptp_ts_valid: if self.ptp_ts_tag is not None:
self.ptp_ts_tag.setimmediatevalue(0)
if self.ptp_ts_valid is not None:
self.ptp_ts_valid.setimmediatevalue(0) self.ptp_ts_valid.setimmediatevalue(0)
self._run_cr = None self._run_cr = None
@@ -242,8 +255,8 @@ class EthMacTx(Reset):
self._run_ts_cr.kill() self._run_ts_cr.kill()
self._run_ts_cr = None self._run_ts_cr = None
if self.ptp_ts_valid: if self.ptp_ts_valid is not None:
self.ptp_ts_valid <= 0 self.ptp_ts_valid.value = 0
self.active = False self.active = False
@@ -252,9 +265,9 @@ class EthMacTx(Reset):
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
if self._run_ts_cr is None and self.ptp_ts: if self._run_ts_cr is None and self.ptp_ts is not None:
self._run_ts_cr = cocotb.fork(self._run_ts()) self._run_ts_cr = cocotb.start_soon(self._run_ts())
async def _run(self): async def _run(self):
frame = None frame = None
@@ -264,7 +277,9 @@ class EthMacTx(Reset):
# wait for data # wait for data
cycle = await self.stream.recv() cycle = await self.stream.recv()
frame = EthMacFrame(bytearray()) frame = EthMacFrame()
data = bytearray()
frame.sim_time_start = get_sim_time() frame.sim_time_start = get_sim_time()
# wait for preamble time # wait for preamble time
@@ -272,23 +287,25 @@ class EthMacTx(Reset):
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
if self.ptp_time: if self.ptp_time is not None:
frame.ptp_timestamp = self.ptp_time.value.integer frame.ptp_timestamp = int(self.ptp_time.value)
self.ts_queue.put_nowait(frame.ptp_timestamp) frame.ptp_tag = int(cycle.tuser) >> 1
self.ts_queue.put_nowait((frame.ptp_timestamp, frame.ptp_tag))
# process frame data # process frame data
while True: while True:
byte_count = 0 byte_count = 0
for offset in range(self.byte_lanes): for offset in range(self.byte_lanes):
if not hasattr(self.bus, "tkeep") or (cycle.tkeep.integer >> offset) & 1: if not hasattr(self.bus, "tkeep") or (int(cycle.tkeep) >> offset) & 1:
frame.data.append((cycle.tdata.integer >> (offset * self.byte_size)) & self.byte_mask) data.append((int(cycle.tdata) >> (offset * self.byte_size)) & self.byte_mask)
byte_count += 1 byte_count += 1
# wait for serialization time # wait for serialization time
await Timer(self.time_scale*byte_count*8//self.speed, 'step') await Timer(self.time_scale*byte_count*8//self.speed, 'step')
if cycle.tlast.integer: if int(cycle.tlast):
frame.data = bytes(data)
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
self.log.info("RX frame: %s", frame) self.log.info("RX frame: %s", frame)
@@ -311,14 +328,18 @@ class EthMacTx(Reset):
await Timer(self.time_scale*self.ifg*8//self.speed, 'step') await Timer(self.time_scale*self.ifg*8//self.speed, 'step')
async def _run_ts(self): async def _run_ts(self):
clock_edge_event = RisingEdge(self.clock)
while True: while True:
await RisingEdge(self.clock) await clock_edge_event
self.ptp_ts_valid <= 0 self.ptp_ts_valid.value = 0
if not self.ts_queue.empty(): if not self.ts_queue.empty():
ts = self.ts_queue.get_nowait() ts, tag = self.ts_queue.get_nowait()
self.ptp_ts <= ts self.ptp_ts.value = ts
self.ptp_ts_valid <= 1 if self.ptp_ts_tag is not None:
self.ptp_ts_tag.value = tag
self.ptp_ts_valid.value = 1
class EthMacRx(Reset): class EthMacRx(Reset):
@@ -331,11 +352,14 @@ class EthMacRx(Reset):
self.ptp_time = ptp_time self.ptp_time = ptp_time
self.ifg = ifg self.ifg = ifg
self.speed = speed self.speed = speed
if bus._name:
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
else:
self.log = logging.getLogger(f"cocotb.{bus._entity._name}")
self.log.info("Ethernet MAC RX model") self.log.info("Ethernet MAC RX model")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2021-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -378,6 +402,10 @@ class EthMacRx(Reset):
self.log.info(" tuser width: %d bits", len(self.bus.tuser)) self.log.info(" tuser width: %d bits", len(self.bus.tuser))
else: else:
self.log.info(" tuser: not present") self.log.info(" tuser: not present")
if self.ptp_time is not None:
self.log.info(" ptp_time width: %d bits", len(self.ptp_time))
else:
self.log.info(" ptp_time: not present")
if self.byte_size != 8: if self.byte_size != 8:
raise ValueError("Byte size must be 8") raise ValueError("Byte size must be 8")
@@ -458,10 +486,11 @@ class EthMacRx(Reset):
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
tuser = 0 tuser = 0
self.active = False self.active = False
@@ -477,14 +506,15 @@ class EthMacRx(Reset):
frame.sim_time_sfd = None frame.sim_time_sfd = None
frame.sim_time_end = None frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame_offset = 0
# wait for preamble time # wait for preamble time
await Timer(self.time_scale*8*8//self.speed, 'step') await Timer(self.time_scale*8*8//self.speed, 'step')
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
if self.ptp_time: if self.ptp_time is not None:
frame.ptp_timestamp = self.ptp_time.value.integer frame.ptp_timestamp = int(self.ptp_time.value)
tuser |= frame.ptp_timestamp << 1 tuser |= frame.ptp_timestamp << 1
# process frame data # process frame data
@@ -499,11 +529,12 @@ class EthMacRx(Reset):
cycle.tuser = tuser cycle.tuser = tuser
for offset in range(self.byte_lanes): for offset in range(self.byte_lanes):
cycle.tdata |= (frame.data.pop(0) & self.byte_mask) << (offset * self.byte_size) cycle.tdata |= (frame.data[frame_offset] & self.byte_mask) << (offset * self.byte_size)
cycle.tkeep |= 1 << offset cycle.tkeep |= 1 << offset
byte_count += 1 byte_count += 1
frame_offset += 1
if len(frame.data) == 0: if frame_offset >= len(frame.data):
cycle.tlast = 1 cycle.tlast = 1
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
@@ -521,13 +552,13 @@ class EthMacRx(Reset):
class EthMac: class EthMac:
def __init__(self, tx_bus=None, tx_clk=None, tx_rst=None, tx_ptp_time=None, tx_ptp_ts=None, tx_ptp_ts_valid=None, def __init__(self, tx_bus=None, tx_clk=None, tx_rst=None, tx_ptp_time=None, tx_ptp_ts=None, tx_ptp_ts_tag=None,
rx_bus=None, rx_clk=None, rx_rst=None, rx_ptp_time=None, tx_ptp_ts_valid=None, rx_bus=None, rx_clk=None, rx_rst=None, rx_ptp_time=None,
reset_active_level=True, ifg=12, speed=1000e6, *args, **kwargs): reset_active_level=True, ifg=12, speed=1000e6, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.tx = EthMacTx(tx_bus, tx_clk, tx_rst, tx_ptp_time, tx_ptp_ts, tx_ptp_ts_valid, self.tx = EthMacTx(tx_bus, tx_clk, tx_rst, tx_ptp_time, tx_ptp_ts, tx_ptp_ts_tag, tx_ptp_ts_valid,
reset_active_level=reset_active_level, ifg=ifg, speed=speed) reset_active_level=reset_active_level, ifg=ifg, speed=speed)
self.rx = EthMacRx(rx_bus, rx_clk, rx_rst, rx_ptp_time, self.rx = EthMacRx(rx_bus, rx_clk, rx_rst, rx_ptp_time,
reset_active_level=reset_active_level, ifg=ifg, speed=speed) reset_active_level=reset_active_level, ifg=ifg, speed=speed)

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -146,7 +146,7 @@ class GmiiSource(Reset):
self.log.info("GMII source") self.log.info("GMII source")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -157,6 +157,7 @@ class GmiiSource(Reset):
self.current_frame = None self.current_frame = None
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event()
self.ifg = 12 self.ifg = 12
self.mii_mode = False self.mii_mode = False
@@ -189,6 +190,7 @@ class GmiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame) await self.queue.put(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -198,6 +200,7 @@ class GmiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
self.queue.put_nowait(frame) self.queue.put_nowait(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -225,6 +228,7 @@ class GmiiSource(Reset):
frame.handle_tx_complete() frame.handle_tx_complete()
self.dequeue_event.set() self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -239,10 +243,10 @@ class GmiiSource(Reset):
self._run_cr = None self._run_cr = None
self.active = False self.active = False
self.data <= 0 self.data.value = 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er.value = 0
self.dv <= 0 self.dv.value = 0
if self.current_frame: if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame) self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
@@ -251,20 +255,30 @@ class GmiiSource(Reset):
if self.queue.empty(): if self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0 ifg_cnt = 0
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: enable_event = None
if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
if ifg_cnt > 0: if ifg_cnt > 0:
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
@@ -283,43 +297,54 @@ class GmiiSource(Reset):
frame.normalize() frame.normalize()
if self.mii_select is not None: if self.mii_select is not None:
self.mii_mode = bool(self.mii_select.value.integer) self.mii_mode = bool(int(self.mii_select.value))
if self.mii_mode: if self.mii_mode:
mii_data = [] # convert to MII
mii_error = [] frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error): for b, e in zip(frame.data, frame.error):
mii_data.append(b & 0x0F) frame_data.append(b & 0x0F)
mii_data.append(b >> 4) frame_data.append(b >> 4)
mii_error.append(e) frame_error.append(e)
mii_error.append(e) frame_error.append(e)
frame.data = mii_data else:
frame.error = mii_error frame_data = frame.data
frame_error = frame.error
self.active = True self.active = True
frame_offset = 0
if frame is not None: if frame is not None:
d = frame.data.pop(0) d = frame_data[frame_offset]
if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD): if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD):
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
self.data <= d self.data.value = d
if self.er is not None: if self.er is not None:
self.er <= frame.error.pop(0) self.er.value = frame_error[frame_offset]
self.dv <= 1 self.dv.value = 1
frame_offset += 1
if not frame.data: if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None self.current_frame = None
else: else:
self.data <= 0 self.data.value = 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er.value = 0
self.dv <= 0 self.dv.value = 0
self.active = False self.active = False
if ifg_cnt == 0 and self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
await self.active_event.wait()
elif self.enable is not None and not self.enable.value:
await enable_event
class GmiiSink(Reset): class GmiiSink(Reset):
@@ -336,7 +361,7 @@ class GmiiSink(Reset):
self.log.info("GMII sink") self.log.info("GMII sink")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -415,19 +440,27 @@ class GmiiSink(Reset):
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: active_event = RisingEdge(self.dv)
d_val = self.data.value.integer
dv_val = self.dv.value.integer enable_event = None
er_val = 0 if self.er is None else self.er.value.integer if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
d_val = int(self.data.value)
dv_val = int(self.dv.value)
er_val = 0 if self.er is None else int(self.er.value)
if frame is None: if frame is None:
if dv_val: if dv_val:
@@ -439,7 +472,7 @@ class GmiiSink(Reset):
# end of frame # end of frame
if self.mii_select is not None: if self.mii_select is not None:
self.mii_mode = bool(self.mii_select.value.integer) self.mii_mode = bool(int(self.mii_select.value))
if self.mii_mode: if self.mii_mode:
odd = True odd = True
@@ -481,6 +514,12 @@ class GmiiSink(Reset):
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
if not dv_val:
await active_event
elif self.enable is not None and not self.enable.value:
await enable_event
class GmiiPhy: class GmiiPhy:
def __init__(self, txd, tx_er, tx_en, tx_clk, gtx_clk, rxd, rx_er, rx_dv, rx_clk, def __init__(self, txd, tx_er, tx_en, tx_clk, gtx_clk, rxd, rx_er, rx_dv, rx_clk,
@@ -510,12 +549,12 @@ class GmiiPhy:
self._clock_cr.kill() self._clock_cr.kill()
if self.speed == 1000e6: if self.speed == 1000e6:
self._clock_cr = cocotb.fork(self._run_clocks(8*1e9/self.speed)) self._clock_cr = cocotb.start_soon(self._run_clocks(8*1e9/self.speed))
self.tx.mii_mode = False self.tx.mii_mode = False
self.rx.mii_mode = False self.rx.mii_mode = False
self.tx.clock = self.gtx_clk self.tx.clock = self.gtx_clk
else: else:
self._clock_cr = cocotb.fork(self._run_clocks(4*1e9/self.speed)) self._clock_cr = cocotb.start_soon(self._run_clocks(4*1e9/self.speed))
self.tx.mii_mode = True self.tx.mii_mode = True
self.rx.mii_mode = True self.rx.mii_mode = True
self.tx.clock = self.tx_clk self.tx.clock = self.tx_clk
@@ -529,8 +568,8 @@ class GmiiPhy:
while True: while True:
await t await t
self.rx_clk <= 1 self.rx_clk.value = 1
self.tx_clk <= 1 self.tx_clk.value = 1
await t await t
self.rx_clk <= 0 self.rx_clk.value = 0
self.tx_clk <= 0 self.tx_clk.value = 0

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -48,7 +48,7 @@ class MiiSource(Reset):
self.log.info("MII source") self.log.info("MII source")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -59,6 +59,7 @@ class MiiSource(Reset):
self.current_frame = None self.current_frame = None
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event()
self.ifg = 12 self.ifg = 12
@@ -90,6 +91,7 @@ class MiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame) await self.queue.put(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -99,6 +101,7 @@ class MiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
self.queue.put_nowait(frame) self.queue.put_nowait(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -126,6 +129,7 @@ class MiiSource(Reset):
frame.handle_tx_complete() frame.handle_tx_complete()
self.dequeue_event.set() self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -140,10 +144,10 @@ class MiiSource(Reset):
self._run_cr = None self._run_cr = None
self.active = False self.active = False
self.data <= 0 self.data.value = 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er.value = 0
self.dv <= 0 self.dv.value = 0
if self.current_frame: if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame) self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
@@ -152,20 +156,30 @@ class MiiSource(Reset):
if self.queue.empty(): if self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0 ifg_cnt = 0
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: enable_event = None
if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
if ifg_cnt > 0: if ifg_cnt > 0:
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
@@ -183,40 +197,48 @@ class MiiSource(Reset):
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
mii_data = [] # convert to MII
mii_error = [] frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error): for b, e in zip(frame.data, frame.error):
mii_data.append(b & 0x0F) frame_data.append(b & 0x0F)
mii_data.append(b >> 4) frame_data.append(b >> 4)
mii_error.append(e) frame_error.append(e)
mii_error.append(e) frame_error.append(e)
frame.data = mii_data
frame.error = mii_error
self.active = True self.active = True
frame_offset = 0
if frame is not None: if frame is not None:
d = frame.data.pop(0) d = frame_data[frame_offset]
if frame.sim_time_sfd is None and d == 0xD: if frame.sim_time_sfd is None and d == 0xD:
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
self.data <= d self.data.value = d
if self.er is not None: if self.er is not None:
self.er <= frame.error.pop(0) self.er.value = frame_error[frame_offset]
self.dv <= 1 self.dv.value = 1
frame_offset += 1
if not frame.data: if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None self.current_frame = None
else: else:
self.data <= 0 self.data.value = 0
if self.er is not None: if self.er is not None:
self.er <= 0 self.er.value = 0
self.dv <= 0 self.dv.value = 0
self.active = False self.active = False
if ifg_cnt == 0 and self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
await self.active_event.wait()
elif self.enable is not None and not self.enable.value:
await enable_event
class MiiSink(Reset): class MiiSink(Reset):
@@ -232,7 +254,7 @@ class MiiSink(Reset):
self.log.info("MII sink") self.log.info("MII sink")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -309,19 +331,27 @@ class MiiSink(Reset):
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: active_event = RisingEdge(self.dv)
d_val = self.data.value.integer
dv_val = self.dv.value.integer enable_event = None
er_val = 0 if self.er is None else self.er.value.integer if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
d_val = int(self.data.value)
dv_val = int(self.dv.value)
er_val = 0 if self.er is None else int(self.er.value)
if frame is None: if frame is None:
if dv_val: if dv_val:
@@ -370,6 +400,12 @@ class MiiSink(Reset):
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
if not dv_val:
await active_event
elif self.enable is not None and not self.enable.value:
await enable_event
class MiiPhy: class MiiPhy:
def __init__(self, txd, tx_er, tx_en, tx_clk, rxd, rx_er, rx_dv, rx_clk, reset=None, def __init__(self, txd, tx_er, tx_en, tx_clk, rxd, rx_er, rx_dv, rx_clk, reset=None,
@@ -398,7 +434,7 @@ class MiiPhy:
if self._clock_cr is not None: if self._clock_cr is not None:
self._clock_cr.kill() self._clock_cr.kill()
self._clock_cr = cocotb.fork(self._run_clocks(4*1e9/self.speed)) self._clock_cr = cocotb.start_soon(self._run_clocks(4*1e9/self.speed))
async def _run_clocks(self, period): async def _run_clocks(self, period):
half_period = get_sim_steps(period / 2.0, 'ns') half_period = get_sim_steps(period / 2.0, 'ns')
@@ -406,8 +442,8 @@ class MiiPhy:
while True: while True:
await t await t
self.tx_clk <= 1 self.tx_clk.value = 1
self.rx_clk <= 1 self.rx_clk.value = 1
await t await t
self.tx_clk <= 0 self.tx_clk.value = 0
self.rx_clk <= 0 self.rx_clk.value = 0

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ THE SOFTWARE.
""" """
import logging import logging
import math from decimal import Decimal, Context
from fractions import Fraction from fractions import Fraction
import cocotb import cocotb
@@ -38,8 +38,8 @@ class PtpClock(Reset):
def __init__( def __init__(
self, self,
ts_96=None, ts_tod=None,
ts_64=None, ts_rel=None,
ts_step=None, ts_step=None,
pps=None, pps=None,
clock=None, clock=None,
@@ -49,42 +49,42 @@ class PtpClock(Reset):
*args, **kwargs): *args, **kwargs):
self.log = logging.getLogger(f"cocotb.eth.{type(self).__name__}") self.log = logging.getLogger(f"cocotb.eth.{type(self).__name__}")
self.ts_96 = ts_96 self.ts_tod = ts_tod
self.ts_64 = ts_64 self.ts_rel = ts_rel
self.ts_step = ts_step self.ts_step = ts_step
self.pps = pps self.pps = pps
self.clock = clock self.clock = clock
self.reset = reset self.reset = reset
self.period_ns = 0
self.period_fns = 0
self.drift_ns = 0
self.drift_fns = 0
self.drift_rate = 0
self.set_period_ns(period_ns)
self.log.info("PTP clock") self.log.info("PTP clock")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.ts_96_s = 0 self.ctx = Context(prec=60)
self.ts_96_ns = 0
self.ts_96_fns = 0
self.ts_64_ns = 0 self.period_ns = 0
self.ts_64_fns = 0 self.period_fns = 0
self.drift_num = 0
self.drift_denom = 0
self.drift_cnt = 0
self.set_period_ns(period_ns)
self.ts_tod_s = 0
self.ts_tod_ns = 0
self.ts_tod_fns = 0
self.ts_rel_ns = 0
self.ts_rel_fns = 0
self.ts_updated = False self.ts_updated = False
self.drift_cnt = 0 if self.ts_tod is not None:
self.ts_tod.setimmediatevalue(0)
if self.ts_96 is not None: if self.ts_rel is not None:
self.ts_96.setimmediatevalue(0) self.ts_rel.setimmediatevalue(0)
if self.ts_64 is not None:
self.ts_64.setimmediatevalue(0)
if self.ts_step is not None: if self.ts_step is not None:
self.ts_step.setimmediatevalue(0) self.ts_step.setimmediatevalue(0)
if self.pps is not None: if self.pps is not None:
@@ -96,90 +96,103 @@ class PtpClock(Reset):
def set_period(self, ns, fns): def set_period(self, ns, fns):
self.period_ns = int(ns) self.period_ns = int(ns)
self.period_fns = int(fns) & 0xffff self.period_fns = int(fns) & 0xffffffff
def set_drift(self, ns, fns, rate): def set_drift(self, num, denom):
self.drift_ns = int(ns) self.drift_num = int(num)
self.drift_fns = int(fns) & 0xffff self.drift_denom = int(denom)
self.drift_rate = int(rate)
def set_period_ns(self, t): def set_period_ns(self, t):
drift, period = math.modf(t*2**16) t = Decimal(t)
period, drift = self.ctx.divmod(Decimal(t) * Decimal(2**32), Decimal(1))
period = int(period) period = int(period)
frac = Fraction(drift).limit_denominator(2**16) frac = Fraction(drift).limit_denominator(2**16-1)
drift = frac.numerator self.set_period(period >> 32, period & 0xffffffff)
rate = frac.denominator self.set_drift(frac.numerator, frac.denominator)
self.period_ns = period >> 16
self.period_fns = period & 0xffff self.log.info("Set period: %s ns", t)
self.drift_ns = drift >> 16 self.log.info("Period: 0x%x ns 0x%08x fns", self.period_ns, self.period_fns)
self.drift_fns = drift & 0xffff self.log.info("Drift: 0x%04x / 0x%04x fns", self.drift_num, self.drift_denom)
self.drift_rate = rate
def get_period_ns(self): def get_period_ns(self):
p = ((self.period_ns << 16) | self.period_fns) / 2**16 p = Decimal((self.period_ns << 32) | self.period_fns)
if self.drift_rate: if self.drift_denom:
return p + ((self.drift_ns << 16) | self.drift_fns) / self.drift_rate / 2**16 p += Decimal(self.drift_num) / Decimal(self.drift_denom)
return p return p / Decimal(2**32)
def set_ts_96(self, ts_s, ts_ns=None, ts_fns=None): def set_ts_tod(self, ts_s, ts_ns, ts_fns):
ts_s = int(ts_s) self.ts_tod_s = int(ts_s)
if ts_fns is not None: self.ts_tod_ns = int(ts_ns)
# got separate fields self.ts_tod_fns = int(ts_fns)
self.ts_96_s = ts_s
self.ts_96_ns = int(ts_ns)
self.ts_96_fns = int(ts_fns)
else:
# got timestamp as integer
self.ts_96_s = ts_s >> 48
self.ts_96_ns = (ts_s >> 16) & 0x3fffffff
self.ts_96_fns = ts_s & 0xffff
self.ts_updated = True self.ts_updated = True
def set_ts_96_ns(self, t): def set_ts_tod_96(self, ts):
self.set_ts_96_s(t*1e-9) ts = int(ts)
self.set_ts_tod(ts >> 48, (ts >> 32) & 0x3fffffff, (ts & 0xffff) << 16)
def set_ts_96_s(self, t): def set_ts_tod_ns(self, t):
ts_ns, ts_s = math.modf(t) ts_s, ts_ns = self.ctx.divmod(Decimal(t), Decimal(1000000000))
ts_ns *= 1e9 ts_ns, ts_fns = self.ctx.divmod(ts_ns, Decimal(1))
ts_fns, ts_ns = math.modf(ts_ns) ts_ns = ts_ns.to_integral_value()
ts_fns *= 2**16 ts_fns = (ts_fns * Decimal(2**32)).to_integral_value()
self.set_ts_96(ts_s, ts_ns, ts_fns) self.set_ts_tod(ts_s, ts_ns, ts_fns)
def get_ts_96(self): def set_ts_tod_s(self, t):
return (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns self.set_ts_tod_ns(Decimal(t).scaleb(9, self.ctx))
def get_ts_96_ns(self): def set_ts_tod_sim_time(self):
return self.ts_96_s*1e9+self.ts_96_ns+self.ts_96_fns/2**16 self.set_ts_tod_ns(Decimal(get_sim_time('fs')).scaleb(-6))
def get_ts_96_s(self): def get_ts_tod(self):
return self.get_ts_96_ns()*1e-9 return (self.ts_tod_s, self.ts_tod_ns, self.ts_tod_fns)
def set_ts_64(self, ts_ns, ts_fns=None): def get_ts_tod_96(self):
ts_ns = int(ts_ns) ts_s, ts_ns, ts_fns = self.get_ts_tod()
if ts_fns is not None: return (ts_s << 48) | (ts_ns << 16) | (ts_fns >> 16)
# got separate fields
self.ts_64_ns = ts_ns def get_ts_tod_ns(self):
self.ts_64_fns = int(ts_fns) ts_s, ts_ns, ts_fns = self.get_ts_tod()
else: ns = Decimal(ts_fns) / Decimal(2**32)
# got timestamp as integer ns = self.ctx.add(ns, Decimal(ts_ns))
self.ts_64_ns = ts_ns >> 16 return self.ctx.add(ns, Decimal(ts_s).scaleb(9))
self.ts_64_fns = ts_ns & 0xffff
def get_ts_tod_s(self):
return self.get_ts_tod_ns().scaleb(-9, self.ctx)
def set_ts_rel(self, ts_ns, ts_fns):
self.ts_rel_ns = int(ts_ns)
self.ts_rel_fns = int(ts_fns)
self.ts_updated = True self.ts_updated = True
def set_ts_64_ns(self, t): def set_ts_rel_64(self, ts):
self.set_ts_64(t*2**16) ts = int(ts)
self.set_ts_rel(ts >> 16, (ts & 0xffff) << 16)
def set_ts_64_s(self, t): def set_ts_rel_ns(self, t):
self.set_ts_64_ns(t*1e9) ts_ns, ts_fns = self.ctx.divmod(Decimal(t), Decimal(1))
ts_ns = ts_ns.to_integral_value()
ts_fns = (ts_fns * Decimal(2**32)).to_integral_value()
self.set_ts_rel(ts_ns, ts_fns)
def get_ts_64(self): def set_ts_rel_s(self, t):
return (self.ts_64_ns << 16) | self.ts_64_fns self.set_ts_rel_ns(Decimal(t).scaleb(9, self.ctx))
def get_ts_64_ns(self): def set_ts_rel_sim_time(self):
return self.get_ts_64()/2**16 self.set_ts_rel_ns(Decimal(get_sim_time('fs')).scaleb(-6))
def get_ts_64_s(self): def get_ts_rel(self):
return self.get_ts_64()*1e-9 return (self.ts_rel_ns, self.ts_rel_fns)
def get_ts_rel_64(self):
ts_ns, ts_fns = self.get_ts_rel()
return (ts_ns << 16) | (ts_fns >> 16)
def get_ts_rel_ns(self):
ts_ns, ts_fns = self.get_ts_rel()
return self.ctx.add(Decimal(ts_fns) / Decimal(2**32), Decimal(ts_ns))
def get_ts_rel_s(self):
return self.get_ts_rel_ns().scaleb(-9, self.ctx)
def _handle_reset(self, state): def _handle_reset(self, state):
if state: if state:
@@ -188,145 +201,169 @@ class PtpClock(Reset):
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.ts_96_s = 0 self.ts_tod_s = 0
self.ts_96_ns = 0 self.ts_tod_ns = 0
self.ts_96_fns = 0 self.ts_tod_fns = 0
self.ts_64_ns = 0 self.ts_rel_ns = 0
self.ts_64_fns = 0 self.ts_rel_fns = 0
self.drift_cnt = 0 self.drift_cnt = 0
if self.ts_96 is not None: if self.ts_tod is not None:
self.ts_96 <= 0 self.ts_tod.value = 0
if self.ts_64 is not None: if self.ts_rel is not None:
self.ts_64 <= 0 self.ts_rel.value = 0
if self.ts_step is not None: if self.ts_step is not None:
self.ts_step <= 0 self.ts_step.value = 0
if self.pps is not None: if self.pps is not None:
self.pps <= 0 self.pps.value = 0
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
clock_edge_event = RisingEdge(self.clock)
while True: while True:
await RisingEdge(self.clock) await clock_edge_event
if self.ts_step is not None: if self.ts_step is not None:
self.ts_step <= self.ts_updated self.ts_step.value = self.ts_updated
self.ts_updated = False self.ts_updated = False
if self.pps is not None: if self.pps is not None:
self.pps <= 0 self.pps.value = 0
# increment 96 bit timestamp # increment tod bit timestamp
if self.ts_96 is not None or self.pps is not None: self.ts_tod_fns += (self.period_ns << 32) + self.period_fns
t = ((self.ts_96_ns << 16) + self.ts_96_fns) + ((self.period_ns << 16) + self.period_fns)
if self.drift_rate and self.drift_cnt == 0: if self.drift_denom and self.drift_cnt == 0:
t += (self.drift_ns << 16) + self.drift_fns self.ts_tod_fns += self.drift_num
if t > (1000000000 << 16): ns_inc = self.ts_tod_fns >> 32
self.ts_96_s += 1 self.ts_tod_fns &= 0xffffffff
t -= (1000000000 << 16)
self.ts_tod_ns += ns_inc
if self.ts_tod_ns >= 1000000000:
self.ts_tod_s += 1
self.ts_tod_ns -= 1000000000
if self.pps is not None: if self.pps is not None:
self.pps <= 1 self.pps.value = 1
self.ts_96_fns = t & 0xffff if self.ts_tod is not None:
self.ts_96_ns = t >> 16 self.ts_tod.value = (self.ts_tod_s << 48) | (self.ts_tod_ns << 16) | (self.ts_tod_fns >> 16)
if self.ts_96 is not None: # increment rel bit timestamp
self.ts_96 <= (self.ts_96_s << 48) | (self.ts_96_ns << 16) | (self.ts_96_fns) self.ts_rel_fns += (self.period_ns << 32) + self.period_fns
# increment 64 bit timestamp if self.drift_denom and self.drift_cnt == 0:
if self.ts_64 is not None: self.ts_rel_fns += self.drift_num
t = ((self.ts_64_ns << 16) + self.ts_64_fns) + ((self.period_ns << 16) + self.period_fns)
if self.drift_rate and self.drift_cnt == 0: ns_inc = self.ts_rel_fns >> 32
t += ((self.drift_ns << 16) + self.drift_fns) self.ts_rel_fns &= 0xffffffff
self.ts_64_fns = t & 0xffff self.ts_rel_ns = (self.ts_rel_ns + ns_inc) & 0xffffffffffff
self.ts_64_ns = t >> 16
self.ts_64 <= (self.ts_64_ns << 16) | self.ts_64_fns if self.ts_rel is not None:
self.ts_rel.value = (self.ts_rel_ns << 16) | (self.ts_rel_fns >> 16)
if self.drift_rate: if self.drift_denom:
if self.drift_cnt > 0: if self.drift_cnt > 0:
self.drift_cnt -= 1 self.drift_cnt -= 1
else: else:
self.drift_cnt = self.drift_rate-1 self.drift_cnt = self.drift_denom-1
class PtpClockSimTime: class PtpClockSimTime:
def __init__(self, ts_96=None, ts_64=None, pps=None, clock=None, *args, **kwargs): def __init__(self, ts_tod=None, ts_rel=None, pps=None, clock=None, *args, **kwargs):
self.log = logging.getLogger(f"cocotb.eth.{type(self).__name__}") self.log = logging.getLogger(f"cocotb.eth.{type(self).__name__}")
self.ts_96 = ts_96 self.ts_tod = ts_tod
self.ts_64 = ts_64 self.ts_rel = ts_rel
self.pps = pps self.pps = pps
self.clock = clock self.clock = clock
self.log.info("PTP clock (sim time)") self.log.info("PTP clock (sim time)")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.ts_96_s = 0 self.ctx = Context(prec=60)
self.ts_96_ns = 0
self.ts_96_fns = 0
self.ts_64_ns = 0 self.ts_tod_s = 0
self.ts_64_fns = 0 self.ts_tod_ns = 0
self.ts_tod_fns = 0
self.last_ts_96_s = 0 self.ts_rel_ns = 0
self.ts_rel_fns = 0
if self.ts_96 is not None: self.last_ts_tod_s = 0
self.ts_96.setimmediatevalue(0)
if self.ts_64 is not None: if self.ts_tod is not None:
self.ts_64.setimmediatevalue(0) self.ts_tod.setimmediatevalue(0)
if self.ts_rel is not None:
self.ts_rel.setimmediatevalue(0)
if self.pps is not None: if self.pps is not None:
self.pps <= 0 self.pps.value = 0
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
def get_ts_96(self): def get_ts_tod(self):
return (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns return (self.ts_tod_s, self.ts_tod_ns, self.ts_tod_fns)
def get_ts_96_ns(self): def get_ts_tod_96(self):
return self.ts_96_s*1e9+self.ts_96_ns+self.ts_96_fns/2**16 ts_s, ts_ns, ts_fns = self.get_ts_tod()
return (ts_s << 48) | (ts_ns << 16) | (ts_fns >> 16)
def get_ts_96_s(self): def get_ts_tod_ns(self):
return self.get_ts_96_ns()*1e-9 ts_s, ts_ns, ts_fns = self.get_ts_tod()
ns = Decimal(ts_fns) / Decimal(2**32)
ns = self.ctx.add(ns, Decimal(ts_ns))
return self.ctx.add(ns, Decimal(ts_s).scaleb(9))
def get_ts_64(self): def get_ts_tod_s(self):
return (self.ts_64_ns << 16) | self.ts_64_fns return self.get_ts_tod_ns().scaleb(-9, self.ctx)
def get_ts_64_ns(self): def get_ts_rel(self):
return self.get_ts_64()/2**16 return (self.ts_rel_ns, self.ts_rel_fns)
def get_ts_64_s(self): def get_ts_rel_64(self):
return self.get_ts_64()*1e-9 ts_ns, ts_fns = self.get_ts_rel()
return (ts_ns << 16) | (ts_fns >> 16)
def get_ts_rel_ns(self):
ts_ns, ts_fns = self.get_ts_rel()
return self.ctx.add(Decimal(ts_fns) / Decimal(2**32), Decimal(ts_ns))
def get_ts_rel_s(self):
return self.get_ts_rel_ns().scaleb(-9, self.ctx)
async def _run(self): async def _run(self):
clock_edge_event = RisingEdge(self.clock)
while True: while True:
await RisingEdge(self.clock) await clock_edge_event
self.ts_64_fns, self.ts_64_ns = math.modf(get_sim_time('ns')) ts_ns, ts_fns = self.ctx.divmod(Decimal(get_sim_time('fs')).scaleb(-6), Decimal(1))
self.ts_64_ns = int(self.ts_64_ns) self.ts_rel_ns = int(ts_ns.to_integral_value()) & 0xffffffffffff
self.ts_64_fns = int(self.ts_64_fns*0x10000) self.ts_rel_fns = int((ts_fns * Decimal(2**16)).to_integral_value())
self.ts_96_s, self.ts_96_ns = divmod(self.ts_64_ns, 1000000000) ts_s, ts_ns = self.ctx.divmod(ts_ns, Decimal(1000000000))
self.ts_96_fns = self.ts_64_fns
if self.ts_96 is not None: self.ts_tod_s = int(ts_s.scaleb(-9).to_integral_value())
self.ts_96 <= (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns self.ts_tod_ns = int(ts_ns.to_integral_value())
self.ts_tod_fns = self.ts_rel_fns
if self.ts_64 is not None: if self.ts_tod is not None:
self.ts_64 <= (self.ts_64_ns << 16) | self.ts_64_fns self.ts_tod.value = (self.ts_tod_s << 48) | (self.ts_tod_ns << 16) | self.ts_tod_fns
if self.ts_rel is not None:
self.ts_rel.value = (self.ts_rel_ns << 16) | self.ts_rel_fns
if self.pps is not None: if self.pps is not None:
self.pps <= int(self.last_ts_96_s != self.ts_96_s) self.pps.value = int(self.last_ts_tod_s != self.ts_tod_s)
self.last_ts_96_s = self.ts_96_s self.last_ts_tod_s = self.ts_tod_s

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ THE SOFTWARE.
""" """
import cocotb import cocotb
from cocotb.triggers import RisingEdge, FallingEdge from cocotb.triggers import Edge
class Reset: class Reset:
@@ -33,7 +33,7 @@ class Reset:
self._reset_state = True self._reset_state = True
if reset_signal is not None: if reset_signal is not None:
cocotb.fork(self._run_reset(reset_signal, bool(active_level))) cocotb.start_soon(self._run_reset(reset_signal, bool(active_level)))
self._update_reset() self._update_reset()
@@ -56,11 +56,14 @@ class Reset:
async def _run_reset(self, reset_signal, active_level): async def _run_reset(self, reset_signal, active_level):
while True: while True:
if bool(reset_signal.value): await reset_signal.value_change
await FallingEdge(reset_signal) try:
self._ext_reset = not active_level level = bool(int(reset_signal.value))
self._update_reset() except ValueError:
else: continue
await RisingEdge(reset_signal) if level:
self._ext_reset = active_level self._ext_reset = active_level
self._update_reset() self._update_reset()
else:
self._ext_reset = not active_level
self._update_reset()

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -50,7 +50,7 @@ class RgmiiSource(Reset):
self.log.info("RGMII source") self.log.info("RGMII source")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -61,6 +61,7 @@ class RgmiiSource(Reset):
self.current_frame = None self.current_frame = None
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event()
self.ifg = 12 self.ifg = 12
self.mii_mode = False self.mii_mode = False
@@ -90,6 +91,7 @@ class RgmiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
await self.queue.put(frame) await self.queue.put(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -99,6 +101,7 @@ class RgmiiSource(Reset):
frame = GmiiFrame(frame) frame = GmiiFrame(frame)
self.queue.put_nowait(frame) self.queue.put_nowait(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -126,6 +129,7 @@ class RgmiiSource(Reset):
frame.handle_tx_complete() frame.handle_tx_complete()
self.dequeue_event.set() self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -136,12 +140,12 @@ class RgmiiSource(Reset):
if state: if state:
self.log.info("Reset asserted") self.log.info("Reset asserted")
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.cancel()
self._run_cr = None self._run_cr = None
self.active = False self.active = False
self.data <= 0 self.data.value = 0
self.ctrl <= 0 self.ctrl.value = 0
if self.current_frame: if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame) self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
@@ -150,30 +154,51 @@ class RgmiiSource(Reset):
if self.queue.empty(): if self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0 ifg_cnt = 0
in_ifg = False
self.active = False self.active = False
d = 0 d = 0
er = 0 er = 0
en = 0 en = 0
clock_rising_edge_event = RisingEdge(self.clock)
clock_falling_edge_event = FallingEdge(self.clock)
enable_event = None
if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True: while True:
await RisingEdge(self.clock) await clock_falling_edge_event
# send low nibble after falling edge, leading in to rising edge
self.data.value = d & 0x0F
self.ctrl.value = en
await clock_rising_edge_event
# 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.value = d >> 4
self.ctrl <= en ^ er self.ctrl.value = en ^ er
if self.enable is None or int(self.enable.value):
in_ifg = False
if self.enable is None or self.enable.value:
if ifg_cnt > 0: if ifg_cnt > 0:
# in IFG # in IFG
ifg_cnt -= 1 ifg_cnt -= 1
in_ifg = True
elif frame is None and not self.queue.empty(): elif frame is None and not self.queue.empty():
# send frame # send frame
@@ -189,31 +214,36 @@ class RgmiiSource(Reset):
frame.normalize() frame.normalize()
if self.mii_select is not None: if self.mii_select is not None:
self.mii_mode = bool(self.mii_select.value.integer) self.mii_mode = bool(int(self.mii_select.value))
if self.mii_mode: if self.mii_mode:
mii_data = [] # convert to MII
mii_error = [] frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error): for b, e in zip(frame.data, frame.error):
mii_data.append((b & 0x0F)*0x11) frame_data.append((b & 0x0F)*0x11)
mii_data.append((b >> 4)*0x11) frame_data.append((b >> 4)*0x11)
mii_error.append(e) frame_error.append(e)
mii_error.append(e) frame_error.append(e)
frame.data = mii_data else:
frame.error = mii_error frame_data = frame.data
frame_error = frame.error
self.active = True self.active = True
frame_offset = 0
if frame is not None: if frame is not None:
d = frame.data.pop(0) d = frame_data[frame_offset]
er = frame.error.pop(0) er = frame_error[frame_offset]
en = 1 en = 1
frame_offset += 1
if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD, 0xDD): if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD, 0xDD):
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
if not frame.data: if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1) ifg_cnt = max(self.ifg, 1)
in_ifg = True
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
frame = None frame = None
@@ -223,13 +253,14 @@ class RgmiiSource(Reset):
er = 0 er = 0
en = 0 en = 0
self.active = False self.active = False
if not in_ifg and self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
await self.active_event.wait()
await FallingEdge(self.clock) elif self.enable is not None and not self.enable.value:
await enable_event
# send low nibble after falling edge, leading in to rising edge
self.data <= d & 0x0F
self.ctrl <= en
class RgmiiSink(Reset): class RgmiiSink(Reset):
@@ -247,7 +278,7 @@ class RgmiiSink(Reset):
self.log.info("RGMII sink") self.log.info("RGMII sink")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -316,14 +347,14 @@ class RgmiiSink(Reset):
if state: if state:
self.log.info("Reset asserted") self.log.info("Reset asserted")
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.cancel()
self._run_cr = None self._run_cr = None
self.active = False self.active = False
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
@@ -332,20 +363,29 @@ class RgmiiSink(Reset):
dv_val = 0 dv_val = 0
er_val = 0 er_val = 0
clock_rising_edge_event = RisingEdge(self.clock)
clock_falling_edge_event = FallingEdge(self.clock)
active_event = RisingEdge(self.ctrl)
enable_event = None
if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True: while True:
await RisingEdge(self.clock) await clock_rising_edge_event
if self.enable is None or int(self.enable.value):
# capture low nibble on rising edge # capture low nibble on rising edge
d_val = self.data.value.integer d_val = int(self.data.value)
dv_val = self.ctrl.value.integer dv_val = int(self.ctrl.value)
await FallingEdge(self.clock) await clock_falling_edge_event
# capture high nibble on falling edge # capture high nibble on falling edge
d_val |= self.data.value.integer << 4 d_val |= int(self.data.value) << 4
er_val = dv_val ^ self.ctrl.value.integer er_val = dv_val ^ int(self.ctrl.value)
if self.enable is None or self.enable.value:
if frame is None: if frame is None:
if dv_val: if dv_val:
@@ -357,7 +397,7 @@ class RgmiiSink(Reset):
# end of frame # end of frame
if self.mii_select is not None: if self.mii_select is not None:
self.mii_mode = bool(self.mii_select.value.integer) self.mii_mode = bool(int(self.mii_select.value))
if self.mii_mode: if self.mii_mode:
odd = True odd = True
@@ -399,6 +439,12 @@ class RgmiiSink(Reset):
frame.data.append(d_val) frame.data.append(d_val)
frame.error.append(er_val) frame.error.append(er_val)
if not dv_val:
await active_event
elif self.enable is not None and not self.enable.value:
await enable_event
class RgmiiPhy: class RgmiiPhy:
def __init__(self, txd, tx_ctl, tx_clk, rxd, rx_ctl, rx_clk, reset=None, def __init__(self, txd, tx_ctl, tx_clk, rxd, rx_ctl, rx_clk, reset=None,
@@ -427,11 +473,11 @@ class RgmiiPhy:
self._clock_cr.kill() self._clock_cr.kill()
if self.speed == 1000e6: if self.speed == 1000e6:
self._clock_cr = cocotb.fork(self._run_clock(8*1e9/self.speed)) self._clock_cr = cocotb.start_soon(self._run_clock(8*1e9/self.speed))
self.tx.mii_mode = False self.tx.mii_mode = False
self.rx.mii_mode = False self.rx.mii_mode = False
else: else:
self._clock_cr = cocotb.fork(self._run_clock(4*1e9/self.speed)) self._clock_cr = cocotb.start_soon(self._run_clock(4*1e9/self.speed))
self.tx.mii_mode = True self.tx.mii_mode = True
self.rx.mii_mode = True self.rx.mii_mode = True
@@ -441,6 +487,6 @@ class RgmiiPhy:
while True: while True:
await t await t
self.rx_clk <= 1 self.rx_clk.value = 1
await t await t
self.rx_clk <= 0 self.rx_clk.value = 0

View File

@@ -1 +1 @@
__version__ = "0.1.12" __version__ = "0.1.24"

View File

@@ -1,6 +1,6 @@
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,7 +28,7 @@ import zlib
import cocotb import cocotb
from cocotb.queue import Queue, QueueFull from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Timer, First, Event from cocotb.triggers import Edge, 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__
@@ -147,7 +147,7 @@ class XgmiiSource(Reset):
self.log.info("XGMII source") self.log.info("XGMII source")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -158,6 +158,7 @@ class XgmiiSource(Reset):
self.current_frame = None self.current_frame = None
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event()
self.enable_dic = True self.enable_dic = True
self.ifg = 12 self.ifg = 12
@@ -170,14 +171,19 @@ class XgmiiSource(Reset):
self.queue_occupancy_limit_frames = -1 self.queue_occupancy_limit_frames = -1
self.width = len(self.data) self.width = len(self.data)
self.byte_width = len(self.ctrl) self.byte_size = 8
self.byte_lanes = len(self.ctrl)
assert self.width == self.byte_width * 8 assert self.width == self.byte_lanes * self.byte_size
self.log.info("XGMII source model configuration")
self.log.info(" Byte size: %d bits", self.byte_size)
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
self.idle_d = 0 self.idle_d = 0
self.idle_c = 0 self.idle_c = 0
for k in range(self.byte_width): for k in range(self.byte_lanes):
self.idle_d |= XgmiiCtrl.IDLE << k*8 self.idle_d |= XgmiiCtrl.IDLE << k*8
self.idle_c |= 1 << k self.idle_c |= 1 << k
@@ -195,6 +201,7 @@ class XgmiiSource(Reset):
frame = XgmiiFrame(frame) frame = XgmiiFrame(frame)
await self.queue.put(frame) await self.queue.put(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -204,6 +211,7 @@ class XgmiiSource(Reset):
frame = XgmiiFrame(frame) frame = XgmiiFrame(frame)
self.queue.put_nowait(frame) self.queue.put_nowait(frame)
self.idle_event.clear() self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame) self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
@@ -231,6 +239,7 @@ class XgmiiSource(Reset):
frame.handle_tx_complete() frame.handle_tx_complete()
self.dequeue_event.set() self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
@@ -245,8 +254,8 @@ class XgmiiSource(Reset):
self._run_cr = None self._run_cr = None
self.active = False self.active = False
self.data <= 0 self.data.value = 0
self.ctrl <= 0 self.ctrl.value = 0
if self.current_frame: if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame) self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
@@ -255,24 +264,32 @@ class XgmiiSource(Reset):
if self.queue.empty(): if self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
ifg_cnt = 0 ifg_cnt = 0
deficit_idle_cnt = 0 deficit_idle_cnt = 0
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: enable_event = None
if ifg_cnt + deficit_idle_cnt > self.byte_width-1 or (not self.enable_dic and ifg_cnt > 4): if self.enable is not None:
enable_event = RisingEdge(self.enable)
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
if ifg_cnt + deficit_idle_cnt > self.byte_lanes-1 or (not self.enable_dic and ifg_cnt > 4):
# in IFG # in IFG
ifg_cnt = ifg_cnt - self.byte_width ifg_cnt = ifg_cnt - self.byte_lanes
if ifg_cnt < 0: if ifg_cnt < 0:
if self.enable_dic: if self.enable_dic:
deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0) deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0)
@@ -306,7 +323,7 @@ class XgmiiSource(Reset):
else: else:
min_ifg = 0 min_ifg = 0
if self.byte_width > 4 and (ifg_cnt > min_ifg or self.force_offset_start): if self.byte_lanes > 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.start_lane = 4
frame.data = bytearray([XgmiiCtrl.IDLE]*4)+frame.data frame.data = bytearray([XgmiiCtrl.IDLE]*4)+frame.data
@@ -316,6 +333,7 @@ class XgmiiSource(Reset):
deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0) deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0)
ifg_cnt = 0 ifg_cnt = 0
self.active = True self.active = True
frame_offset = 0
else: else:
# clear counters # clear counters
deficit_idle_cnt = 0 deficit_idle_cnt = 0
@@ -325,16 +343,17 @@ class XgmiiSource(Reset):
d_val = 0 d_val = 0
c_val = 0 c_val = 0
for k in range(self.byte_width): for k in range(self.byte_lanes):
if frame is not None: if frame is not None:
d = frame.data.pop(0) d = frame.data[frame_offset]
if frame.sim_time_sfd is None and d == EthPre.SFD: if frame.sim_time_sfd is None and d == EthPre.SFD:
frame.sim_time_sfd = get_sim_time() frame.sim_time_sfd = get_sim_time()
d_val |= d << k*8 d_val |= d << k*8
c_val |= frame.ctrl.pop(0) << k c_val |= frame.ctrl[frame_offset] << k
frame_offset += 1
if not frame.data: if frame_offset >= len(frame.data):
ifg_cnt = max(self.ifg - (self.byte_width-k), 0) ifg_cnt = max(self.ifg - (self.byte_lanes-k), 0)
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
frame = None frame = None
@@ -343,13 +362,20 @@ class XgmiiSource(Reset):
d_val |= XgmiiCtrl.IDLE << k*8 d_val |= XgmiiCtrl.IDLE << k*8
c_val |= 1 << k c_val |= 1 << k
self.data <= d_val self.data.value = d_val
self.ctrl <= c_val self.ctrl.value = c_val
else: else:
self.data <= self.idle_d self.data.value = self.idle_d
self.ctrl <= self.idle_c self.ctrl.value = self.idle_c
self.active = False self.active = False
if ifg_cnt == 0 and self.queue.empty():
self.idle_event.set() self.idle_event.set()
self.active_event.clear()
await self.active_event.wait()
elif self.enable is not None and not self.enable.value:
await enable_event
class XgmiiSink(Reset): class XgmiiSink(Reset):
@@ -364,7 +390,7 @@ class XgmiiSink(Reset):
self.log.info("XGMII sink") self.log.info("XGMII sink")
self.log.info("cocotbext-eth version %s", __version__) self.log.info("cocotbext-eth version %s", __version__)
self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("Copyright (c) 2020-2025 Alex Forencich")
self.log.info("https://github.com/alexforencich/cocotbext-eth") self.log.info("https://github.com/alexforencich/cocotbext-eth")
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -377,9 +403,14 @@ class XgmiiSink(Reset):
self.queue_occupancy_frames = 0 self.queue_occupancy_frames = 0
self.width = len(self.data) self.width = len(self.data)
self.byte_width = len(self.ctrl) self.byte_size = 8
self.byte_lanes = len(self.ctrl)
assert self.width == self.byte_width * 8 assert self.width == self.byte_lanes * self.byte_size
self.log.info("XGMII sink model configuration")
self.log.info(" Byte size: %d bits", self.byte_size)
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
self._run_cr = None self._run_cr = None
@@ -437,19 +468,32 @@ class XgmiiSink(Reset):
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.start_soon(self._run())
async def _run(self): async def _run(self):
frame = None frame = None
self.active = False self.active = False
while True: clock_edge_event = RisingEdge(self.clock)
await RisingEdge(self.clock)
if self.enable is None or self.enable.value: active_event = First(Edge(self.data), Edge(self.ctrl))
for offset in range(self.byte_width):
d_val = (self.data.value.integer >> (offset*8)) & 0xff enable_event = None
c_val = (self.ctrl.value.integer >> offset) & 1 if self.enable is not None:
enable_event = RisingEdge(self.enable)
idle_d = sum([XgmiiCtrl.IDLE << n*8 for n in range(self.byte_lanes)])
idle_c = 2**self.byte_lanes-1
while True:
await clock_edge_event
if self.enable is None or int(self.enable.value):
data_val = int(self.data.value)
ctrl_val = int(self.ctrl.value)
for offset in range(self.byte_lanes):
d_val = (data_val >> (offset*8)) & 0xff
c_val = (ctrl_val >> offset) & 1
if frame is None: if frame is None:
if c_val and d_val == XgmiiCtrl.START: if c_val and d_val == XgmiiCtrl.START:
@@ -482,3 +526,9 @@ class XgmiiSink(Reset):
frame.data.append(d_val) frame.data.append(d_val)
frame.ctrl.append(c_val) frame.ctrl.append(c_val)
if data_val == idle_d and ctrl_val == idle_c:
await active_event
elif self.enable is not None and not self.enable.value:
await enable_event

View File

@@ -13,21 +13,22 @@ project_urls =
Source Code = https://github.com/alexforencich/cocotbext-eth Source Code = https://github.com/alexforencich/cocotbext-eth
download_url = https://github.com/alexforencich/cocotbext-eth/tarball/master download_url = https://github.com/alexforencich/cocotbext-eth/tarball/master
long_description = file: README.md long_description = file: README.md
long-description-content-type = text/markdown long_description_content_type = text/markdown
platforms = any platforms = any
classifiers = classifiers =
Development Status :: 3 - Alpha Development Status :: 3 - Alpha
Programming Language :: Python :: 3 Framework :: cocotb
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Operating System :: OS Independent Operating System :: OS Independent
Programming Language :: Python :: 3
Topic :: Scientific/Engineering :: Electronic Design Automation (EDA) Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
[options] [options]
packages = find_namespace: packages = find_namespace:
python_requires = >=3.6 python_requires = >=3.6
install_requires = install_requires =
cocotb cocotb >= 1.6.0
cocotbext-axi cocotbext-axi >= 0.1.16
[options.extras_require] [options.extras_require]
test = test =
@@ -46,31 +47,41 @@ addopts =
# tox configuration # tox configuration
[tox:tox] [tox:tox]
envlist = py36, py37, py38, py39 envlist = py38, py39, py310, py311, py312, py313
skip_missing_interpreters = true
minversion = 3.18.0
requires = virtualenv >= 16.1
[gh-actions] [gh-actions]
python = python =
3.6: py36
3.7: py37
3.8: py38 3.8: py38
3.9: py39 3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313
[testenv] [testenv]
setenv = setenv =
COVERAGE=1 COVERAGE=1
usedevelop = True
deps = deps =
pytest pytest == 8.3.4
pytest-xdist pytest-xdist == 3.6.1
cocotb-test cocotb == 1.9.2
coverage cocotb-bus == 0.2.1
pytest-cov cocotb-test == 0.2.6
cocotbext-axi == 0.1.26
coverage == 7.0.5
pytest-cov == 4.0.0
commands = commands =
pytest --cov=cocotbext --cov=tests --cov-branch -n auto pytest --cov=cocotbext --cov=tests --cov-branch {posargs:-n auto --verbose}
bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append' bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append'
coverage report
whitelist_externals = allowlist_externals =
bash bash
# combine if paths are different # combine if paths are different

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2021 Alex Forencich # Copyright (c) 2021-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,31 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_eth_mac DUT = test_eth_mac
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
# module parameters
export PARAM_PTP_TS_WIDTH := 96
export PARAM_PTP_TAG_WIDTH := 16
export PARAM_AXIS_DATA_WIDTH := 64
export PARAM_AXIS_KEEP_WIDTH := $(shell expr $(PARAM_AXIS_DATA_WIDTH) / 8 )
export PARAM_AXIS_TX_USER_WIDTH := $(shell expr $(PARAM_PTP_TAG_WIDTH) + 1 )
export PARAM_AXIS_RX_USER_WIDTH := $(shell expr $(PARAM_PTP_TS_WIDTH) + 1 )
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2021 Alex Forencich Copyright (c) 2021-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,6 +28,7 @@ import logging
import os import os
import cocotb_test.simulator import cocotb_test.simulator
import pytest
import cocotb import cocotb
from cocotb.clock import Clock from cocotb.clock import Clock
@@ -45,8 +46,20 @@ class TB:
self.log = logging.getLogger("cocotb.tb") self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.tx_clk, 6.4, units="ns").start()) if len(dut.tx_axis_tdata) == 8:
cocotb.fork(Clock(dut.rx_clk, 6.4, units="ns").start()) clk_period = 8
elif len(dut.tx_axis_tdata) == 32:
clk_period = 3.102
elif len(dut.tx_axis_tdata) == 64:
if speed == 25e9:
clk_period = 2.56
else:
clk_period = 6.206
elif len(dut.tx_axis_tdata) == 512:
clk_period = 3.102
cocotb.start_soon(Clock(dut.tx_clk, clk_period, units="ns").start())
cocotb.start_soon(Clock(dut.rx_clk, clk_period, units="ns").start())
self.mac = EthMac( self.mac = EthMac(
tx_clk=dut.tx_clk, tx_clk=dut.tx_clk,
@@ -54,6 +67,7 @@ class TB:
tx_bus=AxiStreamBus.from_prefix(dut, "tx_axis"), tx_bus=AxiStreamBus.from_prefix(dut, "tx_axis"),
tx_ptp_time=dut.tx_ptp_time, tx_ptp_time=dut.tx_ptp_time,
tx_ptp_ts=dut.tx_ptp_ts, tx_ptp_ts=dut.tx_ptp_ts,
tx_ptp_ts_tag=dut.tx_ptp_ts_tag,
tx_ptp_ts_valid=dut.tx_ptp_ts_valid, tx_ptp_ts_valid=dut.tx_ptp_ts_valid,
rx_clk=dut.rx_clk, rx_clk=dut.rx_clk,
rx_rst=dut.rx_rst, rx_rst=dut.rx_rst,
@@ -63,12 +77,12 @@ class TB:
) )
self.tx_ptp = PtpClockSimTime( self.tx_ptp = PtpClockSimTime(
ts_96=dut.tx_ptp_time, ts_tod=dut.tx_ptp_time,
clock=dut.tx_clk clock=dut.tx_clk
) )
self.rx_ptp = PtpClockSimTime( self.rx_ptp = PtpClockSimTime(
ts_96=dut.rx_ptp_time, ts_tod=dut.rx_ptp_time,
clock=dut.rx_clk clock=dut.rx_clk
) )
@@ -80,12 +94,12 @@ class TB:
self.dut.rx_rst.setimmediatevalue(0) self.dut.rx_rst.setimmediatevalue(0)
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
self.dut.tx_rst <= 1 self.dut.tx_rst.value = 1
self.dut.rx_rst <= 1 self.dut.rx_rst.value = 1
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
self.dut.tx_rst <= 0 self.dut.tx_rst.value = 0
self.dut.rx_rst <= 0 self.dut.rx_rst.value = 0
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
await RisingEdge(self.dut.tx_clk) await RisingEdge(self.dut.tx_clk)
@@ -154,14 +168,23 @@ def incrementing_payload(length):
return bytearray(itertools.islice(itertools.cycle(range(256)), length)) return bytearray(itertools.islice(itertools.cycle(range(256)), length))
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
if len(cocotb.top.tx_axis_tdata) == 8:
speed = [100e6, 1e9]
elif len(cocotb.top.tx_axis_tdata) == 32:
speed = [10e9]
elif len(cocotb.top.tx_axis_tdata) == 64:
speed = [10e9, 25e9]
elif len(cocotb.top.tx_axis_tdata) == 512:
speed = [100e9]
for test in [run_test_tx, run_test_rx]: for test in [run_test_tx, run_test_rx]:
factory = TestFactory(test) factory = TestFactory(test)
factory.add_option("payload_lengths", [size_list]) factory.add_option("payload_lengths", [size_list])
factory.add_option("payload_data", [incrementing_payload]) factory.add_option("payload_data", [incrementing_payload])
factory.add_option("speed", [10e9, 1e9]) factory.add_option("speed", speed)
factory.generate_tests() factory.generate_tests()
@@ -170,7 +193,8 @@ if cocotb.SIM_NAME:
tests_dir = os.path.dirname(__file__) tests_dir = os.path.dirname(__file__)
def test_eth_mac(request): @pytest.mark.parametrize("data_width", [8, 32, 64, 512])
def test_eth_mac(request, data_width):
dut = "test_eth_mac" dut = "test_eth_mac"
module = os.path.splitext(os.path.basename(__file__))[0] module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut toplevel = dut
@@ -181,6 +205,13 @@ def test_eth_mac(request):
parameters = {} parameters = {}
parameters['PTP_TS_WIDTH'] = 96
parameters['PTP_TAG_WIDTH'] = 16
parameters['AXIS_DATA_WIDTH'] = data_width
parameters['AXIS_KEEP_WIDTH'] = parameters['AXIS_DATA_WIDTH'] // 8
parameters['AXIS_TX_USER_WIDTH'] = parameters['PTP_TAG_WIDTH']+1
parameters['AXIS_RX_USER_WIDTH'] = parameters['PTP_TS_WIDTH']+1
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", sim_build = os.path.join(tests_dir, "sim_build",

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2021 Alex Forencich Copyright (c) 2021-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -29,28 +29,37 @@ THE SOFTWARE.
/* /*
* Ethernet MAC model test * Ethernet MAC model test
*/ */
module test_eth_mac module test_eth_mac #
(
parameter PTP_TS_WIDTH = 96,
parameter PTP_TAG_WIDTH = 16,
parameter AXIS_DATA_WIDTH = 64,
parameter AXIS_KEEP_WIDTH = (AXIS_DATA_WIDTH/8),
parameter AXIS_TX_USER_WIDTH = PTP_TAG_WIDTH+1,
parameter AXIS_RX_USER_WIDTH = PTP_TS_WIDTH+1
)
( (
inout wire tx_clk, inout wire tx_clk,
inout wire tx_rst, inout wire tx_rst,
inout wire [63:0] tx_axis_tdata, inout wire [AXIS_DATA_WIDTH-1:0] tx_axis_tdata,
inout wire [7:0] tx_axis_tkeep, inout wire [AXIS_KEEP_WIDTH-1:0] tx_axis_tkeep,
inout wire tx_axis_tlast, inout wire tx_axis_tlast,
inout wire tx_axis_tuser, inout wire [AXIS_TX_USER_WIDTH-1:0] tx_axis_tuser,
inout wire tx_axis_tvalid, inout wire tx_axis_tvalid,
inout wire tx_axis_tready, inout wire tx_axis_tready,
inout wire [95:0] tx_ptp_time, inout wire [PTP_TS_WIDTH-1:0] tx_ptp_time,
inout wire [95:0] tx_ptp_ts, inout wire [PTP_TS_WIDTH-1:0] tx_ptp_ts,
inout wire [PTP_TAG_WIDTH-1:0] tx_ptp_ts_tag,
inout wire tx_ptp_ts_valid, inout wire tx_ptp_ts_valid,
inout wire rx_clk, inout wire rx_clk,
inout wire rx_rst, inout wire rx_rst,
inout wire [63:0] rx_axis_tdata, inout wire [AXIS_DATA_WIDTH-1:0] rx_axis_tdata,
inout wire [7:0] rx_axis_tkeep, inout wire [AXIS_KEEP_WIDTH-1:0] rx_axis_tkeep,
inout wire rx_axis_tlast, inout wire rx_axis_tlast,
inout wire [96:0] rx_axis_tuser, inout wire [AXIS_RX_USER_WIDTH-1:0] rx_axis_tuser,
inout wire rx_axis_tvalid, inout wire rx_axis_tvalid,
inout wire [95:0] rx_ptp_time inout wire [PTP_TS_WIDTH-1:0] rx_ptp_time
); );
endmodule endmodule

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_gmii DUT = test_gmii
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = None self._enable_generator = None
self._enable_cr = None self._enable_cr = None
cocotb.fork(Clock(dut.clk, 2, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
self.source = GmiiSource(dut.gmii_d, dut.gmii_er, dut.gmii_en, self.source = GmiiSource(dut.gmii_d, dut.gmii_er, dut.gmii_en,
dut.clk, dut.rst, dut.gmii_clk_en, dut.gmii_mii_sel) dut.clk, dut.rst, dut.gmii_clk_en, dut.gmii_mii_sel)
@@ -61,10 +61,10 @@ class TB:
self.dut.rst.setimmediatevalue(0) self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 1 self.dut.rst.value = 1
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 0 self.dut.rst.value = 0
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
@@ -76,15 +76,17 @@ class TB:
self._enable_generator = generator self._enable_generator = generator
if self._enable_generator is not None: if self._enable_generator is not None:
self._enable_cr = cocotb.fork(self._run_enable()) self._enable_cr = cocotb.start_soon(self._run_enable())
def clear_enable_generator(self): def clear_enable_generator(self):
self.set_enable_generator(None) self.set_enable_generator(None)
async def _run_enable(self): async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator: for val in self._enable_generator:
self.dut.gmii_clk_en <= val self.dut.gmii_clk_en.value = val
await RisingEdge(self.dut.clk) await clock_edge_event
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False):
@@ -92,7 +94,7 @@ async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_
tb = TB(dut) tb = TB(dut)
tb.source.ifg = ifg tb.source.ifg = ifg
tb.dut.gmii_mii_sel <= mii_sel tb.dut.gmii_mii_sel.value = mii_sel
if enable_gen is not None: if enable_gen is not None:
tb.set_enable_generator(enable_gen()) tb.set_enable_generator(enable_gen())
@@ -130,7 +132,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test) factory = TestFactory(run_test)
factory.add_option("payload_lengths", [size_list]) factory.add_option("payload_lengths", [size_list])

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_gmii_phy DUT = test_gmii_phy
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -44,7 +44,7 @@ class TB:
self.log = logging.getLogger("cocotb.tb") self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.phy_gtx_clk, 8, units="ns").start()) cocotb.start_soon(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, 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) dut.phy_rxd, dut.phy_rx_er, dut.phy_rx_dv, dut.phy_rx_clk, dut.phy_rst, speed=speed)
@@ -64,10 +64,10 @@ class TB:
self.dut.phy_rst.setimmediatevalue(0) self.dut.phy_rst.setimmediatevalue(0)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 1 self.dut.phy_rst.value = 1
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 0 self.dut.phy_rst.value = 0
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
@@ -140,7 +140,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
for test in [run_test_tx, run_test_rx]: for test in [run_test_tx, run_test_rx]:

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_mii DUT = test_mii
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = None self._enable_generator = None
self._enable_cr = None self._enable_cr = None
cocotb.fork(Clock(dut.clk, 2, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
self.source = MiiSource(dut.mii_d, dut.mii_er, dut.mii_en, self.source = MiiSource(dut.mii_d, dut.mii_er, dut.mii_en,
dut.clk, dut.rst, dut.mii_clk_en) dut.clk, dut.rst, dut.mii_clk_en)
@@ -60,10 +60,10 @@ class TB:
self.dut.rst.setimmediatevalue(0) self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 1 self.dut.rst.value = 1
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 0 self.dut.rst.value = 0
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
@@ -75,15 +75,17 @@ class TB:
self._enable_generator = generator self._enable_generator = generator
if self._enable_generator is not None: if self._enable_generator is not None:
self._enable_cr = cocotb.fork(self._run_enable()) self._enable_cr = cocotb.start_soon(self._run_enable())
def clear_enable_generator(self): def clear_enable_generator(self):
self.set_enable_generator(None) self.set_enable_generator(None)
async def _run_enable(self): async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator: for val in self._enable_generator:
self.dut.mii_clk_en <= val self.dut.mii_clk_en.value = val
await RisingEdge(self.dut.clk) await clock_edge_event
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None): async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None):
@@ -128,7 +130,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test) factory = TestFactory(run_test)
factory.add_option("payload_lengths", [size_list]) factory.add_option("payload_lengths", [size_list])

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020 Alex-2025 Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_mii_phy DUT = test_mii_phy
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -55,10 +55,10 @@ class TB:
self.dut.phy_rst.setimmediatevalue(0) self.dut.phy_rst.setimmediatevalue(0)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 1 self.dut.phy_rst.value = 1
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 0 self.dut.phy_rst.value = 0
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
@@ -131,7 +131,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
for test in [run_test_tx, run_test_rx]: for test in [run_test_tx, run_test_rx]:

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps COCOTB_HDL_TIMEPRECISION = 1ps
DUT = test_ptp_clock DUT = test_ptp_clock
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -25,12 +25,13 @@ THE SOFTWARE.
import logging import logging
import os import os
from decimal import Decimal
import cocotb_test.simulator import cocotb_test.simulator
import cocotb import cocotb
from cocotb.clock import Clock from cocotb.clock import Clock
from cocotb.triggers import RisingEdge from cocotb.triggers import RisingEdge, ClockCycles
from cocotb.utils import get_sim_time from cocotb.utils import get_sim_time
from cocotbext.eth import PtpClock from cocotbext.eth import PtpClock
@@ -43,11 +44,11 @@ class TB:
self.log = logging.getLogger("cocotb.tb") self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.clk, 6.4, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
self.ptp_clock = PtpClock( self.ptp_clock = PtpClock(
ts_96=dut.ts_96, ts_tod=dut.ts_tod,
ts_64=dut.ts_64, ts_rel=dut.ts_rel,
ts_step=dut.ts_step, ts_step=dut.ts_step,
pps=dut.pps, pps=dut.pps,
clock=dut.clk, clock=dut.clk,
@@ -59,13 +60,21 @@ class TB:
self.dut.rst.setimmediatevalue(0) self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 1 self.dut.rst.value = 1
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 0 self.dut.rst.value = 0
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
def get_ts_tod_ns(self):
ts = int(self.dut.ts_tod.value)
return Decimal(ts >> 48).scaleb(9) + (Decimal(ts & 0xffffffffffff) / Decimal(2**16))
def get_ts_rel_ns(self):
ts = int(self.dut.ts_rel.value)
return Decimal(ts) / Decimal(2**16)
@cocotb.test() @cocotb.test()
async def run_default_rate(dut): async def run_default_rate(dut):
@@ -75,32 +84,32 @@ async def run_default_rate(dut):
await tb.reset() await tb.reset()
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
for k in range(10000): await ClockCycles(dut.clk, 10000)
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
@@ -113,41 +122,41 @@ async def run_load_timestamps(dut):
await tb.reset() await tb.reset()
tb.ptp_clock.set_ts_96(12345678) tb.ptp_clock.set_ts_tod_ns(12345678)
tb.ptp_clock.set_ts_64(12345678) tb.ptp_clock.set_ts_rel_ns(12345678)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
assert dut.ts_96.value.integer == 12345678+((tb.ptp_clock.period_ns << 16) + tb.ptp_clock.period_fns) assert int(dut.ts_tod.value) == (12345678 << 16) + (tb.ptp_clock.period_ns << 16) + (tb.ptp_clock.period_fns >> 16)
assert dut.ts_64.value.integer == 12345678+((tb.ptp_clock.period_ns << 16) + tb.ptp_clock.period_fns) assert int(dut.ts_rel.value) == (12345678 << 16) + (tb.ptp_clock.period_ns << 16) + (tb.ptp_clock.period_fns >> 16)
assert dut.ts_step.value.integer == 1 assert int(dut.ts_step.value) == 1
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
for k in range(2000): await ClockCycles(dut.clk, 2000)
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
@@ -160,47 +169,48 @@ async def run_seconds_increment(dut):
await tb.reset() await tb.reset()
tb.ptp_clock.set_ts_96(999990000*2**16) tb.ptp_clock.set_ts_tod_ns(999990000)
tb.ptp_clock.set_ts_64(999990000*2**16) tb.ptp_clock.set_ts_rel_ns(999990000)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
saw_pps = False saw_pps = False
for k in range(3000): for k in range(3000):
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
if dut.pps.value.integer: if int(dut.pps.value):
saw_pps = True saw_pps = True
assert dut.ts_96.value.integer >> 48 == 1 assert int(dut.ts_tod.value) >> 48 == 1
assert dut.ts_96.value.integer & 0xffffffffffff < 10*2**16 assert int(dut.ts_tod.value) & 0xffffffffffff < 10*2**16
assert saw_pps assert saw_pps
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
@@ -213,36 +223,35 @@ async def run_frequency_adjustment(dut):
await tb.reset() await tb.reset()
tb.ptp_clock.period_ns = 0x6 tb.ptp_clock.set_period(0x6, 0x66240000)
tb.ptp_clock.period_fns = 0x6624
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
for k in range(10000): await ClockCycles(dut.clk, 10000)
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta * 6.4/(6+(0x6624+2/5)/2**16) tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta * 6.4/(6+(0x6624+2/5)/2**16) tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4)/tb.ptp_clock.get_period_ns()
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4)/tb.ptp_clock.get_period_ns()
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
@@ -255,37 +264,35 @@ async def run_drift_adjustment(dut):
await tb.reset() await tb.reset()
tb.ptp_clock.drift_ns = 0 tb.ptp_clock.set_drift(20000, 5)
tb.ptp_clock.drift_fns = 20
tb.ptp_clock.drift_rate = 5
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
for k in range(10000): await ClockCycles(dut.clk, 10000)
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta * 6.4/(6+(0x6666+20/5)/2**16) tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta * 6.4/(6+(0x6666+20/5)/2**16) tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta * Decimal(6.4)/tb.ptp_clock.get_period_ns()
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta * Decimal(6.4)/tb.ptp_clock.get_period_ns()
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -34,8 +34,8 @@ module test_ptp_clock
input wire clk, input wire clk,
input wire rst, input wire rst,
inout wire [95:0] ts_96, inout wire [95:0] ts_tod,
inout wire [63:0] ts_64, inout wire [63:0] ts_rel,
inout wire ts_step, inout wire ts_step,
inout wire pps inout wire pps
); );

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps COCOTB_HDL_TIMEPRECISION = 1ps
DUT = test_ptp_clock_sim_time DUT = test_ptp_clock_sim_time
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2021 Alex Forencich Copyright (c) 2021-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -25,12 +25,13 @@ THE SOFTWARE.
import logging import logging
import os import os
from decimal import Decimal
import cocotb_test.simulator import cocotb_test.simulator
import cocotb import cocotb
from cocotb.clock import Clock from cocotb.clock import Clock
from cocotb.triggers import RisingEdge from cocotb.triggers import RisingEdge, ClockCycles
from cocotb.utils import get_sim_time from cocotb.utils import get_sim_time
from cocotbext.eth import PtpClockSimTime from cocotbext.eth import PtpClockSimTime
@@ -43,15 +44,23 @@ class TB:
self.log = logging.getLogger("cocotb.tb") self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.clk, 6.4, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 6.4, units="ns").start())
self.ptp_clock = PtpClockSimTime( self.ptp_clock = PtpClockSimTime(
ts_96=dut.ts_96, ts_tod=dut.ts_tod,
ts_64=dut.ts_64, ts_rel=dut.ts_rel,
pps=dut.pps, pps=dut.pps,
clock=dut.clk clock=dut.clk
) )
def get_ts_tod_ns(self):
ts = int(self.dut.ts_tod.value)
return Decimal(ts >> 48).scaleb(9) + (Decimal(ts & 0xffffffffffff) / Decimal(2**16))
def get_ts_rel_ns(self):
ts = int(self.dut.ts_rel.value)
return Decimal(ts) / Decimal(2**16)
@cocotb.test() @cocotb.test()
async def run_test(dut): async def run_test(dut):
@@ -62,32 +71,32 @@ async def run_test(dut):
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
start_time = get_sim_time('sec') start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) start_ts_tod = tb.get_ts_tod_ns()
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9 start_ts_rel = tb.get_ts_rel_ns()
for k in range(10000): await ClockCycles(dut.clk, 10000)
await RisingEdge(dut.clk)
stop_time = get_sim_time('sec') stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9) stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9 stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96 ts_tod_delta = stop_ts_tod-start_ts_tod
ts_64_delta = stop_ts_64-start_ts_64 ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta tb.log.info("sim time delta : %s ns", time_delta)
ts_64_diff = time_delta - ts_64_delta tb.log.info("ToD ts delta : %s ns", ts_tod_delta)
tb.log.info("rel ts delta : %s ns", ts_rel_delta)
tb.log.info("sim time delta : %g s", time_delta) ts_tod_diff = time_delta - ts_tod_delta
tb.log.info("96 bit ts delta : %g s", ts_96_delta) ts_rel_diff = time_delta - ts_rel_delta
tb.log.info("64 bit ts delta : %g s", ts_64_delta)
tb.log.info("96 bit ts diff : %g s", ts_96_diff)
tb.log.info("64 bit ts diff : %g s", ts_64_diff)
assert abs(ts_96_diff) < 1e-12 tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
assert abs(ts_64_diff) < 1e-12 tb.log.info("rel ts diff : %s ns", ts_rel_diff)
assert abs(ts_tod_diff) < 1e-3
assert abs(ts_rel_diff) < 1e-3
await RisingEdge(dut.clk) await RisingEdge(dut.clk)
await RisingEdge(dut.clk) await RisingEdge(dut.clk)

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2021 Alex Forencich Copyright (c) 2021-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -33,8 +33,8 @@ module test_ptp_clock_sim_time
( (
input wire clk, input wire clk,
inout wire [95:0] ts_96, inout wire [95:0] ts_tod,
inout wire [63:0] ts_64, inout wire [63:0] ts_rel,
inout wire pps inout wire pps
); );

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_rgmii DUT = test_rgmii
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = None self._enable_generator = None
self._enable_cr = None self._enable_cr = None
cocotb.fork(Clock(dut.clk, 2, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
self.source = RgmiiSource(dut.rgmii_d, dut.rgmii_ctl, dut.clk, dut.rst, dut.rgmii_clk_en, dut.rgmii_mii_sel) self.source = RgmiiSource(dut.rgmii_d, dut.rgmii_ctl, dut.clk, dut.rst, dut.rgmii_clk_en, dut.rgmii_mii_sel)
self.sink = RgmiiSink(dut.rgmii_d, dut.rgmii_ctl, dut.clk, dut.rst, dut.rgmii_clk_en, dut.rgmii_mii_sel) self.sink = RgmiiSink(dut.rgmii_d, dut.rgmii_ctl, dut.clk, dut.rst, dut.rgmii_clk_en, dut.rgmii_mii_sel)
@@ -59,10 +59,10 @@ class TB:
self.dut.rst.setimmediatevalue(0) self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 1 self.dut.rst.value = 1
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 0 self.dut.rst.value = 0
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
@@ -74,15 +74,17 @@ class TB:
self._enable_generator = generator self._enable_generator = generator
if self._enable_generator is not None: if self._enable_generator is not None:
self._enable_cr = cocotb.fork(self._run_enable()) self._enable_cr = cocotb.start_soon(self._run_enable())
def clear_enable_generator(self): def clear_enable_generator(self):
self.set_enable_generator(None) self.set_enable_generator(None)
async def _run_enable(self): async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator: for val in self._enable_generator:
self.dut.rgmii_clk_en <= val self.dut.rgmii_clk_en.value = val
await RisingEdge(self.dut.clk) await clock_edge_event
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False): async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_gen=None, mii_sel=False):
@@ -90,7 +92,7 @@ async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_
tb = TB(dut) tb = TB(dut)
tb.source.ifg = ifg tb.source.ifg = ifg
tb.dut.rgmii_mii_sel <= mii_sel tb.dut.rgmii_mii_sel.value = mii_sel
if enable_gen is not None: if enable_gen is not None:
tb.set_enable_generator(enable_gen()) tb.set_enable_generator(enable_gen())
@@ -128,7 +130,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test) factory = TestFactory(run_test)
factory.add_option("payload_lengths", [size_list]) factory.add_option("payload_lengths", [size_list])

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_rgmii_phy DUT = test_rgmii_phy
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
ifeq ($(WAVES), 1) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -45,11 +45,11 @@ class TB:
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
if speed == 1000e6: if speed == 1000e6:
cocotb.fork(Clock(dut.phy_tx_clk, 8, units="ns").start()) cocotb.start_soon(Clock(dut.phy_tx_clk, 8, units="ns").start())
elif speed == 100e6: elif speed == 100e6:
cocotb.fork(Clock(dut.phy_tx_clk, 40, units="ns").start()) cocotb.start_soon(Clock(dut.phy_tx_clk, 40, units="ns").start())
elif speed == 10e6: elif speed == 10e6:
cocotb.fork(Clock(dut.phy_tx_clk, 400, units="ns").start()) cocotb.start_soon(Clock(dut.phy_tx_clk, 400, units="ns").start())
self.rgmii_phy = RgmiiPhy(dut.phy_txd, dut.phy_tx_ctl, dut.phy_tx_clk, 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) dut.phy_rxd, dut.phy_rx_ctl, dut.phy_rx_clk, dut.phy_rst, speed=speed)
@@ -68,10 +68,10 @@ class TB:
self.dut.phy_rst.setimmediatevalue(0) self.dut.phy_rst.setimmediatevalue(0)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 1 self.dut.phy_rst.value = 1
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 0 self.dut.phy_rst.value = 0
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk) await RisingEdge(self.dut.phy_tx_clk)
@@ -144,7 +144,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
for test in [run_test_tx, run_test_rx]: for test in [run_test_tx, run_test_rx]:

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,4 @@
# Copyright (c) 2020 Alex Forencich # Copyright (c) 2020-2025 Alex Forencich
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -27,45 +27,27 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_xgmii DUT = test_xgmii
TOPLEVEL = $(DUT) COCOTB_TEST_MODULES = $(DUT)
MODULE = $(DUT) COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v VERILOG_SOURCES += $(DUT).v
# module parameters # module parameters
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 )
ifeq ($(SIM), icarus) ifeq ($(SIM), icarus)
PLUSARGS += -fst PLUSARGS += -fst
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH)
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH)
ifeq ($(WAVES), 1) ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif endif
endif endif
include $(shell cocotb-config --makefiles)/Makefile.sim 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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -48,7 +48,7 @@ class TB:
self._enable_generator = None self._enable_generator = None
self._enable_cr = None self._enable_cr = None
cocotb.fork(Clock(dut.clk, 2, units="ns").start()) cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
self.source = XgmiiSource(dut.xgmii_d, dut.xgmii_c, dut.clk, dut.rst, dut.xgmii_clk_en) self.source = XgmiiSource(dut.xgmii_d, dut.xgmii_c, dut.clk, dut.rst, dut.xgmii_clk_en)
self.sink = XgmiiSink(dut.xgmii_d, dut.xgmii_c, dut.clk, dut.rst, dut.xgmii_clk_en) self.sink = XgmiiSink(dut.xgmii_d, dut.xgmii_c, dut.clk, dut.rst, dut.xgmii_clk_en)
@@ -59,10 +59,10 @@ class TB:
self.dut.rst.setimmediatevalue(0) self.dut.rst.setimmediatevalue(0)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 1 self.dut.rst.value = 1
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
self.dut.rst <= 0 self.dut.rst.value = 0
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk) await RisingEdge(self.dut.clk)
@@ -74,15 +74,17 @@ class TB:
self._enable_generator = generator self._enable_generator = generator
if self._enable_generator is not None: if self._enable_generator is not None:
self._enable_cr = cocotb.fork(self._run_enable()) self._enable_cr = cocotb.start_soon(self._run_enable())
def clear_enable_generator(self): def clear_enable_generator(self):
self.set_enable_generator(None) self.set_enable_generator(None)
async def _run_enable(self): async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator: for val in self._enable_generator:
self.dut.xgmii_clk_en <= val self.dut.xgmii_clk_en.value = val
await RisingEdge(self.dut.clk) await clock_edge_event
async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_dic=True, async def run_test(dut, payload_lengths=None, payload_data=None, ifg=12, enable_dic=True,
@@ -123,7 +125,7 @@ async def run_test_alignment(dut, payload_data=None, ifg=12, enable_dic=True,
tb = TB(dut) tb = TB(dut)
byte_width = tb.source.width // 8 byte_lanes = tb.source.byte_lanes
tb.source.ifg = ifg tb.source.ifg = ifg
tb.source.enable_dic = enable_dic tb.source.enable_dic = enable_dic
@@ -164,23 +166,23 @@ 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:
if ifg == 0: if ifg == 0:
lane = 0 lane = 0
if force_offset_start and byte_width > 4: if force_offset_start and byte_lanes > 4:
lane = 4 lane = 4
start_lane_ref.append(lane) start_lane_ref.append(lane)
lane = (lane + len(test_data)+4+ifg) % byte_width lane = (lane + len(test_data)+4+ifg) % byte_lanes
if enable_dic: if enable_dic:
offset = lane % 4 offset = lane % 4
if deficit_idle_count+offset >= 4: if deficit_idle_count+offset >= 4:
offset += 4 offset += 4
lane = (lane - offset) % byte_width lane = (lane - offset) % byte_lanes
deficit_idle_count = (deficit_idle_count + offset) % 4 deficit_idle_count = (deficit_idle_count + offset) % 4
else: else:
offset = lane % 4 offset = lane % 4
if offset > 0: if offset > 0:
offset += 4 offset += 4
lane = (lane - offset) % byte_width lane = (lane - offset) % byte_lanes
tb.log.info("start_lane_ref: %s", start_lane_ref) tb.log.info("start_lane_ref: %s", start_lane_ref)
@@ -206,7 +208,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1]) return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME: if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test) factory = TestFactory(run_test)
factory.add_option("payload_lengths", [size_list]) factory.add_option("payload_lengths", [size_list])

View File

@@ -1,6 +1,6 @@
/* /*
Copyright (c) 2020 Alex Forencich Copyright (c) 2020-2025 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal