49 Commits

Author SHA1 Message Date
c2b5934cc9 Cocotb 2.0 changes
Some checks are pending
build / Build distributions (push) Successful in 8s
build / deploy (push) Has been skipped
Regression Tests / Python 3.10 (push) Waiting to run
Regression Tests / Python 3.11 (push) Waiting to run
Regression Tests / Python 3.12 (push) Waiting to run
Regression Tests / Python 3.13 (push) Waiting to run
Regression Tests / Python 3.8 (push) Waiting to run
Regression Tests / Python 3.9 (push) Waiting to run
build / Build distributions (release) Successful in 8s
build / deploy (release) Successful in 7s
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
45 changed files with 1118 additions and 857 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

112
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
@@ -585,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,
@@ -603,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
@@ -623,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,8 +43,8 @@ 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
@@ -53,7 +53,7 @@ class EthMacFrame:
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
@@ -61,7 +61,7 @@ class EthMacFrame:
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
@@ -133,11 +133,14 @@ class EthMacTx(Reset):
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)
@@ -176,7 +179,7 @@ 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:
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")
@@ -191,11 +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_tag:
if self.ptp_ts_tag is not None:
self.ptp_ts_tag.setimmediatevalue(0)
if self.ptp_ts_valid:
if self.ptp_ts_valid is not None:
self.ptp_ts_valid.setimmediatevalue(0)
self._run_cr = None
@@ -252,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
@@ -262,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
@@ -274,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
@@ -282,9 +287,9 @@ class EthMacTx(Reset):
frame.sim_time_sfd = get_sim_time()
if self.ptp_time:
frame.ptp_timestamp = self.ptp_time.value.integer
frame.ptp_tag = cycle.tuser.integer >> 1
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
@@ -292,14 +297,15 @@ class EthMacTx(Reset):
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)
@@ -322,16 +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, tag = self.ts_queue.get_nowait()
self.ptp_ts <= ts
self.ptp_ts.value = ts
if self.ptp_ts_tag is not None:
self.ptp_ts_tag <= tag
self.ptp_ts_valid <= 1
self.ptp_ts_tag.value = tag
self.ptp_ts_valid.value = 1
class EthMacRx(Reset):
@@ -344,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)
@@ -391,7 +402,7 @@ 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:
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")
@@ -475,7 +486,7 @@ 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
@@ -502,8 +513,8 @@ class EthMacRx(Reset):
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

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,10 +255,11 @@ 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
@@ -264,10 +269,16 @@ class GmiiSource(Reset):
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
@@ -286,7 +297,7 @@ 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:
# convert to MII
@@ -308,10 +319,10 @@ class GmiiSource(Reset):
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[frame_offset]
self.dv <= 1
self.er.value = frame_error[frame_offset]
self.dv.value = 1
frame_offset += 1
if frame_offset >= len(frame_data):
@@ -321,12 +332,19 @@ class GmiiSource(Reset):
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):
@@ -343,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)
@@ -422,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:
@@ -446,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
@@ -488,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,
@@ -517,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
@@ -536,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,10 +156,11 @@ 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
@@ -165,10 +170,16 @@ class MiiSource(Reset):
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
@@ -202,10 +213,10 @@ class MiiSource(Reset):
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[frame_offset]
self.dv <= 1
self.er.value = frame_error[frame_offset]
self.dv.value = 1
frame_offset += 1
if frame_offset >= len(frame_data):
@@ -215,12 +226,19 @@ class MiiSource(Reset):
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):
@@ -236,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)
@@ -313,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:
@@ -374,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,
@@ -402,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')
@@ -410,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,10 +154,11 @@ 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
@@ -161,22 +166,39 @@ class RgmiiSource(Reset):
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
@@ -192,7 +214,7 @@ 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:
# convert to MII
@@ -221,6 +243,7 @@ class RgmiiSource(Reset):
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
@@ -230,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):
@@ -254,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)
@@ -323,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
@@ -339,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:
@@ -364,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
@@ -406,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,
@@ -434,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
@@ -448,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.16"
__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
@@ -200,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
@@ -209,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
@@ -236,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
@@ -250,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)
@@ -260,10 +264,11 @@ 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
@@ -272,10 +277,16 @@ class XgmiiSource(Reset):
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:
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_lanes
@@ -351,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):
@@ -372,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)
@@ -450,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:
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 = (self.data.value.integer >> (offset*8)) & 0xff
c_val = (self.ctrl.value.integer >> offset) & 1
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:
@@ -495,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,
@@ -64,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
)
@@ -81,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)
@@ -155,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()
@@ -171,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
@@ -182,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,29 +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 [16:0] 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 [15:0] tx_ptp_ts_tag,
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,
@@ -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