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:
build:
name: Python ${{matrix.python-version}}
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
strategy:
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:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
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
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
[![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)
[![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)
@@ -30,7 +30,7 @@ Installation for active development:
## 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
@@ -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_ptp_time=dut.tx_ptp_time,
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,
rx_clk=dut.rx_clk,
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()
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
* `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)
* `tlast`: marks the last cycle of a frame
* `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
* `ptp_time`: PTP time input from PHC, captured into `ptp_timestamp` field coincident with transfer of frame SFD and output on `ptp_ts`
* `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` (TX) or `tuser` (RX)
* `ptp_ts`: captured transmit PTP timestamp
* `ptp_ts_tag`: captured transmit PTP timestamp tag
* `ptp_ts_valid`: qualifies captured transmit PTP timestamp
#### Constructor parameters (`EthMacRx` and `EthMacTx`):
@@ -510,7 +514,8 @@ To receive data, call `recv()` or `recv_nowait()`. Optionally call `wait()` to
* _clock_: clock signal
* _reset_: reset signal (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)
* _reset_active_level_: reset active level (optional, default `True`)
* _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_rst_: transmit reset (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)
* _rx_bus_: `AxiStreamBus` object containing receive AXI stream interface signals
* _rx_clk_: receive clock
@@ -565,6 +571,7 @@ Attributes:
* `sim_time_start`: simulation time of first transfer cycle of frame.
* `sim_time_sfd`: simulation time at which the SFD was transferred.
* `sim_time_end`: simulation time of last transfer cycle of frame.
* `ptp_tag`: PTP timestamp tag for transmitted frames.
* `ptp_timestamp`: captured value of `ptp_time` at frame SFD
* `tx_complete`: event or callable triggered when frame is transmitted.
@@ -578,15 +585,15 @@ Methods:
### 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:
from cocotbext.eth import PtpClock
ptp_clock = PtpClock(
ts_96=dut.ts_96,
ts_64=dut.ts_64,
ts_tod=dut.ts_tod,
ts_rel=dut.ts_rel,
ts_step=dut.ts_step,
pps=dut.pps,
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.
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
* `ts_96`: 96-bit timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns)
* `ts_64`: 64-bit timestamp (48 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_rel`: 64-bit relative timestamp (48 bit ns, 16 bit fractional ns)
* `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:
* _ts_96_: 96-bit timestamp signal (optional)
* _ts_64_: 64-bit timestamp signal (optional)
* _ts_tod_: 96-bit time-of-day timestamp signal (optional)
* _ts_rel_: 64-bit relative timestamp signal (optional)
* _ts_step_: timestamp step signal (optional)
* _pps_: pulse-per-second signal (optional)
* _clock_: clock
@@ -616,74 +627,84 @@ Once the clock is instantiated, it will generate a continuous stream of monotoni
#### Attributes:
* _ts_96_s_: current 96-bit timestamp seconds field
* _ts_96_ns_: current 96-bit timestamp ns field
* _ts_96_fns_: current 96-bit timestamp fractional ns field
* _ts_64_ns_: current 64-bit timestamp ns field
* _ts_64_fns_: current 64-bit timestamp fractional ns field
* _ts_tod_s_: current 96-bit ToD timestamp seconds field
* _ts_tod_ns_: current 96-bit ToD timestamp ns field
* _ts_tod_fns_: current 96-bit ToD timestamp fractional ns field
* _ts_rel_ns_: current 64-bit relative timestamp ns field
* _ts_rel_fns_: current 64-bit relative timestamp fractional ns field
#### Methods
* `set_period(ns, fns)`: set clock period from separate fields
* `set_drift(ns, fns, rate)`: set clock drift from separate fields
* `set_period_ns(t)`: set clock period in ns (float)
* `get_period_ns()`: return current clock period in ns (float)
* `set_ts_96(ts_s, ts_ns=None, ts_fns=None)`: set 96-bit timestamp from integer or from separate fields
* `set_ts_96_ns(t)`: set 96-bit timestamp from ns (float)
* `set_ts_96_s(t)`: set 96-bit timestamp from seconds (float)
* `get_ts_96()`: return current 96-bit timestamp as an integer
* `get_ts_96_ns()`: return current 96-bit timestamp in ns (float)
* `get_ts_96_s()`: return current 96-bit timestamp in seconds (float)
* `set_ts_64(ts_ns, ts_fns=None)`: set 64-bit timestamp from integer or from separate fields
* `set_ts_64_ns(t)`: set 64-bit timestamp from ns (float)
* `set_ts_64_s(t)`: set 64-bit timestamp from seconds (float)
* `get_ts_64()`: return current 64-bit timestamp as an integer
* `get_ts_64_ns()`: return current 64-bit timestamp in ns (float)
* `get_ts_64_s()`: return current 64-bit timestamp in seconds (float)
* `set_drift(num, denom)`: set clock drift from separate fields
* `set_period_ns(t)`: set clock period and drift in ns (Decimal)
* `get_period_ns()`: return current clock period in ns (Decimal)
* `set_ts_tod(ts_s, ts_ns, ts_fns)`: set 96-bit ToD timestamp from separate fields
* `set_ts_tod_96(ts)`: set 96-bit ToD timestamp from integer
* `set_ts_tod_ns(t)`: set 96-bit ToD timestamp from ns (Decimal)
* `set_ts_tod_s(t)`: set 96-bit ToD timestamp from seconds (Decimal)
* `set_ts_tod_sim_time()`: set 96-bit ToD timestamp from sim time
* `get_ts_tod()`: return current 96-bit ToD timestamp as separate fields
* `get_ts_tod_96()`: return current 96-bit ToD timestamp as an integer
* `get_ts_tod_ns()`: return current 96-bit ToD timestamp in ns (Decimal)
* `get_ts_tod_s()`: return current 96-bit ToD timestamp in seconds (Decimal)
* `set_ts_rel(ts_ns, ts_fns)`: set 64-bit relative timestamp from separate fields
* `set_ts_rel_64(ts)`: set 64-bit relative timestamp from integer
* `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)
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:
from cocotbext.eth import PtpClockSimTime
ptp_clock = PtpClockSimTime(
ts_96=dut.ts_96,
ts_64=dut.ts_64,
ts_tod=dut.ts_tod,
ts_rel=dut.ts_rel,
pps=dut.pps,
clock=dut.clk
)
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
* `ts_96`: 96-bit timestamp (48 bit seconds, 32 bit ns, 16 bit fractional ns)
* `ts_64`: 64-bit timestamp (48 bit ns, 16 bit fractional ns)
* `pps`: pulse-per-second output, pulsed when ts_96 seconds field increments
* `ts_tod`: 96-bit time-of-day timestamp (48 bit seconds, 32 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_tod seconds field increments
#### Constructor parameters:
* _ts_96_: 96-bit timestamp signal (optional)
* _ts_64_: 64-bit timestamp signal (optional)
* _ts_tod_: 96-bit time-of-day timestamp signal (optional)
* _ts_rel_: 64-bit relative timestamp signal (optional)
* _pps_: pulse-per-second signal (optional)
* _clock_: clock
#### Attributes:
* _ts_96_s_: current 96-bit timestamp seconds field
* _ts_96_ns_: current 96-bit timestamp ns field
* _ts_96_fns_: current 96-bit timestamp fractional ns field
* _ts_64_ns_: current 64-bit timestamp ns field
* _ts_64_fns_: current 64-bit timestamp fractional ns field
* _ts_tod_s_: current 96-bit ToD timestamp seconds field
* _ts_tod_ns_: current 96-bit ToD timestamp ns field
* _ts_tod_fns_: current 96-bit ToD timestamp fractional ns field
* _ts_rel_ns_: current 64-bit relative timestamp ns field
* _ts_rel_fns_: current 64-bit relative timestamp fractional ns field
#### Methods
* `get_ts_96()`: return current 96-bit timestamp as an integer
* `get_ts_96_ns()`: return current 96-bit timestamp in ns (float)
* `get_ts_96_s()`: return current 96-bit timestamp in seconds (float)
* `get_ts_64()`: return current 64-bit timestamp as an integer
* `get_ts_64_ns()`: return current 64-bit timestamp in ns (float)
* `get_ts_64_s()`: return current 64-bit timestamp in seconds (float)
* `get_ts_tod()`: return current 96-bit ToD timestamp as separate fields
* `get_ts_tod_96()`: return current 96-bit ToD timestamp as an integer
* `get_ts_tod_ns()`: return current 96-bit ToD timestamp in ns (Decimal)
* `get_ts_tod_s()`: return current 96-bit ToD timestamp in seconds (Decimal)
* `get_ts_rel()`: return current 64-bit relative timestamp as separate fields
* `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
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
of this software and associated documentation files (the "Software"), to deal
@@ -42,12 +42,12 @@ class XgmiiCtrl(enum.IntEnum):
TERM = 0xfd
ERROR = 0xfe
SEQ_OS = 0x9c
RES0 = 0x1c
RES1 = 0x3c
RES2 = 0x7c
RES3 = 0xbc
RES4 = 0xdc
RES5 = 0xf7
RES_0 = 0x1c
RES_1 = 0x3c
RES_2 = 0x7c
RES_3 = 0xbc
RES_4 = 0xdc
RES_5 = 0xf7
SIG_OS = 0x5c
@@ -93,3 +93,41 @@ class BaseRBlockType(enum.IntEnum):
TERM_5 = 0xd2 # C7 C6 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
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
of this software and associated documentation files (the "Software"), to deal
@@ -43,23 +43,25 @@ AxiStreamBus, AxiStreamTransaction, AxiStreamSource, AxiStreamSink, AxiStreamMon
class EthMacFrame:
def __init__(self, data=None, tx_complete=None):
self.data = bytearray()
def __init__(self, data=b'', tx_complete=None):
self.data = b''
self.sim_time_start = None
self.sim_time_sfd = None
self.sim_time_end = None
self.ptp_timestamp = None
self.ptp_tag = None
self.tx_complete = None
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_sfd = data.sim_time_sfd
self.sim_time_end = data.sim_time_end
self.ptp_timestamp = data.ptp_timestamp
self.ptp_tag = data.ptp_tag
self.tx_complete = data.tx_complete
else:
self.data = bytearray(data)
self.data = bytes(data)
if tx_complete is not None:
self.tx_complete = tx_complete
@@ -104,7 +106,8 @@ class EthMacFrame:
f"sim_time_start={self.sim_time_start!r}, "
f"sim_time_sfd={self.sim_time_sfd!r}, "
f"sim_time_end={self.sim_time_end!r}, "
f"ptp_timestamp={self.ptp_timestamp!r})"
f"ptp_timestamp={self.ptp_timestamp!r}, "
f"ptp_tag={self.ptp_tag!r})"
)
def __len__(self):
@@ -118,7 +121,7 @@ class EthMacFrame:
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):
self.bus = bus
@@ -126,14 +129,18 @@ class EthMacTx(Reset):
self.reset = reset
self.ptp_time = ptp_time
self.ptp_ts = ptp_ts
self.ptp_ts_tag = ptp_ts_tag
self.ptp_ts_valid = ptp_ts_valid
self.ifg = ifg
self.speed = speed
if 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("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")
super().__init__(*args, **kwargs)
@@ -172,6 +179,10 @@ class EthMacTx(Reset):
self.log.info(" tuser width: %d bits", len(self.bus.tuser))
else:
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:
raise ValueError("tready is required")
@@ -183,9 +194,11 @@ class EthMacTx(Reset):
raise ValueError(f"Bus does not evenly divide into byte lanes "
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)
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._run_cr = None
@@ -242,8 +255,8 @@ class EthMacTx(Reset):
self._run_ts_cr.kill()
self._run_ts_cr = None
if self.ptp_ts_valid:
self.ptp_ts_valid <= 0
if self.ptp_ts_valid is not None:
self.ptp_ts_valid.value = 0
self.active = False
@@ -252,9 +265,9 @@ class EthMacTx(Reset):
else:
self.log.info("Reset de-asserted")
if self._run_cr is None:
self._run_cr = cocotb.fork(self._run())
if self._run_ts_cr is None and self.ptp_ts:
self._run_ts_cr = cocotb.fork(self._run_ts())
self._run_cr = cocotb.start_soon(self._run())
if self._run_ts_cr is None and self.ptp_ts is not None:
self._run_ts_cr = cocotb.start_soon(self._run_ts())
async def _run(self):
frame = None
@@ -264,7 +277,9 @@ class EthMacTx(Reset):
# wait for data
cycle = await self.stream.recv()
frame = EthMacFrame(bytearray())
frame = EthMacFrame()
data = bytearray()
frame.sim_time_start = get_sim_time()
# wait for preamble time
@@ -272,23 +287,25 @@ class EthMacTx(Reset):
frame.sim_time_sfd = get_sim_time()
if self.ptp_time:
frame.ptp_timestamp = self.ptp_time.value.integer
self.ts_queue.put_nowait(frame.ptp_timestamp)
if self.ptp_time is not None:
frame.ptp_timestamp = int(self.ptp_time.value)
frame.ptp_tag = int(cycle.tuser) >> 1
self.ts_queue.put_nowait((frame.ptp_timestamp, frame.ptp_tag))
# process frame data
while True:
byte_count = 0
for offset in range(self.byte_lanes):
if not hasattr(self.bus, "tkeep") or (cycle.tkeep.integer >> offset) & 1:
frame.data.append((cycle.tdata.integer >> (offset * self.byte_size)) & self.byte_mask)
if not hasattr(self.bus, "tkeep") or (int(cycle.tkeep) >> offset) & 1:
data.append((int(cycle.tdata) >> (offset * self.byte_size)) & self.byte_mask)
byte_count += 1
# wait for serialization time
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()
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')
async def _run_ts(self):
clock_edge_event = RisingEdge(self.clock)
while True:
await RisingEdge(self.clock)
self.ptp_ts_valid <= 0
await clock_edge_event
self.ptp_ts_valid.value = 0
if not self.ts_queue.empty():
ts = self.ts_queue.get_nowait()
self.ptp_ts <= ts
self.ptp_ts_valid <= 1
ts, tag = self.ts_queue.get_nowait()
self.ptp_ts.value = ts
if self.ptp_ts_tag is not None:
self.ptp_ts_tag.value = tag
self.ptp_ts_valid.value = 1
class EthMacRx(Reset):
@@ -331,11 +352,14 @@ class EthMacRx(Reset):
self.ptp_time = ptp_time
self.ifg = ifg
self.speed = speed
if 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("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")
super().__init__(*args, **kwargs)
@@ -378,6 +402,10 @@ class EthMacRx(Reset):
self.log.info(" tuser width: %d bits", len(self.bus.tuser))
else:
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:
raise ValueError("Byte size must be 8")
@@ -458,10 +486,11 @@ class EthMacRx(Reset):
else:
self.log.info("Reset de-asserted")
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):
frame = None
frame_offset = 0
tuser = 0
self.active = False
@@ -477,14 +506,15 @@ class EthMacRx(Reset):
frame.sim_time_sfd = None
frame.sim_time_end = None
self.log.info("TX frame: %s", frame)
frame_offset = 0
# wait for preamble time
await Timer(self.time_scale*8*8//self.speed, 'step')
frame.sim_time_sfd = get_sim_time()
if self.ptp_time:
frame.ptp_timestamp = self.ptp_time.value.integer
if self.ptp_time is not None:
frame.ptp_timestamp = int(self.ptp_time.value)
tuser |= frame.ptp_timestamp << 1
# process frame data
@@ -499,11 +529,12 @@ class EthMacRx(Reset):
cycle.tuser = tuser
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
byte_count += 1
frame_offset += 1
if len(frame.data) == 0:
if frame_offset >= len(frame.data):
cycle.tlast = 1
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
@@ -521,13 +552,13 @@ class EthMacRx(Reset):
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,
rx_bus=None, rx_clk=None, rx_rst=None, rx_ptp_time=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,
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):
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)
self.rx = EthMacRx(rx_bus, rx_clk, rx_rst, rx_ptp_time,
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
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("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")
super().__init__(*args, **kwargs)
@@ -157,6 +157,7 @@ class GmiiSource(Reset):
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.active_event = Event()
self.ifg = 12
self.mii_mode = False
@@ -189,6 +190,7 @@ class GmiiSource(Reset):
frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -198,6 +200,7 @@ class GmiiSource(Reset):
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -225,6 +228,7 @@ class GmiiSource(Reset):
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
@@ -239,10 +243,10 @@ class GmiiSource(Reset):
self._run_cr = None
self.active = False
self.data <= 0
self.data.value = 0
if self.er is not None:
self.er <= 0
self.dv <= 0
self.er.value = 0
self.dv.value = 0
if 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():
self.idle_event.set()
self.active_event.clear()
else:
self.log.info("Reset de-asserted")
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):
frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = 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:
# in IFG
ifg_cnt -= 1
@@ -283,43 +297,54 @@ class GmiiSource(Reset):
frame.normalize()
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:
mii_data = []
mii_error = []
# convert to MII
frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error):
mii_data.append(b & 0x0F)
mii_data.append(b >> 4)
mii_error.append(e)
mii_error.append(e)
frame.data = mii_data
frame.error = mii_error
frame_data.append(b & 0x0F)
frame_data.append(b >> 4)
frame_error.append(e)
frame_error.append(e)
else:
frame_data = frame.data
frame_error = frame.error
self.active = True
frame_offset = 0
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):
frame.sim_time_sfd = get_sim_time()
self.data <= d
self.data.value = d
if self.er is not None:
self.er <= frame.error.pop(0)
self.dv <= 1
self.er.value = frame_error[frame_offset]
self.dv.value = 1
frame_offset += 1
if not frame.data:
if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None
self.current_frame = None
else:
self.data <= 0
self.data.value = 0
if self.er is not None:
self.er <= 0
self.dv <= 0
self.er.value = 0
self.dv.value = 0
self.active = False
if ifg_cnt == 0 and self.queue.empty():
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):
@@ -336,7 +361,7 @@ class GmiiSink(Reset):
self.log.info("GMII sink")
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")
super().__init__(*args, **kwargs)
@@ -415,19 +440,27 @@ class GmiiSink(Reset):
else:
self.log.info("Reset de-asserted")
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):
frame = None
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = RisingEdge(self.clock)
if self.enable is None or self.enable.value:
d_val = self.data.value.integer
dv_val = self.dv.value.integer
er_val = 0 if self.er is None else self.er.value.integer
active_event = RisingEdge(self.dv)
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):
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 dv_val:
@@ -439,7 +472,7 @@ class GmiiSink(Reset):
# end of frame
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:
odd = True
@@ -481,6 +514,12 @@ class GmiiSink(Reset):
frame.data.append(d_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:
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()
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.rx.mii_mode = False
self.tx.clock = self.gtx_clk
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.rx.mii_mode = True
self.tx.clock = self.tx_clk
@@ -529,8 +568,8 @@ class GmiiPhy:
while True:
await t
self.rx_clk <= 1
self.tx_clk <= 1
self.rx_clk.value = 1
self.tx_clk.value = 1
await t
self.rx_clk <= 0
self.tx_clk <= 0
self.rx_clk.value = 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
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("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")
super().__init__(*args, **kwargs)
@@ -59,6 +59,7 @@ class MiiSource(Reset):
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.active_event = Event()
self.ifg = 12
@@ -90,6 +91,7 @@ class MiiSource(Reset):
frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -99,6 +101,7 @@ class MiiSource(Reset):
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -126,6 +129,7 @@ class MiiSource(Reset):
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
@@ -140,10 +144,10 @@ class MiiSource(Reset):
self._run_cr = None
self.active = False
self.data <= 0
self.data.value = 0
if self.er is not None:
self.er <= 0
self.dv <= 0
self.er.value = 0
self.dv.value = 0
if 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():
self.idle_event.set()
self.active_event.clear()
else:
self.log.info("Reset de-asserted")
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):
frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = 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:
# in IFG
ifg_cnt -= 1
@@ -183,40 +197,48 @@ class MiiSource(Reset):
self.log.info("TX frame: %s", frame)
frame.normalize()
mii_data = []
mii_error = []
# convert to MII
frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error):
mii_data.append(b & 0x0F)
mii_data.append(b >> 4)
mii_error.append(e)
mii_error.append(e)
frame.data = mii_data
frame.error = mii_error
frame_data.append(b & 0x0F)
frame_data.append(b >> 4)
frame_error.append(e)
frame_error.append(e)
self.active = True
frame_offset = 0
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:
frame.sim_time_sfd = get_sim_time()
self.data <= d
self.data.value = d
if self.er is not None:
self.er <= frame.error.pop(0)
self.dv <= 1
self.er.value = frame_error[frame_offset]
self.dv.value = 1
frame_offset += 1
if not frame.data:
if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None
self.current_frame = None
else:
self.data <= 0
self.data.value = 0
if self.er is not None:
self.er <= 0
self.dv <= 0
self.er.value = 0
self.dv.value = 0
self.active = False
if ifg_cnt == 0 and self.queue.empty():
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):
@@ -232,7 +254,7 @@ class MiiSink(Reset):
self.log.info("MII sink")
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")
super().__init__(*args, **kwargs)
@@ -309,19 +331,27 @@ class MiiSink(Reset):
else:
self.log.info("Reset de-asserted")
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):
frame = None
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = RisingEdge(self.clock)
if self.enable is None or self.enable.value:
d_val = self.data.value.integer
dv_val = self.dv.value.integer
er_val = 0 if self.er is None else self.er.value.integer
active_event = RisingEdge(self.dv)
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):
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 dv_val:
@@ -370,6 +400,12 @@ class MiiSink(Reset):
frame.data.append(d_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:
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:
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):
half_period = get_sim_steps(period / 2.0, 'ns')
@@ -406,8 +442,8 @@ class MiiPhy:
while True:
await t
self.tx_clk <= 1
self.rx_clk <= 1
self.tx_clk.value = 1
self.rx_clk.value = 1
await t
self.tx_clk <= 0
self.rx_clk <= 0
self.tx_clk.value = 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
of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ THE SOFTWARE.
"""
import logging
import math
from decimal import Decimal, Context
from fractions import Fraction
import cocotb
@@ -38,8 +38,8 @@ class PtpClock(Reset):
def __init__(
self,
ts_96=None,
ts_64=None,
ts_tod=None,
ts_rel=None,
ts_step=None,
pps=None,
clock=None,
@@ -49,42 +49,42 @@ class PtpClock(Reset):
*args, **kwargs):
self.log = logging.getLogger(f"cocotb.eth.{type(self).__name__}")
self.ts_96 = ts_96
self.ts_64 = ts_64
self.ts_tod = ts_tod
self.ts_rel = ts_rel
self.ts_step = ts_step
self.pps = pps
self.clock = clock
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("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")
super().__init__(*args, **kwargs)
self.ts_96_s = 0
self.ts_96_ns = 0
self.ts_96_fns = 0
self.ctx = Context(prec=60)
self.ts_64_ns = 0
self.ts_64_fns = 0
self.period_ns = 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.drift_cnt = 0
if self.ts_96 is not None:
self.ts_96.setimmediatevalue(0)
if self.ts_64 is not None:
self.ts_64.setimmediatevalue(0)
if self.ts_tod is not None:
self.ts_tod.setimmediatevalue(0)
if self.ts_rel is not None:
self.ts_rel.setimmediatevalue(0)
if self.ts_step is not None:
self.ts_step.setimmediatevalue(0)
if self.pps is not None:
@@ -96,90 +96,103 @@ class PtpClock(Reset):
def set_period(self, ns, fns):
self.period_ns = int(ns)
self.period_fns = int(fns) & 0xffff
self.period_fns = int(fns) & 0xffffffff
def set_drift(self, ns, fns, rate):
self.drift_ns = int(ns)
self.drift_fns = int(fns) & 0xffff
self.drift_rate = int(rate)
def set_drift(self, num, denom):
self.drift_num = int(num)
self.drift_denom = int(denom)
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)
frac = Fraction(drift).limit_denominator(2**16)
drift = frac.numerator
rate = frac.denominator
self.period_ns = period >> 16
self.period_fns = period & 0xffff
self.drift_ns = drift >> 16
self.drift_fns = drift & 0xffff
self.drift_rate = rate
frac = Fraction(drift).limit_denominator(2**16-1)
self.set_period(period >> 32, period & 0xffffffff)
self.set_drift(frac.numerator, frac.denominator)
self.log.info("Set period: %s ns", t)
self.log.info("Period: 0x%x ns 0x%08x fns", self.period_ns, self.period_fns)
self.log.info("Drift: 0x%04x / 0x%04x fns", self.drift_num, self.drift_denom)
def get_period_ns(self):
p = ((self.period_ns << 16) | self.period_fns) / 2**16
if self.drift_rate:
return p + ((self.drift_ns << 16) | self.drift_fns) / self.drift_rate / 2**16
return p
p = Decimal((self.period_ns << 32) | self.period_fns)
if self.drift_denom:
p += Decimal(self.drift_num) / Decimal(self.drift_denom)
return p / Decimal(2**32)
def set_ts_96(self, ts_s, ts_ns=None, ts_fns=None):
ts_s = int(ts_s)
if ts_fns is not None:
# got separate fields
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
def set_ts_tod(self, ts_s, ts_ns, ts_fns):
self.ts_tod_s = int(ts_s)
self.ts_tod_ns = int(ts_ns)
self.ts_tod_fns = int(ts_fns)
self.ts_updated = True
def set_ts_96_ns(self, t):
self.set_ts_96_s(t*1e-9)
def set_ts_tod_96(self, ts):
ts = int(ts)
self.set_ts_tod(ts >> 48, (ts >> 32) & 0x3fffffff, (ts & 0xffff) << 16)
def set_ts_96_s(self, t):
ts_ns, ts_s = math.modf(t)
ts_ns *= 1e9
ts_fns, ts_ns = math.modf(ts_ns)
ts_fns *= 2**16
self.set_ts_96(ts_s, ts_ns, ts_fns)
def set_ts_tod_ns(self, t):
ts_s, ts_ns = self.ctx.divmod(Decimal(t), Decimal(1000000000))
ts_ns, ts_fns = self.ctx.divmod(ts_ns, Decimal(1))
ts_ns = ts_ns.to_integral_value()
ts_fns = (ts_fns * Decimal(2**32)).to_integral_value()
self.set_ts_tod(ts_s, ts_ns, ts_fns)
def get_ts_96(self):
return (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns
def set_ts_tod_s(self, t):
self.set_ts_tod_ns(Decimal(t).scaleb(9, self.ctx))
def get_ts_96_ns(self):
return self.ts_96_s*1e9+self.ts_96_ns+self.ts_96_fns/2**16
def set_ts_tod_sim_time(self):
self.set_ts_tod_ns(Decimal(get_sim_time('fs')).scaleb(-6))
def get_ts_96_s(self):
return self.get_ts_96_ns()*1e-9
def get_ts_tod(self):
return (self.ts_tod_s, self.ts_tod_ns, self.ts_tod_fns)
def set_ts_64(self, ts_ns, ts_fns=None):
ts_ns = int(ts_ns)
if ts_fns is not None:
# got separate fields
self.ts_64_ns = ts_ns
self.ts_64_fns = int(ts_fns)
else:
# got timestamp as integer
self.ts_64_ns = ts_ns >> 16
self.ts_64_fns = ts_ns & 0xffff
def get_ts_tod_96(self):
ts_s, ts_ns, ts_fns = self.get_ts_tod()
return (ts_s << 48) | (ts_ns << 16) | (ts_fns >> 16)
def get_ts_tod_ns(self):
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_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
def set_ts_64_ns(self, t):
self.set_ts_64(t*2**16)
def set_ts_rel_64(self, ts):
ts = int(ts)
self.set_ts_rel(ts >> 16, (ts & 0xffff) << 16)
def set_ts_64_s(self, t):
self.set_ts_64_ns(t*1e9)
def set_ts_rel_ns(self, t):
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):
return (self.ts_64_ns << 16) | self.ts_64_fns
def set_ts_rel_s(self, t):
self.set_ts_rel_ns(Decimal(t).scaleb(9, self.ctx))
def get_ts_64_ns(self):
return self.get_ts_64()/2**16
def set_ts_rel_sim_time(self):
self.set_ts_rel_ns(Decimal(get_sim_time('fs')).scaleb(-6))
def get_ts_64_s(self):
return self.get_ts_64()*1e-9
def get_ts_rel(self):
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):
if state:
@@ -188,145 +201,169 @@ class PtpClock(Reset):
self._run_cr.kill()
self._run_cr = None
self.ts_96_s = 0
self.ts_96_ns = 0
self.ts_96_fns = 0
self.ts_64_ns = 0
self.ts_64_fns = 0
self.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.drift_cnt = 0
if self.ts_96 is not None:
self.ts_96 <= 0
if self.ts_64 is not None:
self.ts_64 <= 0
if self.ts_tod is not None:
self.ts_tod.value = 0
if self.ts_rel is not None:
self.ts_rel.value = 0
if self.ts_step is not None:
self.ts_step <= 0
self.ts_step.value = 0
if self.pps is not None:
self.pps <= 0
self.pps.value = 0
else:
self.log.info("Reset de-asserted")
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):
clock_edge_event = RisingEdge(self.clock)
while True:
await RisingEdge(self.clock)
await clock_edge_event
if self.ts_step is not None:
self.ts_step <= self.ts_updated
self.ts_step.value = self.ts_updated
self.ts_updated = False
if self.pps is not None:
self.pps <= 0
self.pps.value = 0
# increment 96 bit timestamp
if self.ts_96 is not None or self.pps is not None:
t = ((self.ts_96_ns << 16) + self.ts_96_fns) + ((self.period_ns << 16) + self.period_fns)
# increment tod bit timestamp
self.ts_tod_fns += (self.period_ns << 32) + self.period_fns
if self.drift_rate and self.drift_cnt == 0:
t += (self.drift_ns << 16) + self.drift_fns
if self.drift_denom and self.drift_cnt == 0:
self.ts_tod_fns += self.drift_num
if t > (1000000000 << 16):
self.ts_96_s += 1
t -= (1000000000 << 16)
ns_inc = self.ts_tod_fns >> 32
self.ts_tod_fns &= 0xffffffff
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:
self.pps <= 1
self.pps.value = 1
self.ts_96_fns = t & 0xffff
self.ts_96_ns = t >> 16
if self.ts_tod is not None:
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:
self.ts_96 <= (self.ts_96_s << 48) | (self.ts_96_ns << 16) | (self.ts_96_fns)
# increment rel bit timestamp
self.ts_rel_fns += (self.period_ns << 32) + self.period_fns
# increment 64 bit timestamp
if self.ts_64 is not None:
t = ((self.ts_64_ns << 16) + self.ts_64_fns) + ((self.period_ns << 16) + self.period_fns)
if self.drift_denom and self.drift_cnt == 0:
self.ts_rel_fns += self.drift_num
if self.drift_rate and self.drift_cnt == 0:
t += ((self.drift_ns << 16) + self.drift_fns)
ns_inc = self.ts_rel_fns >> 32
self.ts_rel_fns &= 0xffffffff
self.ts_64_fns = t & 0xffff
self.ts_64_ns = t >> 16
self.ts_rel_ns = (self.ts_rel_ns + ns_inc) & 0xffffffffffff
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:
self.drift_cnt -= 1
else:
self.drift_cnt = self.drift_rate-1
self.drift_cnt = self.drift_denom-1
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.ts_96 = ts_96
self.ts_64 = ts_64
self.ts_tod = ts_tod
self.ts_rel = ts_rel
self.pps = pps
self.clock = clock
self.log.info("PTP clock (sim time)")
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")
super().__init__(*args, **kwargs)
self.ts_96_s = 0
self.ts_96_ns = 0
self.ts_96_fns = 0
self.ctx = Context(prec=60)
self.ts_64_ns = 0
self.ts_64_fns = 0
self.ts_tod_s = 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.ts_96.setimmediatevalue(0)
if self.ts_64 is not None:
self.ts_64.setimmediatevalue(0)
self.last_ts_tod_s = 0
if self.ts_tod is not None:
self.ts_tod.setimmediatevalue(0)
if self.ts_rel is not None:
self.ts_rel.setimmediatevalue(0)
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):
return (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns
def get_ts_tod(self):
return (self.ts_tod_s, self.ts_tod_ns, self.ts_tod_fns)
def get_ts_96_ns(self):
return self.ts_96_s*1e9+self.ts_96_ns+self.ts_96_fns/2**16
def get_ts_tod_96(self):
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):
return self.get_ts_96_ns()*1e-9
def get_ts_tod_ns(self):
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):
return (self.ts_64_ns << 16) | self.ts_64_fns
def get_ts_tod_s(self):
return self.get_ts_tod_ns().scaleb(-9, self.ctx)
def get_ts_64_ns(self):
return self.get_ts_64()/2**16
def get_ts_rel(self):
return (self.ts_rel_ns, self.ts_rel_fns)
def get_ts_64_s(self):
return self.get_ts_64()*1e-9
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)
async def _run(self):
clock_edge_event = RisingEdge(self.clock)
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_64_fns = int(self.ts_64_fns*0x10000)
self.ts_rel_ns = int(ts_ns.to_integral_value()) & 0xffffffffffff
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)
self.ts_96_fns = self.ts_64_fns
ts_s, ts_ns = self.ctx.divmod(ts_ns, Decimal(1000000000))
if self.ts_96 is not None:
self.ts_96 <= (self.ts_96_s << 48) | (self.ts_96_ns << 16) | self.ts_96_fns
self.ts_tod_s = int(ts_s.scaleb(-9).to_integral_value())
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:
self.ts_64 <= (self.ts_64_ns << 16) | self.ts_64_fns
if self.ts_tod is not None:
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:
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
of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ THE SOFTWARE.
"""
import cocotb
from cocotb.triggers import RisingEdge, FallingEdge
from cocotb.triggers import Edge
class Reset:
@@ -33,7 +33,7 @@ class Reset:
self._reset_state = True
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()
@@ -56,11 +56,14 @@ class Reset:
async def _run_reset(self, reset_signal, active_level):
while True:
if bool(reset_signal.value):
await FallingEdge(reset_signal)
self._ext_reset = not active_level
self._update_reset()
else:
await RisingEdge(reset_signal)
await reset_signal.value_change
try:
level = bool(int(reset_signal.value))
except ValueError:
continue
if level:
self._ext_reset = active_level
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
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("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")
super().__init__(*args, **kwargs)
@@ -61,6 +61,7 @@ class RgmiiSource(Reset):
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.active_event = Event()
self.ifg = 12
self.mii_mode = False
@@ -90,6 +91,7 @@ class RgmiiSource(Reset):
frame = GmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -99,6 +101,7 @@ class RgmiiSource(Reset):
frame = GmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -126,6 +129,7 @@ class RgmiiSource(Reset):
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
@@ -136,12 +140,12 @@ class RgmiiSource(Reset):
if state:
self.log.info("Reset asserted")
if self._run_cr is not None:
self._run_cr.kill()
self._run_cr.cancel()
self._run_cr = None
self.active = False
self.data <= 0
self.ctrl <= 0
self.data.value = 0
self.ctrl.value = 0
if 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():
self.idle_event.set()
self.active_event.clear()
else:
self.log.info("Reset de-asserted")
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):
frame = None
frame_offset = 0
frame_data = None
frame_error = None
ifg_cnt = 0
in_ifg = False
self.active = False
d = 0
er = 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:
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
self.data <= d >> 4
self.ctrl <= en ^ er
self.data.value = d >> 4
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:
# in IFG
ifg_cnt -= 1
in_ifg = True
elif frame is None and not self.queue.empty():
# send frame
@@ -189,31 +214,36 @@ class RgmiiSource(Reset):
frame.normalize()
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:
mii_data = []
mii_error = []
# convert to MII
frame_data = []
frame_error = []
for b, e in zip(frame.data, frame.error):
mii_data.append((b & 0x0F)*0x11)
mii_data.append((b >> 4)*0x11)
mii_error.append(e)
mii_error.append(e)
frame.data = mii_data
frame.error = mii_error
frame_data.append((b & 0x0F)*0x11)
frame_data.append((b >> 4)*0x11)
frame_error.append(e)
frame_error.append(e)
else:
frame_data = frame.data
frame_error = frame.error
self.active = True
frame_offset = 0
if frame is not None:
d = frame.data.pop(0)
er = frame.error.pop(0)
d = frame_data[frame_offset]
er = frame_error[frame_offset]
en = 1
frame_offset += 1
if frame.sim_time_sfd is None and d in (EthPre.SFD, 0xD, 0xDD):
frame.sim_time_sfd = get_sim_time()
if not frame.data:
if frame_offset >= len(frame_data):
ifg_cnt = max(self.ifg, 1)
in_ifg = True
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None
@@ -223,13 +253,14 @@ class RgmiiSource(Reset):
er = 0
en = 0
self.active = False
if not in_ifg and self.queue.empty():
self.idle_event.set()
self.active_event.clear()
await self.active_event.wait()
await FallingEdge(self.clock)
# send low nibble after falling edge, leading in to rising edge
self.data <= d & 0x0F
self.ctrl <= en
elif self.enable is not None and not self.enable.value:
await enable_event
class RgmiiSink(Reset):
@@ -247,7 +278,7 @@ class RgmiiSink(Reset):
self.log.info("RGMII sink")
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")
super().__init__(*args, **kwargs)
@@ -316,14 +347,14 @@ class RgmiiSink(Reset):
if state:
self.log.info("Reset asserted")
if self._run_cr is not None:
self._run_cr.kill()
self._run_cr.cancel()
self._run_cr = None
self.active = False
else:
self.log.info("Reset de-asserted")
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):
frame = None
@@ -332,20 +363,29 @@ class RgmiiSink(Reset):
dv_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:
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
d_val = self.data.value.integer
dv_val = self.ctrl.value.integer
d_val = int(self.data.value)
dv_val = int(self.ctrl.value)
await FallingEdge(self.clock)
await clock_falling_edge_event
# capture high nibble on falling edge
d_val |= self.data.value.integer << 4
er_val = dv_val ^ self.ctrl.value.integer
if self.enable is None or self.enable.value:
d_val |= int(self.data.value) << 4
er_val = dv_val ^ int(self.ctrl.value)
if frame is None:
if dv_val:
@@ -357,7 +397,7 @@ class RgmiiSink(Reset):
# end of frame
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:
odd = True
@@ -399,6 +439,12 @@ class RgmiiSink(Reset):
frame.data.append(d_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:
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()
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.rx.mii_mode = False
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.rx.mii_mode = True
@@ -441,6 +487,6 @@ class RgmiiPhy:
while True:
await t
self.rx_clk <= 1
self.rx_clk.value = 1
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
of this software and associated documentation files (the "Software"), to deal
@@ -28,7 +28,7 @@ import zlib
import cocotb
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 .version import __version__
@@ -147,7 +147,7 @@ class XgmiiSource(Reset):
self.log.info("XGMII source")
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")
super().__init__(*args, **kwargs)
@@ -158,6 +158,7 @@ class XgmiiSource(Reset):
self.current_frame = None
self.idle_event = Event()
self.idle_event.set()
self.active_event = Event()
self.enable_dic = True
self.ifg = 12
@@ -170,14 +171,19 @@ class XgmiiSource(Reset):
self.queue_occupancy_limit_frames = -1
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_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_c |= 1 << k
@@ -195,6 +201,7 @@ class XgmiiSource(Reset):
frame = XgmiiFrame(frame)
await self.queue.put(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -204,6 +211,7 @@ class XgmiiSource(Reset):
frame = XgmiiFrame(frame)
self.queue.put_nowait(frame)
self.idle_event.clear()
self.active_event.set()
self.queue_occupancy_bytes += len(frame)
self.queue_occupancy_frames += 1
@@ -231,6 +239,7 @@ class XgmiiSource(Reset):
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set()
self.active_event.clear()
self.queue_occupancy_bytes = 0
self.queue_occupancy_frames = 0
@@ -245,8 +254,8 @@ class XgmiiSource(Reset):
self._run_cr = None
self.active = False
self.data <= 0
self.ctrl <= 0
self.data.value = 0
self.ctrl.value = 0
if 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():
self.idle_event.set()
self.active_event.clear()
else:
self.log.info("Reset de-asserted")
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):
frame = None
frame_offset = 0
ifg_cnt = 0
deficit_idle_cnt = 0
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = RisingEdge(self.clock)
if self.enable is None or self.enable.value:
if ifg_cnt + deficit_idle_cnt > self.byte_width-1 or (not self.enable_dic and ifg_cnt > 4):
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 + deficit_idle_cnt > self.byte_lanes-1 or (not self.enable_dic and ifg_cnt > 4):
# in IFG
ifg_cnt = ifg_cnt - self.byte_width
ifg_cnt = ifg_cnt - self.byte_lanes
if ifg_cnt < 0:
if self.enable_dic:
deficit_idle_cnt = max(deficit_idle_cnt+ifg_cnt, 0)
@@ -306,7 +323,7 @@ class XgmiiSource(Reset):
else:
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
frame.start_lane = 4
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)
ifg_cnt = 0
self.active = True
frame_offset = 0
else:
# clear counters
deficit_idle_cnt = 0
@@ -325,16 +343,17 @@ class XgmiiSource(Reset):
d_val = 0
c_val = 0
for k in range(self.byte_width):
for k in range(self.byte_lanes):
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:
frame.sim_time_sfd = get_sim_time()
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:
ifg_cnt = max(self.ifg - (self.byte_width-k), 0)
if frame_offset >= len(frame.data):
ifg_cnt = max(self.ifg - (self.byte_lanes-k), 0)
frame.sim_time_end = get_sim_time()
frame.handle_tx_complete()
frame = None
@@ -343,13 +362,20 @@ class XgmiiSource(Reset):
d_val |= XgmiiCtrl.IDLE << k*8
c_val |= 1 << k
self.data <= d_val
self.ctrl <= c_val
self.data.value = d_val
self.ctrl.value = c_val
else:
self.data <= self.idle_d
self.ctrl <= self.idle_c
self.data.value = self.idle_d
self.ctrl.value = self.idle_c
self.active = False
if ifg_cnt == 0 and self.queue.empty():
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):
@@ -364,7 +390,7 @@ class XgmiiSink(Reset):
self.log.info("XGMII sink")
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")
super().__init__(*args, **kwargs)
@@ -377,9 +403,14 @@ class XgmiiSink(Reset):
self.queue_occupancy_frames = 0
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
@@ -437,19 +468,32 @@ class XgmiiSink(Reset):
else:
self.log.info("Reset de-asserted")
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):
frame = None
self.active = False
while True:
await RisingEdge(self.clock)
clock_edge_event = RisingEdge(self.clock)
if self.enable is None or self.enable.value:
for offset in range(self.byte_width):
d_val = (self.data.value.integer >> (offset*8)) & 0xff
c_val = (self.ctrl.value.integer >> offset) & 1
active_event = First(Edge(self.data), Edge(self.ctrl))
enable_event = None
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 c_val and d_val == XgmiiCtrl.START:
@@ -482,3 +526,9 @@ class XgmiiSink(Reset):
frame.data.append(d_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
download_url = https://github.com/alexforencich/cocotbext-eth/tarball/master
long_description = file: README.md
long-description-content-type = text/markdown
long_description_content_type = text/markdown
platforms = any
classifiers =
Development Status :: 3 - Alpha
Programming Language :: Python :: 3
Framework :: cocotb
License :: OSI Approved :: MIT License
Operating System :: OS Independent
Programming Language :: Python :: 3
Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
[options]
packages = find_namespace:
python_requires = >=3.6
install_requires =
cocotb
cocotbext-axi
cocotb >= 1.6.0
cocotbext-axi >= 0.1.16
[options.extras_require]
test =
@@ -46,31 +47,41 @@ addopts =
# tox configuration
[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]
python =
3.6: py36
3.7: py37
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313
[testenv]
setenv =
COVERAGE=1
usedevelop = True
deps =
pytest
pytest-xdist
cocotb-test
coverage
pytest-cov
pytest == 8.3.4
pytest-xdist == 3.6.1
cocotb == 1.9.2
cocotb-bus == 0.2.1
cocotb-test == 0.2.6
cocotbext-axi == 0.1.26
coverage == 7.0.5
pytest-cov == 4.0.0
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'
coverage report
whitelist_externals =
allowlist_externals =
bash
# 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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,31 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_eth_mac
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
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)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -28,6 +28,7 @@ import logging
import os
import cocotb_test.simulator
import pytest
import cocotb
from cocotb.clock import Clock
@@ -45,8 +46,20 @@ class TB:
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
cocotb.fork(Clock(dut.tx_clk, 6.4, units="ns").start())
cocotb.fork(Clock(dut.rx_clk, 6.4, units="ns").start())
if len(dut.tx_axis_tdata) == 8:
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(
tx_clk=dut.tx_clk,
@@ -54,6 +67,7 @@ class TB:
tx_bus=AxiStreamBus.from_prefix(dut, "tx_axis"),
tx_ptp_time=dut.tx_ptp_time,
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,
rx_clk=dut.rx_clk,
rx_rst=dut.rx_rst,
@@ -63,12 +77,12 @@ class TB:
)
self.tx_ptp = PtpClockSimTime(
ts_96=dut.tx_ptp_time,
ts_tod=dut.tx_ptp_time,
clock=dut.tx_clk
)
self.rx_ptp = PtpClockSimTime(
ts_96=dut.rx_ptp_time,
ts_tod=dut.rx_ptp_time,
clock=dut.rx_clk
)
@@ -80,12 +94,12 @@ class TB:
self.dut.rx_rst.setimmediatevalue(0)
await RisingEdge(self.dut.tx_clk)
await RisingEdge(self.dut.tx_clk)
self.dut.tx_rst <= 1
self.dut.rx_rst <= 1
self.dut.tx_rst.value = 1
self.dut.rx_rst.value = 1
await RisingEdge(self.dut.tx_clk)
await RisingEdge(self.dut.tx_clk)
self.dut.tx_rst <= 0
self.dut.rx_rst <= 0
self.dut.tx_rst.value = 0
self.dut.rx_rst.value = 0
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))
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]:
factory = TestFactory(test)
factory.add_option("payload_lengths", [size_list])
factory.add_option("payload_data", [incrementing_payload])
factory.add_option("speed", [10e9, 1e9])
factory.add_option("speed", speed)
factory.generate_tests()
@@ -170,7 +193,8 @@ if cocotb.SIM_NAME:
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"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
@@ -181,6 +205,13 @@ def test_eth_mac(request):
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()}
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
of this software and associated documentation files (the "Software"), to deal
@@ -29,28 +29,37 @@ THE SOFTWARE.
/*
* 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_rst,
inout wire [63:0] tx_axis_tdata,
inout wire [7:0] tx_axis_tkeep,
inout wire [AXIS_DATA_WIDTH-1:0] tx_axis_tdata,
inout wire [AXIS_KEEP_WIDTH-1:0] tx_axis_tkeep,
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_tready,
inout wire [95:0] tx_ptp_time,
inout wire [95:0] tx_ptp_ts,
inout wire [PTP_TS_WIDTH-1:0] tx_ptp_time,
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 rx_clk,
inout wire rx_rst,
inout wire [63:0] rx_axis_tdata,
inout wire [7:0] rx_axis_tkeep,
inout wire [AXIS_DATA_WIDTH-1:0] rx_axis_tdata,
inout wire [AXIS_KEEP_WIDTH-1:0] rx_axis_tkeep,
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 [95:0] rx_ptp_time
inout wire [PTP_TS_WIDTH-1:0] rx_ptp_time
);
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_gmii
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = 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,
dut.clk, dut.rst, dut.gmii_clk_en, dut.gmii_mii_sel)
@@ -61,10 +61,10 @@ class TB:
self.dut.rst.setimmediatevalue(0)
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)
self.dut.rst <= 0
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
@@ -76,15 +76,17 @@ class TB:
self._enable_generator = generator
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):
self.set_enable_generator(None)
async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator:
self.dut.gmii_clk_en <= val
await RisingEdge(self.dut.clk)
self.dut.gmii_clk_en.value = val
await clock_edge_event
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.source.ifg = ifg
tb.dut.gmii_mii_sel <= mii_sel
tb.dut.gmii_mii_sel.value = mii_sel
if enable_gen is not None:
tb.set_enable_generator(enable_gen())
@@ -130,7 +132,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME:
if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test)
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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_gmii_phy
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
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.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,
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)
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)
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)
@@ -140,7 +140,7 @@ def cycle_en():
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]:

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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_mii
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = 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,
dut.clk, dut.rst, dut.mii_clk_en)
@@ -60,10 +60,10 @@ class TB:
self.dut.rst.setimmediatevalue(0)
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)
self.dut.rst <= 0
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
@@ -75,15 +75,17 @@ class TB:
self._enable_generator = generator
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):
self.set_enable_generator(None)
async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator:
self.dut.mii_clk_en <= val
await RisingEdge(self.dut.clk)
self.dut.mii_clk_en.value = val
await clock_edge_event
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])
if cocotb.SIM_NAME:
if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test)
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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_mii_phy
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -55,10 +55,10 @@ class TB:
self.dut.phy_rst.setimmediatevalue(0)
await RisingEdge(self.dut.phy_tx_clk)
await RisingEdge(self.dut.phy_tx_clk)
self.dut.phy_rst <= 1
self.dut.phy_rst.value = 1
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)
@@ -131,7 +131,7 @@ def cycle_en():
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]:

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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
DUT = test_ptp_clock
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

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

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
of this software and associated documentation files (the "Software"), to deal
@@ -34,8 +34,8 @@ module test_ptp_clock
input wire clk,
input wire rst,
inout wire [95:0] ts_96,
inout wire [63:0] ts_64,
inout wire [95:0] ts_tod,
inout wire [63:0] ts_rel,
inout wire ts_step,
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
DUT = test_ptp_clock_sim_time
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -25,12 +25,13 @@ THE SOFTWARE.
import logging
import os
from decimal import Decimal
import cocotb_test.simulator
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotb.triggers import RisingEdge, ClockCycles
from cocotb.utils import get_sim_time
from cocotbext.eth import PtpClockSimTime
@@ -43,15 +44,23 @@ class TB:
self.log = logging.getLogger("cocotb.tb")
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(
ts_96=dut.ts_96,
ts_64=dut.ts_64,
ts_tod=dut.ts_tod,
ts_rel=dut.ts_rel,
pps=dut.pps,
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()
async def run_test(dut):
@@ -62,32 +71,32 @@ async def run_test(dut):
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
start_time = get_sim_time('sec')
start_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9)
start_ts_64 = dut.ts_64.value.integer/2**16*1e-9
start_time = Decimal(get_sim_time('fs')).scaleb(-6)
start_ts_tod = tb.get_ts_tod_ns()
start_ts_rel = tb.get_ts_rel_ns()
for k in range(10000):
await RisingEdge(dut.clk)
await ClockCycles(dut.clk, 10000)
stop_time = get_sim_time('sec')
stop_ts_96 = (dut.ts_96.value.integer >> 48) + ((dut.ts_96.value.integer & 0xffffffffffff)/2**16*1e-9)
stop_ts_64 = dut.ts_64.value.integer/2**16*1e-9
stop_time = Decimal(get_sim_time('fs')).scaleb(-6)
stop_ts_tod = tb.get_ts_tod_ns()
stop_ts_rel = tb.get_ts_rel_ns()
time_delta = stop_time-start_time
ts_96_delta = stop_ts_96-start_ts_96
ts_64_delta = stop_ts_64-start_ts_64
ts_tod_delta = stop_ts_tod-start_ts_tod
ts_rel_delta = stop_ts_rel-start_ts_rel
ts_96_diff = time_delta - ts_96_delta
ts_64_diff = time_delta - ts_64_delta
tb.log.info("sim time delta : %s ns", time_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)
tb.log.info("96 bit ts delta : %g s", ts_96_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)
ts_tod_diff = time_delta - ts_tod_delta
ts_rel_diff = time_delta - ts_rel_delta
assert abs(ts_96_diff) < 1e-12
assert abs(ts_64_diff) < 1e-12
tb.log.info("ToD ts diff : %s ns", ts_tod_diff)
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)

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
of this software and associated documentation files (the "Software"), to deal
@@ -33,8 +33,8 @@ module test_ptp_clock_sim_time
(
input wire clk,
inout wire [95:0] ts_96,
inout wire [63:0] ts_64,
inout wire [95:0] ts_tod,
inout wire [63:0] ts_rel,
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_rgmii
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,7 @@ class TB:
self._enable_generator = 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.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)
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)
self.dut.rst <= 0
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
@@ -74,15 +74,17 @@ class TB:
self._enable_generator = generator
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):
self.set_enable_generator(None)
async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator:
self.dut.rgmii_clk_en <= val
await RisingEdge(self.dut.clk)
self.dut.rgmii_clk_en.value = val
await clock_edge_event
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.source.ifg = ifg
tb.dut.rgmii_mii_sel <= mii_sel
tb.dut.rgmii_mii_sel.value = mii_sel
if enable_gen is not None:
tb.set_enable_generator(enable_gen())
@@ -128,7 +130,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME:
if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test)
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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,35 +27,23 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_rgmii_phy
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
ifeq ($(SIM), icarus)
PLUSARGS += -fst
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
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)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -45,11 +45,11 @@ class TB:
self.log.setLevel(logging.DEBUG)
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:
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:
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,
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)
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)
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)
@@ -144,7 +144,7 @@ def cycle_en():
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]:

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
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
# of this software and associated documentation files (the "Software"), to deal
@@ -27,45 +27,27 @@ COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ns
DUT = test_xgmii
TOPLEVEL = $(DUT)
MODULE = $(DUT)
COCOTB_TEST_MODULES = $(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(DUT).v
# module parameters
export PARAM_DATA_WIDTH ?= 64
export PARAM_CTRL_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
export PARAM_DATA_WIDTH := 64
export PARAM_CTRL_WIDTH := $(shell expr $(PARAM_DATA_WIDTH) / 8 )
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
COMPILE_ARGS += -P $(TOPLEVEL).CTRL_WIDTH=$(PARAM_CTRL_WIDTH)
ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v
COMPILE_ARGS += -s iverilog_dump
endif
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
COMPILE_ARGS += -GCTRL_WIDTH=$(PARAM_CTRL_WIDTH)
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim
iverilog_dump.v:
echo 'module iverilog_dump();' > $@
echo 'initial begin' >> $@
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
echo 'end' >> $@
echo 'endmodule' >> $@
clean::
@rm -rf iverilog_dump.v
@rm -rf dump.fst $(TOPLEVEL).fst

View File

@@ -1,7 +1,7 @@
#!/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
of this software and associated documentation files (the "Software"), to deal
@@ -48,7 +48,7 @@ class TB:
self._enable_generator = 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.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)
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)
self.dut.rst <= 0
self.dut.rst.value = 0
await RisingEdge(self.dut.clk)
await RisingEdge(self.dut.clk)
@@ -74,15 +74,17 @@ class TB:
self._enable_generator = generator
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):
self.set_enable_generator(None)
async def _run_enable(self):
clock_edge_event = RisingEdge(self.dut.clk)
for val in self._enable_generator:
self.dut.xgmii_clk_en <= val
await RisingEdge(self.dut.clk)
self.dut.xgmii_clk_en.value = val
await clock_edge_event
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)
byte_width = tb.source.width // 8
byte_lanes = tb.source.byte_lanes
tb.source.ifg = ifg
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:
if ifg == 0:
lane = 0
if force_offset_start and byte_width > 4:
if force_offset_start and byte_lanes > 4:
lane = 4
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:
offset = lane % 4
if deficit_idle_count+offset >= 4:
offset += 4
lane = (lane - offset) % byte_width
lane = (lane - offset) % byte_lanes
deficit_idle_count = (deficit_idle_count + offset) % 4
else:
offset = lane % 4
if offset > 0:
offset += 4
lane = (lane - offset) % byte_width
lane = (lane - offset) % byte_lanes
tb.log.info("start_lane_ref: %s", start_lane_ref)
@@ -206,7 +208,7 @@ def cycle_en():
return itertools.cycle([0, 0, 0, 1])
if cocotb.SIM_NAME:
if getattr(cocotb, 'top', None) is not None:
factory = TestFactory(run_test)
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
of this software and associated documentation files (the "Software"), to deal