Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71dc00e850 | |||
| aa3605a55c | |||
| 0496147ddf | |||
|
|
3e1e7fc1ec | ||
|
|
698c29b05f | ||
|
|
33f510688a | ||
|
|
da00960112 | ||
|
|
88b6624a93 | ||
|
|
dcb9a6bd02 | ||
|
|
7136dddd0a | ||
|
|
6c15d7d57d | ||
|
|
4595bd8a08 | ||
|
|
204ad7a517 | ||
|
|
f3dbc07100 | ||
|
|
a0a5b7ee55 | ||
|
|
a28ec41f79 | ||
|
|
f2bf8c0ed8 | ||
|
|
28f4585c08 | ||
|
|
775301c6fe | ||
|
|
39b4ca4a93 | ||
|
|
f70731a8d8 | ||
|
|
28bc97f226 | ||
|
|
e816d6a088 | ||
|
|
af377b2c11 | ||
|
|
cfb52c6130 | ||
|
|
7e32e584ff | ||
|
|
50cf2af49f | ||
|
|
ddfa1e3c92 | ||
|
|
5e8b246159 | ||
|
|
62c2eef4ec | ||
|
|
ad6012aea5 | ||
|
|
432bd81011 | ||
|
|
bde123e05f | ||
|
|
8604017159 | ||
|
|
e21b9ffcc8 | ||
|
|
47cd74eb6c | ||
|
|
4bf5945aa3 | ||
|
|
f3a7652362 | ||
|
|
a84ce5447d | ||
|
|
1c03ec4697 | ||
|
|
824eba793d | ||
|
|
a0aad34698 | ||
|
|
ede6270ed7 | ||
|
|
cd1a8b47a5 | ||
|
|
be6d490adb | ||
|
|
39686b849a | ||
|
|
706051cb89 | ||
|
|
3e4f8d7e92 | ||
|
|
afae9e69ff | ||
|
|
035c1ba803 | ||
|
|
873bb1a034 | ||
|
|
2d70e5cbe5 | ||
|
|
35d9742ae8 | ||
|
|
0f20e2e9bf | ||
|
|
7606d7d7bd | ||
|
|
dd345e87c3 | ||
|
|
9c0592c16a | ||
|
|
8aab5a7294 | ||
|
|
4f26621e2b | ||
|
|
1b6993d80d | ||
|
|
6d9ed8a2d2 | ||
|
|
d772b73eb2 | ||
|
|
bd88eda17b | ||
|
|
53313699a9 | ||
|
|
3f7193b77c | ||
|
|
2b0b12c68d | ||
|
|
4a91212f37 |
63
.github/workflows/build.yaml
vendored
Normal file
63
.github/workflows/build.yaml
vendored
Normal 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/*
|
||||||
8
.github/workflows/regression-tests.yml
vendored
8
.github/workflows/regression-tests.yml
vendored
@@ -5,17 +5,17 @@ on: [push, pull_request]
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Python ${{matrix.python-version}}
|
name: Python ${{matrix.python-version}}
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
77
README.md
77
README.md
@@ -1,6 +1,6 @@
|
|||||||
# AXI interface modules for Cocotb
|
# AXI interface modules for Cocotb
|
||||||
|
|
||||||
[](https://github.com/alexforencich/cocotbext-axi/actions/)
|
[](https://github.com/alexforencich/cocotbext-axi/actions/workflows/regression-tests.yml)
|
||||||
[](https://codecov.io/gh/alexforencich/cocotbext-axi)
|
[](https://codecov.io/gh/alexforencich/cocotbext-axi)
|
||||||
[](https://pypi.org/project/cocotbext-axi)
|
[](https://pypi.org/project/cocotbext-axi)
|
||||||
[](https://pepy.tech/project/cocotbext-axi)
|
[](https://pepy.tech/project/cocotbext-axi)
|
||||||
@@ -9,7 +9,7 @@ GitHub repository: https://github.com/alexforencich/cocotbext-axi
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
AXI, AXI lite, and AXI stream simulation models for [cocotb](https://github.com/cocotb/cocotb).
|
AXI, AXI lite, AXI stream, and APB simulation models for [cocotb](https://github.com/cocotb/cocotb).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -28,13 +28,13 @@ Installation for active development:
|
|||||||
|
|
||||||
## Documentation and usage examples
|
## Documentation and usage examples
|
||||||
|
|
||||||
See the `tests` directory, [verilog-axi](https://github.com/alexforencich/verilog-axi), and [verilog-axis](https://github.com/alexforencich/verilog-axis) for complete testbenches using these modules.
|
See the `tests` directory, [taxi](https://github.com/fpganinja/taxi), [verilog-axi](https://github.com/alexforencich/verilog-axi), and [verilog-axis](https://github.com/alexforencich/verilog-axis) for complete testbenches using these modules.
|
||||||
|
|
||||||
### AXI and AXI lite master
|
### AXI, AXI lite, and APB master
|
||||||
|
|
||||||
The `AxiMaster` and `AxiLiteMaster` classes implement AXI masters and are capable of generating read and write operations against AXI slaves. Requested operations will be split and aligned according to the AXI specification. The `AxiMaster` module is capable of generating narrow bursts, handling multiple in-flight operations, and handling reordering and interleaving in responses across different transaction IDs. `AxiMaster` and `AxiLiteMaster` and related objects all extend `Region`, so they can be attached to `AddressSpace` objects to handle memory operations in the specified region.
|
The `AxiMaster`, `AxiLiteMaster`, and `ApbMaster` classes implement AXI, AXI-lite, and APB masters and are capable of generating read and write operations against the corresponding slaves. Requested operations will be split and aligned according to the AXI specification. The `AxiMaster` module is capable of generating narrow bursts, handling multiple in-flight operations, and handling reordering and interleaving in responses across different transaction IDs. `AxiMaster` and `AxiLiteMaster` and related objects all extend `Region`, so they can be attached to `AddressSpace` objects to handle memory operations in the specified region.
|
||||||
|
|
||||||
The `AxiMaster` is a wrapper around `AxiMasterWrite` and `AxiMasterRead`. Similarly, `AxiLiteMaster` is a wrapper around `AxiLiteMasterWrite` and `AxiLiteMasterRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.
|
The `AxiMaster` is a wrapper around `AxiMasterWrite` and `AxiMasterRead`. Similarly, `AxiLiteMaster` is a wrapper around `AxiLiteMasterWrite` and `AxiLiteMasterRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same. APB is not channelized, so only `ApbSlave` is available.
|
||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
@@ -64,9 +64,9 @@ Alternatively, operations can be initiated with non-blocking `init_read()` and `
|
|||||||
|
|
||||||
With this method, it is possible to start multiple concurrent operations from the same coroutine. It is also possible to use the events with `Combine`, `First`, and `with_timeout`.
|
With this method, it is possible to start multiple concurrent operations from the same coroutine. It is also possible to use the events with `Combine`, `First`, and `with_timeout`.
|
||||||
|
|
||||||
#### `AxiMaster` and `AxiLiteMaster` constructor parameters
|
#### `AxiMaster`, `AxiLiteMaster`, and `ApbMaster` constructor parameters
|
||||||
|
|
||||||
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
* _bus_: `AxiBus`, `AxiLiteBus`, or `ApbBus` object containing interface signals
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
* _reset_active_level_: reset active level (optional, default `True`)
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
@@ -114,20 +114,20 @@ With this method, it is possible to start multiple concurrent operations from th
|
|||||||
* _wuser_: AXI wuser signal, default `0` (write-related methods only)
|
* _wuser_: AXI wuser signal, default `0` (write-related methods only)
|
||||||
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
||||||
|
|
||||||
#### Additional optional arguments for `AxiLiteMaster`
|
#### Additional optional arguments for `AxiLiteMaster` and `ApbMaster`
|
||||||
|
|
||||||
* _prot_: AXI protection flags, default `AxiProt.NONSECURE`
|
* _prot_: AXI protection flags, default `AxiProt.NONSECURE`
|
||||||
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
||||||
|
|
||||||
#### `AxiBus` and `AxiLiteBus` objects
|
#### `AxiBus`, `AxiLiteBus`, and `ApbBus` objects
|
||||||
|
|
||||||
The `AxiBus`, `AxiLiteBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are currently extensions of `cocotb_bus.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate.
|
The `AxiBus`, `AxiLiteBus`, `ApbBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are currently extensions of `cocotb_bus.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate. For APB interfaces, use `ApbBus`.
|
||||||
|
|
||||||
### AXI and AXI lite slave
|
### AXI, AXI lite, and APB slave
|
||||||
|
|
||||||
The `AxiSlave` and `AxiLiteSlave` classes implement AXI slaves and are capable of completing read and write operations from upstream AXI masters. The `AxiSlave` module is capable of handling narrow bursts. These modules can either be used to perform memory reads and writes on a `MemoryInterface` on behalf of the DUT, or they can be extended to implement customized functionality.
|
The `AxiSlave`, `AxiLiteSlave`, and `ApbSlave` classes implement AXI, AXI-lite, and APB slaves and are capable of completing read and write operations from upstream AXI masters. The `AxiSlave` module is capable of handling narrow bursts. These modules can either be used to perform memory reads and writes on a `MemoryInterface` on behalf of the DUT, or they can be extended to implement customized functionality.
|
||||||
|
|
||||||
The `AxiSlave` is a wrapper around `AxiSlaveWrite` and `AxiSlaveRead`. Similarly, `AxiLiteSlave` is a wrapper around `AxiLiteSlaveWrite` and `AxiLiteSlaveRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.
|
The `AxiSlave` is a wrapper around `AxiSlaveWrite` and `AxiSlaveRead`. Similarly, `AxiLiteSlave` is a wrapper around `AxiLiteSlaveWrite` and `AxiLiteSlaveRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same. APB is not channelized, so only `ApbSlave` is available.
|
||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
@@ -137,13 +137,13 @@ To use these modules, import the one you need and connect it to the DUT:
|
|||||||
region = MemoryRegion(2**axi_slave.read_if.address_width)
|
region = MemoryRegion(2**axi_slave.read_if.address_width)
|
||||||
axi_slave.target = region
|
axi_slave.target = region
|
||||||
|
|
||||||
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
The first argument to the constructor accepts an `AxiBus`, `AxiLiteBus`, or `ApbBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||||
|
|
||||||
It is also possible to extend these modules; operation can be customized by overriding the internal `_read()` and `_write()` methods. See `AxiRam` and `AxiLiteRam` for an example.
|
It is also possible to extend these modules; operation can be customized by overriding the internal `_read()` and `_write()` methods. See `AxiRam` and `AxiLiteRam` for an example.
|
||||||
|
|
||||||
#### `AxiSlave` and `AxiLiteSlave` constructor parameters
|
#### `AxiSlave`, `AxiLiteSlave`, and `ApbSlave` constructor parameters
|
||||||
|
|
||||||
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
* _bus_: `AxiBus`, `AxiLiteBus`, or `ApbBus` object containing interface signals
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
* _reset_active_level_: reset active level (optional, default `True`)
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
@@ -153,21 +153,21 @@ It is also possible to extend these modules; operation can be customized by over
|
|||||||
|
|
||||||
* _target_: target region
|
* _target_: target region
|
||||||
|
|
||||||
### AXI and AXI lite RAM
|
### AXI, AXI lite, and APB RAM
|
||||||
|
|
||||||
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts. These modules are extensions of the corresponding `AxiSlave` and `AxiLiteSlave` modules.
|
The `AxiRam`, `AxiLiteRam`, and `ApbRam` classes implement AXI, AXI-lite, and APB RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts. These modules are extensions of the corresponding `AxiSlave`, `AxiLiteSlave`, and `ApbSlave` modules. Internally, `SparseMemory` is used to support emulating very large memories.
|
||||||
|
|
||||||
The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `AxiLiteRam` is a wrapper around `AxiLiteRamWrite` and `AxiLiteRamRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same.
|
The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `AxiLiteRam` is a wrapper around `AxiLiteRamWrite` and `AxiLiteRamRead`. If a read-only or write-only interface is required instead of a full interface, use the corresponding read-only or write-only variant, the usage and API are exactly the same. APB is not channelized, so only `ApbRam` is available.
|
||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
from cocotbext.axi import AxiBus, AxiRam
|
from cocotbext.axi import AxiBus, AxiRam
|
||||||
|
|
||||||
axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**16)
|
axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**32)
|
||||||
|
|
||||||
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
The first argument to the constructor accepts an `AxiBus`, `AxiLiteBus`, or `ApbBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||||
|
|
||||||
Once the module is instantiated, the memory contents can be accessed in a couple of different ways. First, the `mmap` object can be accessed directly via the `mem` attribute. Second, `read()`, `write()`, and various word-access wrappers are available. Hex dump helper methods are also provided for debugging. For example:
|
Once the module is instantiated, the memory contents can be accessed in a couple of different ways. First, the `mmap`/`SparseMemory` object can be accessed directly via the `mem` attribute. Second, `read()`, `write()`, and various word-access wrappers are available. Hex dump helper methods are also provided for debugging. For example:
|
||||||
|
|
||||||
axi_ram.write(0x0000, b'test')
|
axi_ram.write(0x0000, b'test')
|
||||||
data = axi_ram.read(0x0000, 4)
|
data = axi_ram.read(0x0000, 4)
|
||||||
@@ -175,23 +175,23 @@ Once the module is instantiated, the memory contents can be accessed in a couple
|
|||||||
|
|
||||||
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
||||||
|
|
||||||
axi_ram_p1 = AxiRam(AxiBus.from_prefix(dut, "m00_axi"), dut.clk, dut.rst, size=2**16)
|
axi_ram_p1 = AxiRam(AxiBus.from_prefix(dut, "m00_axi"), dut.clk, dut.rst, size=2**32)
|
||||||
axi_ram_p2 = AxiRam(AxiBus.from_prefix(dut, "m01_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
axi_ram_p2 = AxiRam(AxiBus.from_prefix(dut, "m01_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||||
axi_ram_p3 = AxiRam(AxiBus.from_prefix(dut, "m02_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
axi_ram_p3 = AxiRam(AxiBus.from_prefix(dut, "m02_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||||
axi_ram_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
axi_ram_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||||
|
|
||||||
#### `AxiRam` and `AxiLiteRam` constructor parameters
|
#### `AxiRam`, `AxiLiteRam`, and `ApbRam` constructor parameters
|
||||||
|
|
||||||
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
* _bus_: `AxiBus`, `AxiLiteBus`, or `ApbBus` object containing interface signals
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
* _reset_active_level_: reset active level (optional, default `True`)
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
* _size_: memory size in bytes (optional, default 1024)
|
* _size_: memory size in bytes (optional, default `2**64`)
|
||||||
* _mem_: mmap object to use (optional, overrides _size_)
|
* _mem_: `mmap` or `SparseMemory` backing object to use (optional, overrides _size_)
|
||||||
|
|
||||||
#### Attributes:
|
#### Attributes:
|
||||||
|
|
||||||
* _mem_: directly access shared `mmap` object
|
* _mem_: directly access shared `mmap` or `SparseMemory` backing object
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
@@ -334,6 +334,8 @@ The address space abstraction provides a framework for cross-connecting multiple
|
|||||||
|
|
||||||
`MemoryRegion` is an extension of `Region` that uses an `mmap` instance to handle memory operations. `MemoryRegion` also provides hex dump methods as well as indexing and slicing.
|
`MemoryRegion` is an extension of `Region` that uses an `mmap` instance to handle memory operations. `MemoryRegion` also provides hex dump methods as well as indexing and slicing.
|
||||||
|
|
||||||
|
`SparseMemoryRegion` is similar to `MemoryRegion` but is backed by `SparseMemory` instead of `mmap` and as such can emulate extremely large regions of address space.
|
||||||
|
|
||||||
`PeripheralRegion` is an extension of `Region` that can wrap another object that implements `read()` and `write()`, as an alternative to extending `Region`.
|
`PeripheralRegion` is an extension of `Region` that can wrap another object that implements `read()` and `write()`, as an alternative to extending `Region`.
|
||||||
|
|
||||||
`AddressSpace` is the core object for handling address spaces. `Region` objects can be registered with `AddressSpace` with specified base address, size, and offset. The `AddressSpace` object will then direct `read()` and `write()` operations to the appropriate `Region`s, splitting requests appropriately when necessary and translating addresses. Regions registered with `offset` other than `None` are translated such that accesses to base address + N map to N + offset. Regions registered with an `offset` of `None` are not translated. `Region` objects registered with the same `AddressSpace` cannot overlap, however the same `Region` can be registered multiple times. `AddressSpace` also provides a method for creating `Pool` objects.
|
`AddressSpace` is the core object for handling address spaces. `Region` objects can be registered with `AddressSpace` with specified base address, size, and offset. The `AddressSpace` object will then direct `read()` and `write()` operations to the appropriate `Region`s, splitting requests appropriately when necessary and translating addresses. Regions registered with `offset` other than `None` are translated such that accesses to base address + N map to N + offset. Regions registered with an `offset` of `None` are not translated. `Region` objects registered with the same `AddressSpace` cannot overlap, however the same `Region` can be registered multiple times. `AddressSpace` also provides a method for creating `Pool` objects.
|
||||||
@@ -344,14 +346,14 @@ The address space abstraction provides a framework for cross-connecting multiple
|
|||||||
|
|
||||||
This is a simple example that shows how the address space abstraction components can be used to connect a DUT to a simulated host system, including simulated RAM, an AXI interface from the DUT for DMA, and an AXI lite interface to the DUT for control.
|
This is a simple example that shows how the address space abstraction components can be used to connect a DUT to a simulated host system, including simulated RAM, an AXI interface from the DUT for DMA, and an AXI lite interface to the DUT for control.
|
||||||
|
|
||||||
from cocotbext.axi import AddressSpace, MemoryRegion
|
from cocotbext.axi import AddressSpace, SparseMemoryRegion
|
||||||
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave
|
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave
|
||||||
|
|
||||||
# system address space
|
# system address space
|
||||||
address_space = AddressSpace(2**32)
|
address_space = AddressSpace(2**32)
|
||||||
|
|
||||||
# RAM
|
# RAM
|
||||||
ram = MemoryRegion(2**24)
|
ram = SparseMemoryRegion(2**24)
|
||||||
address_space.register_region(ram, 0x0000_0000)
|
address_space.register_region(ram, 0x0000_0000)
|
||||||
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
|
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
|
||||||
|
|
||||||
@@ -469,3 +471,16 @@ This is a simple example that shows how the address space abstraction components
|
|||||||
* `tid`: ID signal, can be used for routing
|
* `tid`: ID signal, can be used for routing
|
||||||
* `tdest`: destination signal, can be used for routing
|
* `tdest`: destination signal, can be used for routing
|
||||||
* `tuser`: additional sideband data
|
* `tuser`: additional sideband data
|
||||||
|
|
||||||
|
### APB signals
|
||||||
|
|
||||||
|
* `paddr`: address
|
||||||
|
* `pprot`: protection bits
|
||||||
|
* `psel`: select signal, for selecting a target device
|
||||||
|
* `penable`: enable signal, for performing an operation
|
||||||
|
* `pwrite`: read/write control signal
|
||||||
|
* `pwdata`: write data
|
||||||
|
* `pstrb`: write strobe
|
||||||
|
* `pready`: ready signal to stall bus
|
||||||
|
* `prdata`: read data
|
||||||
|
* `pslverr`: read/write response, indicating SLVERR
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,7 +27,7 @@ from .version import __version__
|
|||||||
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
||||||
|
|
||||||
from .address_space import MemoryInterface, Window, WindowPool
|
from .address_space import MemoryInterface, Window, WindowPool
|
||||||
from .address_space import Region, MemoryRegion, PeripheralRegion
|
from .address_space import Region, MemoryRegion, SparseMemoryRegion, PeripheralRegion
|
||||||
from .address_space import AddressSpace, Pool
|
from .address_space import AddressSpace, Pool
|
||||||
|
|
||||||
from .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
from .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||||
@@ -43,3 +43,5 @@ from .axi_channels import AxiWriteBus, AxiReadBus, AxiBus
|
|||||||
from .axi_master import AxiMasterWrite, AxiMasterRead, AxiMaster
|
from .axi_master import AxiMasterWrite, AxiMasterRead, AxiMaster
|
||||||
from .axi_slave import AxiSlaveWrite, AxiSlaveRead, AxiSlave
|
from .axi_slave import AxiSlaveWrite, AxiSlaveRead, AxiSlave
|
||||||
from .axi_ram import AxiRamWrite, AxiRamRead, AxiRam
|
from .axi_ram import AxiRamWrite, AxiRamRead, AxiRam
|
||||||
|
|
||||||
|
from .apb import ApbBus, ApbMaster, ApbSlave, ApbRam
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -25,6 +25,7 @@ THE SOFTWARE.
|
|||||||
import mmap
|
import mmap
|
||||||
|
|
||||||
from .buddy_allocator import BuddyAllocator
|
from .buddy_allocator import BuddyAllocator
|
||||||
|
from .sparse_memory import SparseMemory
|
||||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ class MemoryInterface:
|
|||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._size = size
|
self._size = size
|
||||||
self._base = base
|
self._base = base
|
||||||
|
self.window_type = Window
|
||||||
|
self.window_pool_type = WindowPool
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -125,17 +128,22 @@ class MemoryInterface:
|
|||||||
async def write_qword(self, address, data, byteorder='little', **kwargs):
|
async def write_qword(self, address, data, byteorder='little', **kwargs):
|
||||||
await self.write_qwords(address, [data], byteorder, **kwargs)
|
await self.write_qwords(address, [data], byteorder, **kwargs)
|
||||||
|
|
||||||
def create_window(self, offset, size):
|
def create_window(self, offset, size=None, window_type=None):
|
||||||
|
if not size or size < 0:
|
||||||
|
size = self.size - offset
|
||||||
|
window_type = window_type or self.window_type or Window
|
||||||
self.check_range(offset, size)
|
self.check_range(offset, size)
|
||||||
return Window(self, offset, size, base=self.get_absolute_address(offset))
|
return window_type(self, offset, size, base=self.get_absolute_address(offset))
|
||||||
|
|
||||||
def create_window_pool(self, offset=None, size=None):
|
def create_window_pool(self, offset=None, size=None, window_pool_type=None, window_type=None):
|
||||||
if offset is None:
|
if offset is None:
|
||||||
offset = 0
|
offset = 0
|
||||||
if size is None:
|
if size is None:
|
||||||
size = self.size - offset
|
size = self.size - offset
|
||||||
|
window_pool_type = window_pool_type or self.window_pool_type or WindowPool
|
||||||
|
window_type = window_type or self.window_type
|
||||||
self.check_range(offset, size)
|
self.check_range(offset, size)
|
||||||
return WindowPool(self, offset, size, base=self.get_absolute_address(offset))
|
return window_pool_type(self, offset, size, base=self.get_absolute_address(offset), window_type=window_type)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return self._size
|
return self._size
|
||||||
@@ -163,12 +171,13 @@ class Window(MemoryInterface):
|
|||||||
|
|
||||||
|
|
||||||
class WindowPool(Window):
|
class WindowPool(Window):
|
||||||
def __init__(self, parent, offset, size, base=None, **kwargs):
|
def __init__(self, parent, offset, size, base=None, window_type=None, **kwargs):
|
||||||
super().__init__(parent, offset, size, base=base, **kwargs)
|
super().__init__(parent, offset, size, base=base, **kwargs)
|
||||||
|
self.window_type = window_type or Window
|
||||||
self.allocator = BuddyAllocator(size)
|
self.allocator = BuddyAllocator(size)
|
||||||
|
|
||||||
def alloc_window(self, size):
|
def alloc_window(self, size, window_type=None):
|
||||||
return self.create_window(self.allocator.alloc(size), size)
|
return self.create_window(self.allocator.alloc(size), size, window_type)
|
||||||
|
|
||||||
|
|
||||||
class Region(MemoryInterface):
|
class Region(MemoryInterface):
|
||||||
@@ -208,6 +217,35 @@ class MemoryRegion(Region):
|
|||||||
return bytes(self.mem)
|
return bytes(self.mem)
|
||||||
|
|
||||||
|
|
||||||
|
class SparseMemoryRegion(Region):
|
||||||
|
def __init__(self, size=2**64, mem=None, **kwargs):
|
||||||
|
super().__init__(size, **kwargs)
|
||||||
|
if mem is None:
|
||||||
|
mem = SparseMemory(size)
|
||||||
|
self.mem = mem
|
||||||
|
|
||||||
|
async def _read(self, address, length, **kwargs):
|
||||||
|
return self.mem.read(address, length)
|
||||||
|
|
||||||
|
async def _write(self, address, data, **kwargs):
|
||||||
|
self.mem.write(address, data)
|
||||||
|
|
||||||
|
def hexdump(self, address, length, prefix=""):
|
||||||
|
self.mem.hexdump(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def hexdump_lines(self, address, length, prefix=""):
|
||||||
|
return self.mem.hexdump_lines(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def hexdump_str(self, address, length, prefix=""):
|
||||||
|
return self.mem.hexdump_str(address, length, prefix=prefix)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.mem[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.mem[key] = value
|
||||||
|
|
||||||
|
|
||||||
class PeripheralRegion(Region):
|
class PeripheralRegion(Region):
|
||||||
def __init__(self, obj, size, **kwargs):
|
def __init__(self, obj, size, **kwargs):
|
||||||
super().__init__(size, **kwargs)
|
super().__init__(size, **kwargs)
|
||||||
@@ -229,6 +267,7 @@ class PeripheralRegion(Region):
|
|||||||
class AddressSpace(Region):
|
class AddressSpace(Region):
|
||||||
def __init__(self, size=2**64, base=0, parent=None, **kwargs):
|
def __init__(self, size=2**64, base=0, parent=None, **kwargs):
|
||||||
super().__init__(size=size, base=base, parent=parent, **kwargs)
|
super().__init__(size=size, base=base, parent=parent, **kwargs)
|
||||||
|
self.pool_type = Pool
|
||||||
self.regions = []
|
self.regions = []
|
||||||
|
|
||||||
def find_regions(self, address, length=1):
|
def find_regions(self, address, length=1):
|
||||||
@@ -297,24 +336,27 @@ class AddressSpace(Region):
|
|||||||
if length > 0:
|
if length > 0:
|
||||||
raise Exception("Invalid address")
|
raise Exception("Invalid address")
|
||||||
|
|
||||||
def create_pool(self, base=None, size=None):
|
def create_pool(self, base=None, size=None, pool_type=None, region_type=None):
|
||||||
if base is None:
|
if base is None:
|
||||||
base = 0
|
base = 0
|
||||||
if size is None:
|
if size is None:
|
||||||
size = self.size - base
|
size = self.size - base
|
||||||
|
pool_type = pool_type or self.pool_type or Pool
|
||||||
self.check_range(base, size)
|
self.check_range(base, size)
|
||||||
pool = Pool(self, base, size)
|
pool = pool_type(self, base, size, region_type=region_type)
|
||||||
self.register_region(pool, base, size)
|
self.register_region(pool, base, size)
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
|
|
||||||
class Pool(AddressSpace):
|
class Pool(AddressSpace):
|
||||||
def __init__(self, parent, base, size, **kwargs):
|
def __init__(self, parent, base, size, region_type=None, **kwargs):
|
||||||
super().__init__(parent=parent, base=base, size=size, **kwargs)
|
super().__init__(parent=parent, base=base, size=size, **kwargs)
|
||||||
|
self.region_type = region_type or MemoryRegion
|
||||||
self.allocator = BuddyAllocator(size)
|
self.allocator = BuddyAllocator(size)
|
||||||
|
|
||||||
def alloc_region(self, size):
|
def alloc_region(self, size, region_type=None):
|
||||||
|
region_type = region_type or self.region_type or MemoryRegion
|
||||||
base = self.allocator.alloc(size)
|
base = self.allocator.alloc(size)
|
||||||
region = MemoryRegion(size)
|
region = region_type(size)
|
||||||
self.register_region(region, base)
|
self.register_region(region, base)
|
||||||
return region
|
return region
|
||||||
|
|||||||
623
cocotbext/axi/apb.py
Normal file
623
cocotbext/axi/apb.py
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 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
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.queue import Queue
|
||||||
|
from cocotb.triggers import RisingEdge, Event
|
||||||
|
from cocotb_bus.bus import Bus
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
from .constants import AxiResp, AxiProt
|
||||||
|
from .address_space import Region
|
||||||
|
from .reset import Reset
|
||||||
|
from .memory import Memory
|
||||||
|
|
||||||
|
|
||||||
|
# APB master write helper objects
|
||||||
|
class ApbWriteCmd(NamedTuple):
|
||||||
|
address: int
|
||||||
|
data: bytes
|
||||||
|
prot: AxiProt
|
||||||
|
event: Event
|
||||||
|
|
||||||
|
|
||||||
|
class ApbWriteResp(NamedTuple):
|
||||||
|
address: int
|
||||||
|
length: int
|
||||||
|
resp: AxiResp
|
||||||
|
|
||||||
|
|
||||||
|
# APB master read helper objects
|
||||||
|
class ApbReadCmd(NamedTuple):
|
||||||
|
address: int
|
||||||
|
length: int
|
||||||
|
prot: AxiProt
|
||||||
|
event: Event
|
||||||
|
|
||||||
|
|
||||||
|
class ApbReadResp(NamedTuple):
|
||||||
|
address: int
|
||||||
|
data: bytes
|
||||||
|
resp: AxiResp
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
|
class ApbBus(Bus):
|
||||||
|
|
||||||
|
_signals = ["paddr", "psel", "penable", "pwrite", "pwdata", "pstrb", "pready", "prdata"]
|
||||||
|
_optional_signals = ["pprot", "pslverr"]
|
||||||
|
|
||||||
|
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||||
|
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_entity(cls, entity, **kwargs):
|
||||||
|
return cls(entity, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_prefix(cls, entity, prefix, **kwargs):
|
||||||
|
return cls(entity, prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ApbPause:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self._pause = False
|
||||||
|
self._pause_generator = None
|
||||||
|
self._pause_cr = None
|
||||||
|
|
||||||
|
def _pause_update(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pause(self):
|
||||||
|
return self._pause
|
||||||
|
|
||||||
|
@pause.setter
|
||||||
|
def pause(self, val):
|
||||||
|
if self._pause != val:
|
||||||
|
self._pause_update(val)
|
||||||
|
self._pause = val
|
||||||
|
|
||||||
|
def set_pause_generator(self, generator=None):
|
||||||
|
if self._pause_cr is not None:
|
||||||
|
self._pause_cr.kill()
|
||||||
|
self._pause_cr = None
|
||||||
|
|
||||||
|
self._pause_generator = generator
|
||||||
|
|
||||||
|
if self._pause_generator is not None:
|
||||||
|
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||||
|
|
||||||
|
def clear_pause_generator(self):
|
||||||
|
self.set_pause_generator(None)
|
||||||
|
|
||||||
|
async def _run_pause(self):
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
for val in self._pause_generator:
|
||||||
|
self.pause = val
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
|
|
||||||
|
class ApbMaster(ApbPause, Region, Reset):
|
||||||
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, **kwargs):
|
||||||
|
self.bus = bus
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
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("APB master")
|
||||||
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
|
self.log.info("Copyright (c) 2025 Alex Forencich")
|
||||||
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
|
self.command_queue = Queue()
|
||||||
|
self.command_queue.queue_occupancy_limit = 2
|
||||||
|
self.current_command = None
|
||||||
|
|
||||||
|
self.read_resp = None
|
||||||
|
self.write_resp = None
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle = Event()
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
|
self.address_width = len(self.bus.paddr)
|
||||||
|
self.width = len(self.bus.pwdata)
|
||||||
|
self.byte_size = 8
|
||||||
|
self.byte_lanes = self.width // self.byte_size
|
||||||
|
self.strb_mask = 2**self.byte_lanes-1
|
||||||
|
|
||||||
|
self.pprot_present = hasattr(self.bus, "pprot")
|
||||||
|
self.pstrb_present = hasattr(self.bus, "pstrb")
|
||||||
|
self.pslverr_present = hasattr(self.bus, "pslverr")
|
||||||
|
|
||||||
|
super().__init__(2**self.address_width, **kwargs)
|
||||||
|
|
||||||
|
self.log.info("APB master configuration:")
|
||||||
|
self.log.info(" Address width: %d bits", self.address_width)
|
||||||
|
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||||
|
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||||
|
|
||||||
|
self.log.info("APB master signals:")
|
||||||
|
for sig in sorted(list(set().union(self.bus._signals, self.bus._optional_signals))):
|
||||||
|
if hasattr(bus, sig):
|
||||||
|
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||||
|
else:
|
||||||
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
|
if self.pstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.bus.pstrb)
|
||||||
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
|
self.bus.paddr.setimmediatevalue(0)
|
||||||
|
if self.pprot_present:
|
||||||
|
self.bus.pprot.setimmediatevalue(0)
|
||||||
|
self.bus.psel.setimmediatevalue(False)
|
||||||
|
self.bus.penable.setimmediatevalue(False)
|
||||||
|
self.bus.pwrite.setimmediatevalue(False)
|
||||||
|
self.bus.pwdata.setimmediatevalue(0)
|
||||||
|
if self.pstrb_present:
|
||||||
|
self.bus.pstrb.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
|
||||||
|
if event is None:
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
if not isinstance(event, Event):
|
||||||
|
raise ValueError("Expected event object")
|
||||||
|
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if isinstance(data, int):
|
||||||
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.pprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("pprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
data = bytes(data)
|
||||||
|
|
||||||
|
cocotb.start_soon(self._write_wrapper(address, bytes(data), prot, event))
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
||||||
|
if event is None:
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
if not isinstance(event, Event):
|
||||||
|
raise ValueError("Expected event object")
|
||||||
|
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.pprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
cocotb.start_soon(self._read_wrapper(address, length, prot, event))
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def idle(self):
|
||||||
|
return not self.in_flight_operations
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
while not self.idle():
|
||||||
|
await self._idle.wait()
|
||||||
|
|
||||||
|
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if isinstance(data, int):
|
||||||
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.pprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("pprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
data = bytes(data)
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
await self.command_queue.put(ApbWriteCmd(address, data, prot, event))
|
||||||
|
await event.wait()
|
||||||
|
return self.write_resp
|
||||||
|
|
||||||
|
async def _write_wrapper(self, address, data, prot, event):
|
||||||
|
event.set(await self.write(address, data, prot))
|
||||||
|
|
||||||
|
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.pprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
await self.command_queue.put(ApbReadCmd(address, length, prot, event))
|
||||||
|
|
||||||
|
await event.wait()
|
||||||
|
return self.read_resp
|
||||||
|
|
||||||
|
async def _read_wrapper(self, address, length, prot, event):
|
||||||
|
event.set(await self.read(address, length, prot))
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
|
||||||
|
self.bus.psel.value = False
|
||||||
|
self.bus.penable.value = False
|
||||||
|
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
def flush_cmd(cmd):
|
||||||
|
self.log.warning("Flushed write operation during reset: %s", cmd)
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.command_queue.empty():
|
||||||
|
cmd = self.command_queue.get_nowait()
|
||||||
|
flush_cmd(cmd)
|
||||||
|
|
||||||
|
if self.current_command:
|
||||||
|
cmd = self.current_command
|
||||||
|
self.current_command = None
|
||||||
|
flush_cmd(cmd)
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle.set()
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._run_cr is None:
|
||||||
|
self._run_cr = cocotb.start_soon(self._run())
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
cmd = await self.command_queue.get()
|
||||||
|
self.current_command = cmd
|
||||||
|
|
||||||
|
length = 0
|
||||||
|
pwrite = False
|
||||||
|
|
||||||
|
if isinstance(cmd, ApbWriteCmd):
|
||||||
|
length = len(cmd.data)
|
||||||
|
pwrite = True
|
||||||
|
else:
|
||||||
|
length = cmd.length
|
||||||
|
pwrite = False
|
||||||
|
|
||||||
|
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
|
||||||
|
|
||||||
|
start_offset = cmd.address % self.byte_lanes
|
||||||
|
end_offset = ((cmd.address + length - 1) % self.byte_lanes) + 1
|
||||||
|
|
||||||
|
strb_start = (self.strb_mask << start_offset) & self.strb_mask
|
||||||
|
strb_end = self.strb_mask >> (self.byte_lanes - end_offset)
|
||||||
|
|
||||||
|
cycles = (length + (cmd.address % self.byte_lanes) + self.byte_lanes-1) // self.byte_lanes
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
read_data = bytearray()
|
||||||
|
resp = AxiResp.OKAY
|
||||||
|
|
||||||
|
if self.log.isEnabledFor(logging.INFO):
|
||||||
|
if pwrite:
|
||||||
|
self.log.info("Write start addr: 0x%08x prot: %s data: %s",
|
||||||
|
cmd.address, cmd.prot, ' '.join((f'{c:02x}' for c in cmd.data)))
|
||||||
|
else:
|
||||||
|
self.log.info("Read start addr: 0x%08x prot: %s length: %d",
|
||||||
|
cmd.address, cmd.prot, cmd.length)
|
||||||
|
|
||||||
|
await clock_edge_event
|
||||||
|
self.bus.psel.value = True
|
||||||
|
|
||||||
|
for k in range(cycles):
|
||||||
|
start = 0
|
||||||
|
stop = self.byte_lanes
|
||||||
|
strb = self.strb_mask
|
||||||
|
|
||||||
|
if k == 0:
|
||||||
|
start = start_offset
|
||||||
|
strb &= strb_start
|
||||||
|
if k == cycles-1:
|
||||||
|
stop = end_offset
|
||||||
|
strb &= strb_end
|
||||||
|
|
||||||
|
val = 0
|
||||||
|
if pwrite:
|
||||||
|
for j in range(start, stop):
|
||||||
|
val |= cmd.data[offset] << j*8
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
if not self.pstrb_present and strb != self.strb_mask:
|
||||||
|
self.log.warning("Partial operation requested with pstrb not connected, write will be zero-padded (0x%x != 0x%x)", strb, self.strb_mask)
|
||||||
|
else:
|
||||||
|
strb = 0
|
||||||
|
|
||||||
|
while self.pause:
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
|
if k == 0:
|
||||||
|
self.bus.paddr.value = cmd.address
|
||||||
|
else:
|
||||||
|
self.bus.paddr.value = word_addr + k*self.byte_lanes
|
||||||
|
self.bus.pprot.value = cmd.prot
|
||||||
|
self.bus.penable.value = True
|
||||||
|
self.bus.pwrite.value = pwrite
|
||||||
|
self.bus.pwdata.value = val
|
||||||
|
self.bus.pstrb.value = strb
|
||||||
|
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
|
while not int(self.bus.pready.value):
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
|
self.bus.penable.value = False
|
||||||
|
|
||||||
|
cycle_data = int(self.bus.prdata.value)
|
||||||
|
if self.pslverr_present and int(self.bus.pslverr.value):
|
||||||
|
resp = AxiResp.SLVERR
|
||||||
|
|
||||||
|
start = 0
|
||||||
|
stop = self.byte_lanes
|
||||||
|
|
||||||
|
if k == 0:
|
||||||
|
start = start_offset
|
||||||
|
if k == cycles-1:
|
||||||
|
stop = end_offset
|
||||||
|
|
||||||
|
for j in range(start, stop):
|
||||||
|
read_data.append((cycle_data >> j*8) & 0xff)
|
||||||
|
|
||||||
|
self.bus.psel.value = False
|
||||||
|
|
||||||
|
if pwrite:
|
||||||
|
self.log.info("Write complete addr: 0x%08x prot: %s resp: %s length: %d",
|
||||||
|
cmd.address, cmd.prot, resp, length)
|
||||||
|
self.write_resp = ApbWriteResp(cmd.address, length, resp)
|
||||||
|
cmd.event.set()
|
||||||
|
else:
|
||||||
|
if self.log.isEnabledFor(logging.INFO):
|
||||||
|
self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s",
|
||||||
|
cmd.address, cmd.prot, resp, ' '.join((f'{c:02x}' for c in read_data)))
|
||||||
|
self.read_resp = ApbReadResp(cmd.address, bytes(read_data), resp)
|
||||||
|
cmd.event.set()
|
||||||
|
|
||||||
|
self.current_write_command = None
|
||||||
|
|
||||||
|
self.in_flight_operations -= 1
|
||||||
|
|
||||||
|
if self.in_flight_operations == 0:
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
|
|
||||||
|
class ApbSlave(ApbPause, Reset):
|
||||||
|
def __init__(self, bus, clock, reset=None, target=None, reset_active_level=True, **kwargs):
|
||||||
|
self.bus = bus
|
||||||
|
self.clock = clock
|
||||||
|
self.reset = reset
|
||||||
|
self.target = target
|
||||||
|
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("APB slave model")
|
||||||
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
|
self.log.info("Copyright (c) 2025 Alex Forencich")
|
||||||
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.address_width = len(self.bus.paddr)
|
||||||
|
self.width = len(self.bus.pwdata)
|
||||||
|
self.byte_size = 8
|
||||||
|
self.byte_lanes = self.width // self.byte_size
|
||||||
|
self.strb_mask = 2**self.byte_lanes-1
|
||||||
|
|
||||||
|
self.pprot_present = hasattr(self.bus, "pprot")
|
||||||
|
self.pstrb_present = hasattr(self.bus, "pstrb")
|
||||||
|
self.pslverr_present = hasattr(self.bus, "pslverr")
|
||||||
|
|
||||||
|
self.log.info("APB slave model configuration:")
|
||||||
|
self.log.info(" Address width: %d bits", self.address_width)
|
||||||
|
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||||
|
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||||
|
|
||||||
|
self.log.info("APB slave model signals:")
|
||||||
|
for sig in sorted(list(set().union(self.bus._signals, self.bus._optional_signals))):
|
||||||
|
if hasattr(bus, sig):
|
||||||
|
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||||
|
else:
|
||||||
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
|
if self.pstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.bus.pstrb)
|
||||||
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
|
self.bus.pready.setimmediatevalue(False)
|
||||||
|
self.bus.prdata.setimmediatevalue(0)
|
||||||
|
if self.pslverr_present:
|
||||||
|
self.bus.pslverr.setimmediatevalue(0)
|
||||||
|
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
async def _write(self, address, data):
|
||||||
|
await self.target.write(address, data)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
|
||||||
|
self.bus.pready.value = False
|
||||||
|
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
self._run_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._run_cr is None:
|
||||||
|
self._run_cr = cocotb.start_soon(self._run())
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
self.bus.pready.value = False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
|
if self.pause:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not int(self.bus.psel.value) or not int(self.bus.penable.value):
|
||||||
|
continue
|
||||||
|
|
||||||
|
addr = (int(self.bus.paddr.value) // self.byte_lanes) * self.byte_lanes
|
||||||
|
if self.pprot_present:
|
||||||
|
prot = AxiProt(int(self.bus.pprot.value))
|
||||||
|
else:
|
||||||
|
prot = AxiProt.NONSECURE
|
||||||
|
|
||||||
|
pslverr = False
|
||||||
|
|
||||||
|
if (int(self.bus.pwrite.value)):
|
||||||
|
data = int(self.bus.pwdata.value)
|
||||||
|
|
||||||
|
if self.pstrb_present:
|
||||||
|
strb = int(self.bus.pstrb.value)
|
||||||
|
else:
|
||||||
|
strb = self.strb_mask
|
||||||
|
|
||||||
|
# generate operation list
|
||||||
|
offset = 0
|
||||||
|
start_offset = None
|
||||||
|
write_ops = []
|
||||||
|
|
||||||
|
data = data.to_bytes(self.byte_lanes, 'little')
|
||||||
|
|
||||||
|
if self.log.isEnabledFor(logging.INFO):
|
||||||
|
self.log.info("Write data paddr: 0x%08x pprot: %s pstrb: 0x%02x data: %s",
|
||||||
|
addr, prot, strb, ' '.join((f'{c:02x}' for c in data)))
|
||||||
|
|
||||||
|
for i in range(self.byte_lanes):
|
||||||
|
if strb & (1 << i):
|
||||||
|
if start_offset is None:
|
||||||
|
start_offset = offset
|
||||||
|
else:
|
||||||
|
if start_offset is not None and offset != start_offset:
|
||||||
|
write_ops.append((addr+start_offset, data[start_offset:offset]))
|
||||||
|
start_offset = None
|
||||||
|
|
||||||
|
offset += 1
|
||||||
|
|
||||||
|
if start_offset is not None and offset != start_offset:
|
||||||
|
write_ops.append((addr+start_offset, data[start_offset:offset]))
|
||||||
|
|
||||||
|
# perform writes
|
||||||
|
try:
|
||||||
|
for addr, data in write_ops:
|
||||||
|
await self._write(addr, data)
|
||||||
|
except Exception:
|
||||||
|
self.log.warning("Write operation failed")
|
||||||
|
pslverr = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data = await self._read(addr, self.byte_lanes)
|
||||||
|
except Exception:
|
||||||
|
self.log.warning("Read operation failed")
|
||||||
|
data = bytes(self.byte_lanes)
|
||||||
|
pslverr = True
|
||||||
|
|
||||||
|
if self.log.isEnabledFor(logging.INFO):
|
||||||
|
self.log.info("Read data paddr: 0x%08x pprot: %s data: %s",
|
||||||
|
addr, prot, ' '.join((f'{c:02x}' for c in data)))
|
||||||
|
|
||||||
|
self.bus.prdata.value = int.from_bytes(data, 'little')
|
||||||
|
|
||||||
|
await clock_edge_event
|
||||||
|
if self.pslverr_present:
|
||||||
|
self.bus.pslverr.value = pslverr
|
||||||
|
self.bus.pready.value = True
|
||||||
|
await clock_edge_event
|
||||||
|
self.bus.pready.value = False
|
||||||
|
if self.pslverr_present:
|
||||||
|
self.bus.pslverr.value = False
|
||||||
|
|
||||||
|
|
||||||
|
class ApbRam(ApbSlave, Memory):
|
||||||
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
|
async def _write(self, address, data):
|
||||||
|
self.write(address % self.size, data)
|
||||||
|
|
||||||
|
async def _read(self, address, length):
|
||||||
|
return self.read(address % self.size, length)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -34,8 +34,8 @@ AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream
|
|||||||
|
|
||||||
# Write data channel
|
# Write data channel
|
||||||
AxiWBus, AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
AxiWBus, AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
||||||
signals=["wdata", "wstrb", "wlast", "wvalid", "wready"],
|
signals=["wdata", "wlast", "wvalid", "wready"],
|
||||||
optional_signals=["wuser"],
|
optional_signals=["wstrb", "wuser"],
|
||||||
signal_widths={"wlast": 1}
|
signal_widths={"wlast": 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -124,7 +124,7 @@ class TagContext:
|
|||||||
|
|
||||||
def _start(self):
|
def _start(self):
|
||||||
if self._cr is None:
|
if self._cr is None:
|
||||||
self._cr = cocotb.fork(self._process_queue())
|
self._cr = cocotb.start_soon(self._process_queue())
|
||||||
|
|
||||||
def _flush(self):
|
def _flush(self):
|
||||||
flushed_cmds = []
|
flushed_cmds = []
|
||||||
@@ -198,11 +198,14 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
if bus.aw._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI master (write)")
|
self.log.info("AXI master (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi 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-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level)
|
self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level)
|
||||||
@@ -213,6 +216,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
self.b_channel.queue_occupancy_limit = 2
|
self.b_channel.queue_occupancy_limit = 2
|
||||||
|
|
||||||
self.write_command_queue = Queue()
|
self.write_command_queue = Queue()
|
||||||
|
self.write_command_queue.queue_occupancy_limit = 2
|
||||||
self.current_write_command = None
|
self.current_write_command = None
|
||||||
|
|
||||||
self.id_count = 2**len(self.aw_channel.bus.awid)
|
self.id_count = 2**len(self.aw_channel.bus.awid)
|
||||||
@@ -241,6 +245,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
self.awqos_present = hasattr(self.bus.aw, "awqos")
|
self.awqos_present = hasattr(self.bus.aw, "awqos")
|
||||||
self.awregion_present = hasattr(self.bus.aw, "awregion")
|
self.awregion_present = hasattr(self.bus.aw, "awregion")
|
||||||
self.awuser_present = hasattr(self.bus.aw, "awuser")
|
self.awuser_present = hasattr(self.bus.aw, "awuser")
|
||||||
|
self.wstrb_present = hasattr(self.bus.w, "wstrb")
|
||||||
self.wuser_present = hasattr(self.bus.w, "wuser")
|
self.wuser_present = hasattr(self.bus.w, "wuser")
|
||||||
self.buser_present = hasattr(self.bus.b, "buser")
|
self.buser_present = hasattr(self.bus.b, "buser")
|
||||||
|
|
||||||
@@ -263,7 +268,8 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info(" %s: not present", sig)
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
if self.wstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_lanes * self.byte_size == self.width
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||||
@@ -288,6 +294,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
raise ValueError("Expected bytes or bytearray for data")
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if awid is None or awid < 0:
|
if awid is None or awid < 0:
|
||||||
awid = None
|
awid = None
|
||||||
elif awid > self.id_count:
|
elif awid > self.id_count:
|
||||||
@@ -331,12 +340,10 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
wuser = list(wuser)
|
wuser = list(wuser)
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
data = bytes(data)
|
||||||
self._idle.clear()
|
|
||||||
|
|
||||||
cmd = AxiWriteCmd(address, bytes(data), awid, burst, size, lock,
|
cocotb.start_soon(self._write_wrapper(address, data, awid, burst, size,
|
||||||
cache, prot, qos, region, user, wuser, event)
|
lock, cache, prot, qos, region, user, wuser, event))
|
||||||
self.write_command_queue.put_nowait(cmd)
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
@@ -349,10 +356,77 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None,
|
async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None,
|
||||||
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0):
|
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0):
|
||||||
event = self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser)
|
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if isinstance(data, int):
|
||||||
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if awid is None or awid < 0:
|
||||||
|
awid = None
|
||||||
|
elif awid > self.id_count:
|
||||||
|
raise ValueError("Requested ID exceeds maximum ID allowed for ID signal width")
|
||||||
|
|
||||||
|
burst = AxiBurstType(burst)
|
||||||
|
|
||||||
|
if size is None or size < 0:
|
||||||
|
size = self.max_burst_size
|
||||||
|
elif size > self.max_burst_size:
|
||||||
|
raise ValueError("Requested burst size exceeds maximum burst size allowed for bus width")
|
||||||
|
|
||||||
|
lock = AxiLockType(lock)
|
||||||
|
prot = AxiProt(prot)
|
||||||
|
|
||||||
|
if not self.awlock_present and lock != AxiLockType.NORMAL:
|
||||||
|
raise ValueError("awlock sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.awcache_present and cache != 0b0011:
|
||||||
|
raise ValueError("awcache sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.awqos_present and qos != 0:
|
||||||
|
raise ValueError("awqos sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.awregion_present and region != 0:
|
||||||
|
raise ValueError("awregion sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.awuser_present and user != 0:
|
||||||
|
raise ValueError("awuser sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.wuser_present and wuser != 0:
|
||||||
|
raise ValueError("wuser sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if wuser is None:
|
||||||
|
wuser = 0
|
||||||
|
elif isinstance(wuser, int):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
wuser = list(wuser)
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
data = bytes(data)
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
cmd = AxiWriteCmd(address, data, awid, burst, size, lock,
|
||||||
|
cache, prot, qos, region, user, wuser, event)
|
||||||
|
await self.write_command_queue.put(cmd)
|
||||||
|
|
||||||
await event.wait()
|
await event.wait()
|
||||||
return event.data
|
return event.data
|
||||||
|
|
||||||
|
async def _write_wrapper(self, address, data, awid, burst, size,
|
||||||
|
lock, cache, prot, qos, region, user, wuser, event):
|
||||||
|
event.set(await self.write(address, data, awid, burst, size,
|
||||||
|
lock, cache, prot, qos, region, user, wuser))
|
||||||
|
|
||||||
def _handle_reset(self, state):
|
def _handle_reset(self, state):
|
||||||
if state:
|
if state:
|
||||||
self.log.info("Reset asserted")
|
self.log.info("Reset asserted")
|
||||||
@@ -392,9 +466,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_write_cr is None:
|
if self._process_write_cr is None:
|
||||||
self._process_write_cr = cocotb.fork(self._process_write())
|
self._process_write_cr = cocotb.start_soon(self._process_write())
|
||||||
if self._process_write_resp_cr is None:
|
if self._process_write_resp_cr is None:
|
||||||
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
self._process_write_resp_cr = cocotb.start_soon(self._process_write_resp())
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -411,7 +485,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
cycles = (len(cmd.data) + (cmd.address % num_bytes) + num_bytes-1) // num_bytes
|
cycles = (len(cmd.data) + (cmd.address % num_bytes) + num_bytes-1) // num_bytes
|
||||||
|
|
||||||
cur_addr = aligned_addr
|
cur_addr = cmd.address
|
||||||
offset = 0
|
offset = 0
|
||||||
cycle_offset = aligned_addr-word_addr
|
cycle_offset = aligned_addr-word_addr
|
||||||
n = 0
|
n = 0
|
||||||
@@ -480,6 +554,9 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
|
if not self.wstrb_present and strb != self.strb_mask:
|
||||||
|
self.log.warning("Partial operation requested with wstrb not connected, write will be zero-padded (0x%x != 0x%x)", strb, self.strb_mask)
|
||||||
|
|
||||||
w = self.w_channel._transaction_obj()
|
w = self.w_channel._transaction_obj()
|
||||||
w.wdata = val
|
w.wdata = val
|
||||||
w.wstrb = strb
|
w.wstrb = strb
|
||||||
@@ -495,7 +572,12 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
await self.w_channel.send(w)
|
await self.w_channel.send(w)
|
||||||
|
|
||||||
cur_addr += num_bytes
|
if cmd.burst == AxiBurstType.FIXED:
|
||||||
|
cur_addr = cmd.address
|
||||||
|
elif k == 0:
|
||||||
|
cur_addr = aligned_addr + num_bytes
|
||||||
|
else:
|
||||||
|
cur_addr += num_bytes
|
||||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_lanes
|
cycle_offset = (cycle_offset + num_bytes) % self.byte_lanes
|
||||||
|
|
||||||
resp_cmd = AxiWriteRespCmd(cmd.address, len(cmd.data), cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
resp_cmd = AxiWriteRespCmd(cmd.address, len(cmd.data), cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
||||||
@@ -509,8 +591,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
|
|
||||||
bid = int(getattr(b, 'bid', 0))
|
bid = int(getattr(b, 'bid', 0))
|
||||||
|
|
||||||
if self.active_id[bid] <= 0:
|
assert self.active_id[bid] > 0, "unexpected burst ID"
|
||||||
raise Exception(f"Unexpected burst ID {bid}")
|
|
||||||
|
|
||||||
self.tag_context_manager.put_resp(bid, b)
|
self.tag_context_manager.put_resp(bid, b)
|
||||||
|
|
||||||
@@ -523,7 +604,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
for burst_length in cmd.burst_list:
|
for burst_length in cmd.burst_list:
|
||||||
b = await context.get_resp()
|
b = await context.get_resp()
|
||||||
|
|
||||||
burst_resp = AxiResp(getattr(b, 'bresp', AxiResp.OKAY))
|
burst_resp = AxiResp(int(getattr(b, 'bresp', AxiResp.OKAY)))
|
||||||
burst_user = int(getattr(b, 'buser', 0))
|
burst_user = int(getattr(b, 'buser', 0))
|
||||||
|
|
||||||
if burst_resp != AxiResp.OKAY:
|
if burst_resp != AxiResp.OKAY:
|
||||||
@@ -532,8 +613,7 @@ class AxiMasterWrite(Region, Reset):
|
|||||||
if burst_user is not None:
|
if burst_user is not None:
|
||||||
user.append(burst_user)
|
user.append(burst_user)
|
||||||
|
|
||||||
if self.active_id[bid] <= 0:
|
assert self.active_id[bid] > 0, "unexpected burst ID"
|
||||||
raise Exception(f"Unexpected burst ID {bid}")
|
|
||||||
|
|
||||||
self.active_id[bid] -= 1
|
self.active_id[bid] -= 1
|
||||||
|
|
||||||
@@ -560,11 +640,14 @@ class AxiMasterRead(Region, Reset):
|
|||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
if bus.ar._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI master (read)")
|
self.log.info("AXI master (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi 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-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level)
|
self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level)
|
||||||
@@ -573,6 +656,7 @@ class AxiMasterRead(Region, Reset):
|
|||||||
self.r_channel.queue_occupancy_limit = 2
|
self.r_channel.queue_occupancy_limit = 2
|
||||||
|
|
||||||
self.read_command_queue = Queue()
|
self.read_command_queue = Queue()
|
||||||
|
self.read_command_queue.queue_occupancy_limit = 2
|
||||||
self.current_read_command = None
|
self.current_read_command = None
|
||||||
|
|
||||||
self.id_count = 2**len(self.ar_channel.bus.arid)
|
self.id_count = 2**len(self.ar_channel.bus.arid)
|
||||||
@@ -645,6 +729,9 @@ class AxiMasterRead(Region, Reset):
|
|||||||
if length < 0:
|
if length < 0:
|
||||||
raise ValueError("Read length must be positive")
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if arid is None or arid < 0:
|
if arid is None or arid < 0:
|
||||||
arid = None
|
arid = None
|
||||||
elif arid > self.id_count:
|
elif arid > self.id_count:
|
||||||
@@ -678,11 +765,8 @@ class AxiMasterRead(Region, Reset):
|
|||||||
if not self.aruser_present and user != 0:
|
if not self.aruser_present and user != 0:
|
||||||
raise ValueError("aruser sideband signal value specified, but signal is not connected")
|
raise ValueError("aruser sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
cocotb.start_soon(self._read_wrapper(address, length, arid, burst, size,
|
||||||
self._idle.clear()
|
lock, cache, prot, qos, region, user, event))
|
||||||
|
|
||||||
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
|
||||||
self.read_command_queue.put_nowait(cmd)
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
@@ -695,10 +779,65 @@ class AxiMasterRead(Region, Reset):
|
|||||||
|
|
||||||
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
||||||
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0):
|
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0):
|
||||||
event = self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user)
|
|
||||||
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if burst != AxiBurstType.FIXED and address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if arid is None or arid < 0:
|
||||||
|
arid = None
|
||||||
|
elif arid > self.id_count:
|
||||||
|
raise ValueError("Requested ID exceeds maximum ID allowed for ID signal width")
|
||||||
|
|
||||||
|
burst = AxiBurstType(burst)
|
||||||
|
|
||||||
|
if size is None or size < 0:
|
||||||
|
size = self.max_burst_size
|
||||||
|
elif size > self.max_burst_size:
|
||||||
|
raise ValueError("Requested burst size exceeds maximum burst size allowed for bus width")
|
||||||
|
|
||||||
|
lock = AxiLockType(lock)
|
||||||
|
prot = AxiProt(prot)
|
||||||
|
|
||||||
|
if not self.arlock_present and lock != AxiLockType.NORMAL:
|
||||||
|
raise ValueError("arlock sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.arcache_present and cache != 0b0011:
|
||||||
|
raise ValueError("arcache sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.arqos_present and qos != 0:
|
||||||
|
raise ValueError("arqos sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.arregion_present and region != 0:
|
||||||
|
raise ValueError("arregion sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
if not self.aruser_present and user != 0:
|
||||||
|
raise ValueError("aruser sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||||
|
await self.read_command_queue.put(cmd)
|
||||||
|
|
||||||
await event.wait()
|
await event.wait()
|
||||||
return event.data
|
return event.data
|
||||||
|
|
||||||
|
async def _read_wrapper(self, address, length, arid, burst, size,
|
||||||
|
lock, cache, prot, qos, region, user, event):
|
||||||
|
event.set(await self.read(address, length, arid, burst, size,
|
||||||
|
lock, cache, prot, qos, region, user))
|
||||||
|
|
||||||
def _handle_reset(self, state):
|
def _handle_reset(self, state):
|
||||||
if state:
|
if state:
|
||||||
self.log.info("Reset asserted")
|
self.log.info("Reset asserted")
|
||||||
@@ -737,9 +876,9 @@ class AxiMasterRead(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_read_cr is None:
|
if self._process_read_cr is None:
|
||||||
self._process_read_cr = cocotb.fork(self._process_read())
|
self._process_read_cr = cocotb.start_soon(self._process_read())
|
||||||
if self._process_read_resp_cr is None:
|
if self._process_read_resp_cr is None:
|
||||||
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
self._process_read_resp_cr = cocotb.start_soon(self._process_read_resp())
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -754,7 +893,7 @@ class AxiMasterRead(Region, Reset):
|
|||||||
|
|
||||||
burst_list = []
|
burst_list = []
|
||||||
|
|
||||||
cur_addr = aligned_addr
|
cur_addr = cmd.address
|
||||||
n = 0
|
n = 0
|
||||||
|
|
||||||
burst_length = 0
|
burst_length = 0
|
||||||
@@ -799,7 +938,12 @@ class AxiMasterRead(Region, Reset):
|
|||||||
self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
||||||
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||||
|
|
||||||
cur_addr += num_bytes
|
if cmd.burst == AxiBurstType.FIXED:
|
||||||
|
cur_addr = cmd.address
|
||||||
|
elif k == 0:
|
||||||
|
cur_addr = aligned_addr + num_bytes
|
||||||
|
else:
|
||||||
|
cur_addr += num_bytes
|
||||||
|
|
||||||
resp_cmd = AxiReadRespCmd(cmd.address, cmd.length, cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
resp_cmd = AxiReadRespCmd(cmd.address, cmd.length, cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
||||||
self.tag_context_manager.start_cmd(arid, resp_cmd)
|
self.tag_context_manager.start_cmd(arid, resp_cmd)
|
||||||
@@ -807,27 +951,14 @@ class AxiMasterRead(Region, Reset):
|
|||||||
self.current_read_command = None
|
self.current_read_command = None
|
||||||
|
|
||||||
async def _process_read_resp(self):
|
async def _process_read_resp(self):
|
||||||
burst = []
|
|
||||||
cur_rid = None
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
r = await self.r_channel.recv()
|
r = await self.r_channel.recv()
|
||||||
|
|
||||||
rid = int(getattr(r, 'rid', 0))
|
rid = int(getattr(r, 'rid', 0))
|
||||||
|
|
||||||
if cur_rid is not None and cur_rid != rid:
|
assert self.active_id[rid] > 0, "unexpected burst ID"
|
||||||
raise Exception(f"ID not constant within burst (expected {cur_rid}, got {rid})")
|
|
||||||
|
|
||||||
if self.active_id[rid] <= 0:
|
self.tag_context_manager.put_resp(rid, r)
|
||||||
raise Exception(f"Unexpected burst ID {rid}")
|
|
||||||
|
|
||||||
burst.append(r)
|
|
||||||
cur_rid = rid
|
|
||||||
|
|
||||||
if int(r.rlast):
|
|
||||||
self.tag_context_manager.put_resp(rid, burst)
|
|
||||||
burst = []
|
|
||||||
cur_rid = None
|
|
||||||
|
|
||||||
async def _process_read_resp_id(self, context, cmd):
|
async def _process_read_resp_id(self, context, cmd):
|
||||||
rid = context.current_tag
|
rid = context.current_tag
|
||||||
@@ -848,14 +979,18 @@ class AxiMasterRead(Region, Reset):
|
|||||||
first = True
|
first = True
|
||||||
|
|
||||||
for burst_length in cmd.burst_list:
|
for burst_length in cmd.burst_list:
|
||||||
burst = await context.get_resp()
|
for k in range(burst_length):
|
||||||
|
r = await context.get_resp()
|
||||||
|
|
||||||
if len(burst) != burst_length:
|
assert self.active_id[rid] > 0, "unexpected burst ID"
|
||||||
raise Exception(f"Burst length incorrect (ID {rid}, expected {burst_length}, got {len(burst)}")
|
|
||||||
|
if k == burst_length-1:
|
||||||
|
assert int(r.rlast), "missing rlast at end of burst"
|
||||||
|
else:
|
||||||
|
assert not int(r.rlast), "unexpected rlast within burst"
|
||||||
|
|
||||||
for r in burst:
|
|
||||||
cycle_data = int(r.rdata)
|
cycle_data = int(r.rdata)
|
||||||
cycle_resp = AxiResp(getattr(r, "rresp", AxiResp.OKAY))
|
cycle_resp = AxiResp(int(getattr(r, "rresp", AxiResp.OKAY)))
|
||||||
cycle_user = int(getattr(r, "ruser", 0))
|
cycle_user = int(getattr(r, "ruser", 0))
|
||||||
|
|
||||||
if cycle_resp != AxiResp.OKAY:
|
if cycle_resp != AxiResp.OKAY:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,7 +27,7 @@ from .memory import Memory
|
|||||||
|
|
||||||
|
|
||||||
class AxiRamWrite(AxiSlaveWrite, Memory):
|
class AxiRamWrite(AxiSlaveWrite, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _write(self, address, data):
|
async def _write(self, address, data):
|
||||||
@@ -35,7 +35,7 @@ class AxiRamWrite(AxiSlaveWrite, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiRamRead(AxiSlaveRead, Memory):
|
class AxiRamRead(AxiSlaveRead, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _read(self, address, length):
|
async def _read(self, address, length):
|
||||||
@@ -43,7 +43,7 @@ class AxiRamRead(AxiSlaveRead, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiRam(Memory):
|
class AxiRam(Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
self.write_if = None
|
self.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -38,11 +38,14 @@ class AxiSlaveWrite(Reset):
|
|||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.target = target
|
self.target = target
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
if bus.aw._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI slave model (write)")
|
self.log.info("AXI slave model (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2021 Alex Forencich")
|
self.log.info("Copyright (c) 2021-2025 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -63,6 +66,8 @@ class AxiSlaveWrite(Reset):
|
|||||||
|
|
||||||
self.max_burst_size = (self.byte_lanes-1).bit_length()
|
self.max_burst_size = (self.byte_lanes-1).bit_length()
|
||||||
|
|
||||||
|
self.wstrb_present = hasattr(self.bus.w, "wstrb")
|
||||||
|
|
||||||
self.log.info("AXI slave model configuration:")
|
self.log.info("AXI slave model configuration:")
|
||||||
self.log.info(" Address width: %d bits", self.address_width)
|
self.log.info(" Address width: %d bits", self.address_width)
|
||||||
self.log.info(" ID width: %d bits", self.id_width)
|
self.log.info(" ID width: %d bits", self.id_width)
|
||||||
@@ -77,7 +82,8 @@ class AxiSlaveWrite(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info(" %s: not present", sig)
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
if self.wstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_lanes * self.byte_size == self.width
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||||
@@ -102,7 +108,7 @@ class AxiSlaveWrite(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_write_cr is None:
|
if self._process_write_cr is None:
|
||||||
self._process_write_cr = cocotb.fork(self._process_write())
|
self._process_write_cr = cocotb.start_soon(self._process_write())
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -112,8 +118,8 @@ class AxiSlaveWrite(Reset):
|
|||||||
addr = int(aw.awaddr)
|
addr = int(aw.awaddr)
|
||||||
length = int(getattr(aw, 'awlen', 0))
|
length = int(getattr(aw, 'awlen', 0))
|
||||||
size = int(getattr(aw, 'awsize', self.max_burst_size))
|
size = int(getattr(aw, 'awsize', self.max_burst_size))
|
||||||
burst = AxiBurstType(getattr(aw, 'awburst', AxiBurstType.INCR))
|
burst = AxiBurstType(int(getattr(aw, 'awburst', AxiBurstType.INCR)))
|
||||||
prot = AxiProt(getattr(aw, 'awprot', AxiProt.NONSECURE))
|
prot = AxiProt(int(getattr(aw, 'awprot', AxiProt.NONSECURE)))
|
||||||
|
|
||||||
self.log.info("Write burst awid: 0x%x awaddr: 0x%08x awlen: %d awsize: %d awprot: %s",
|
self.log.info("Write burst awid: 0x%x awaddr: 0x%08x awlen: %d awsize: %d awprot: %s",
|
||||||
awid, addr, length, size, prot)
|
awid, addr, length, size, prot)
|
||||||
@@ -146,7 +152,10 @@ class AxiSlaveWrite(Reset):
|
|||||||
w = await self.w_channel.recv()
|
w = await self.w_channel.recv()
|
||||||
|
|
||||||
data = int(w.wdata)
|
data = int(w.wdata)
|
||||||
strb = int(getattr(w, 'wstrb', self.strb_mask))
|
if self.wstrb_present:
|
||||||
|
strb = int(getattr(w, 'wstrb', self.strb_mask))
|
||||||
|
else:
|
||||||
|
strb = self.strb_mask
|
||||||
last = int(w.wlast)
|
last = int(w.wlast)
|
||||||
|
|
||||||
# generate operation list
|
# generate operation list
|
||||||
@@ -200,11 +209,14 @@ class AxiSlaveRead(Reset):
|
|||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.target = target
|
self.target = target
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
if bus.ar._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI slave model (read)")
|
self.log.info("AXI slave model (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2021 Alex Forencich")
|
self.log.info("Copyright (c) 2021-2025 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -259,7 +271,7 @@ class AxiSlaveRead(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_read_cr is None:
|
if self._process_read_cr is None:
|
||||||
self._process_read_cr = cocotb.fork(self._process_read())
|
self._process_read_cr = cocotb.start_soon(self._process_read())
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -269,8 +281,8 @@ class AxiSlaveRead(Reset):
|
|||||||
addr = int(ar.araddr)
|
addr = int(ar.araddr)
|
||||||
length = int(getattr(ar, 'arlen', 0))
|
length = int(getattr(ar, 'arlen', 0))
|
||||||
size = int(getattr(ar, 'arsize', self.max_burst_size))
|
size = int(getattr(ar, 'arsize', self.max_burst_size))
|
||||||
burst = AxiBurstType(getattr(ar, 'arburst', AxiBurstType.INCR))
|
burst = AxiBurstType(int(getattr(ar, 'arburst', AxiBurstType.INCR)))
|
||||||
prot = AxiProt(getattr(ar, 'arprot', AxiProt.NONSECURE))
|
prot = AxiProt(int(getattr(ar, 'arprot', AxiProt.NONSECURE)))
|
||||||
|
|
||||||
self.log.info("Read burst arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
self.log.info("Read burst arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
||||||
arid, addr, length, size, prot)
|
arid, addr, length, size, prot)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -33,7 +33,8 @@ AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMon
|
|||||||
|
|
||||||
# Write data channel
|
# Write data channel
|
||||||
AxiLiteWBus, AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
AxiLiteWBus, AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
||||||
signals=["wdata", "wstrb", "wvalid", "wready"]
|
signals=["wdata", "wvalid", "wready"],
|
||||||
|
optional_signals=["wstrb"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write response channel
|
# Write response channel
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -88,11 +88,14 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
if bus.aw._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite master (write)")
|
self.log.info("AXI lite master (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi 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-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level)
|
self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level)
|
||||||
@@ -103,6 +106,7 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
self.b_channel.queue_occupancy_limit = 2
|
self.b_channel.queue_occupancy_limit = 2
|
||||||
|
|
||||||
self.write_command_queue = Queue()
|
self.write_command_queue = Queue()
|
||||||
|
self.write_command_queue.queue_occupancy_limit = 2
|
||||||
self.current_write_command = None
|
self.current_write_command = None
|
||||||
|
|
||||||
self.int_write_resp_command_queue = Queue()
|
self.int_write_resp_command_queue = Queue()
|
||||||
@@ -119,6 +123,7 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
self.strb_mask = 2**self.byte_lanes-1
|
self.strb_mask = 2**self.byte_lanes-1
|
||||||
|
|
||||||
self.awprot_present = hasattr(self.bus.aw, "awprot")
|
self.awprot_present = hasattr(self.bus.aw, "awprot")
|
||||||
|
self.wstrb_present = hasattr(self.bus.w, "wstrb")
|
||||||
|
|
||||||
super().__init__(2**self.address_width, **kwargs)
|
super().__init__(2**self.address_width, **kwargs)
|
||||||
|
|
||||||
@@ -135,7 +140,8 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info(" %s: not present", sig)
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
if self.wstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_lanes * self.byte_size == self.width
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
self._process_write_cr = None
|
self._process_write_cr = None
|
||||||
@@ -156,13 +162,15 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
raise ValueError("Expected bytes or bytearray for data")
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||||
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
data = bytes(data)
|
||||||
self._idle.clear()
|
|
||||||
|
|
||||||
self.write_command_queue.put_nowait(AxiLiteWriteCmd(address, bytes(data), prot, event))
|
cocotb.start_soon(self._write_wrapper(address, bytes(data), prot, event))
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
@@ -174,10 +182,31 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
await self._idle.wait()
|
await self._idle.wait()
|
||||||
|
|
||||||
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
||||||
event = self.init_write(address, data, prot)
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if isinstance(data, int):
|
||||||
|
raise ValueError("Expected bytes or bytearray for data")
|
||||||
|
|
||||||
|
if address+len(data) > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
data = bytes(data)
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
await self.write_command_queue.put(AxiLiteWriteCmd(address, data, prot, event))
|
||||||
await event.wait()
|
await event.wait()
|
||||||
return event.data
|
return event.data
|
||||||
|
|
||||||
|
async def _write_wrapper(self, address, data, prot, event):
|
||||||
|
event.set(await self.write(address, data, prot))
|
||||||
|
|
||||||
def _handle_reset(self, state):
|
def _handle_reset(self, state):
|
||||||
if state:
|
if state:
|
||||||
self.log.info("Reset asserted")
|
self.log.info("Reset asserted")
|
||||||
@@ -220,9 +249,9 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_write_cr is None:
|
if self._process_write_cr is None:
|
||||||
self._process_write_cr = cocotb.fork(self._process_write())
|
self._process_write_cr = cocotb.start_soon(self._process_write())
|
||||||
if self._process_write_resp_cr is None:
|
if self._process_write_resp_cr is None:
|
||||||
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
self._process_write_resp_cr = cocotb.start_soon(self._process_write_resp())
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -266,9 +295,15 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
offset += 1
|
offset += 1
|
||||||
|
|
||||||
aw = self.aw_channel._transaction_obj()
|
aw = self.aw_channel._transaction_obj()
|
||||||
aw.awaddr = word_addr + k*self.byte_lanes
|
if k == 0:
|
||||||
|
aw.awaddr = cmd.address
|
||||||
|
else:
|
||||||
|
aw.awaddr = word_addr + k*self.byte_lanes
|
||||||
aw.awprot = cmd.prot
|
aw.awprot = cmd.prot
|
||||||
|
|
||||||
|
if not self.wstrb_present and strb != self.strb_mask:
|
||||||
|
self.log.warning("Partial operation requested with wstrb not connected, write will be zero-padded (0x%x != 0x%x)", strb, self.strb_mask)
|
||||||
|
|
||||||
w = self.w_channel._transaction_obj()
|
w = self.w_channel._transaction_obj()
|
||||||
w.wdata = val
|
w.wdata = val
|
||||||
w.wstrb = strb
|
w.wstrb = strb
|
||||||
@@ -288,7 +323,7 @@ class AxiLiteMasterWrite(Region, Reset):
|
|||||||
for k in range(cmd.cycles):
|
for k in range(cmd.cycles):
|
||||||
b = await self.b_channel.recv()
|
b = await self.b_channel.recv()
|
||||||
|
|
||||||
cycle_resp = AxiResp(getattr(b, 'bresp', AxiResp.OKAY))
|
cycle_resp = AxiResp(int(getattr(b, 'bresp', AxiResp.OKAY)))
|
||||||
|
|
||||||
if cycle_resp != AxiResp.OKAY:
|
if cycle_resp != AxiResp.OKAY:
|
||||||
resp = cycle_resp
|
resp = cycle_resp
|
||||||
@@ -313,11 +348,14 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
if bus.ar._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite master (read)")
|
self.log.info("AXI lite master (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi 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-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level)
|
self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level)
|
||||||
@@ -326,6 +364,7 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
self.r_channel.queue_occupancy_limit = 2
|
self.r_channel.queue_occupancy_limit = 2
|
||||||
|
|
||||||
self.read_command_queue = Queue()
|
self.read_command_queue = Queue()
|
||||||
|
self.read_command_queue.queue_occupancy_limit = 2
|
||||||
self.current_read_command = None
|
self.current_read_command = None
|
||||||
|
|
||||||
self.int_read_resp_command_queue = Queue()
|
self.int_read_resp_command_queue = Queue()
|
||||||
@@ -374,13 +413,16 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
if address < 0 or address >= 2**self.address_width:
|
if address < 0 or address >= 2**self.address_width:
|
||||||
raise ValueError("Address out of range")
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||||
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
cocotb.start_soon(self._read_wrapper(address, length, prot, event))
|
||||||
self._idle.clear()
|
|
||||||
|
|
||||||
self.read_command_queue.put_nowait(AxiLiteReadCmd(address, length, prot, event))
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|
||||||
@@ -392,10 +434,31 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
await self._idle.wait()
|
await self._idle.wait()
|
||||||
|
|
||||||
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
||||||
event = self.init_read(address, length, prot)
|
if address < 0 or address >= 2**self.address_width:
|
||||||
|
raise ValueError("Address out of range")
|
||||||
|
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("Read length must be positive")
|
||||||
|
|
||||||
|
if address+length > 2**self.address_width:
|
||||||
|
raise ValueError("Requested transfer overruns end of address space")
|
||||||
|
|
||||||
|
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||||
|
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||||
|
|
||||||
|
event = Event()
|
||||||
|
|
||||||
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
|
await self.read_command_queue.put(AxiLiteReadCmd(address, length, prot, event))
|
||||||
|
|
||||||
await event.wait()
|
await event.wait()
|
||||||
return event.data
|
return event.data
|
||||||
|
|
||||||
|
async def _read_wrapper(self, address, length, prot, event):
|
||||||
|
event.set(await self.read(address, length, prot))
|
||||||
|
|
||||||
def _handle_reset(self, state):
|
def _handle_reset(self, state):
|
||||||
if state:
|
if state:
|
||||||
self.log.info("Reset asserted")
|
self.log.info("Reset asserted")
|
||||||
@@ -437,9 +500,9 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_read_cr is None:
|
if self._process_read_cr is None:
|
||||||
self._process_read_cr = cocotb.fork(self._process_read())
|
self._process_read_cr = cocotb.start_soon(self._process_read())
|
||||||
if self._process_read_resp_cr is None:
|
if self._process_read_resp_cr is None:
|
||||||
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
self._process_read_resp_cr = cocotb.start_soon(self._process_read_resp())
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
@@ -458,7 +521,10 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
|
|
||||||
for k in range(cycles):
|
for k in range(cycles):
|
||||||
ar = self.ar_channel._transaction_obj()
|
ar = self.ar_channel._transaction_obj()
|
||||||
ar.araddr = word_addr + k*self.byte_lanes
|
if k == 0:
|
||||||
|
ar.araddr = cmd.address
|
||||||
|
else:
|
||||||
|
ar.araddr = word_addr + k*self.byte_lanes
|
||||||
ar.arprot = cmd.prot
|
ar.arprot = cmd.prot
|
||||||
|
|
||||||
await self.ar_channel.send(ar)
|
await self.ar_channel.send(ar)
|
||||||
@@ -481,7 +547,7 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
r = await self.r_channel.recv()
|
r = await self.r_channel.recv()
|
||||||
|
|
||||||
cycle_data = int(r.rdata)
|
cycle_data = int(r.rdata)
|
||||||
cycle_resp = AxiResp(getattr(r, 'rresp', AxiResp.OKAY))
|
cycle_resp = AxiResp(int(getattr(r, 'rresp', AxiResp.OKAY)))
|
||||||
|
|
||||||
if cycle_resp != AxiResp.OKAY:
|
if cycle_resp != AxiResp.OKAY:
|
||||||
resp = cycle_resp
|
resp = cycle_resp
|
||||||
@@ -495,7 +561,7 @@ class AxiLiteMasterRead(Region, Reset):
|
|||||||
stop = end_offset
|
stop = end_offset
|
||||||
|
|
||||||
for j in range(start, stop):
|
for j in range(start, stop):
|
||||||
data.extend(bytearray([(cycle_data >> j*8) & 0xff]))
|
data.append((cycle_data >> j*8) & 0xff)
|
||||||
|
|
||||||
if self.log.isEnabledFor(logging.INFO):
|
if self.log.isEnabledFor(logging.INFO):
|
||||||
self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s",
|
self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,7 +27,7 @@ from .memory import Memory
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _write(self, address, data):
|
async def _write(self, address, data):
|
||||||
@@ -35,7 +35,7 @@ class AxiLiteRamWrite(AxiLiteSlaveWrite, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
super().__init__(bus, clock, reset, reset_active_level=reset_active_level, size=size, mem=mem, **kwargs)
|
||||||
|
|
||||||
async def _read(self, address, length):
|
async def _read(self, address, length):
|
||||||
@@ -43,7 +43,7 @@ class AxiLiteRamRead(AxiLiteSlaveRead, Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiLiteRam(Memory):
|
class AxiLiteRam(Memory):
|
||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=2**64, mem=None, **kwargs):
|
||||||
self.write_if = None
|
self.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -38,11 +38,14 @@ class AxiLiteSlaveWrite(Reset):
|
|||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.target = target
|
self.target = target
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
if bus.aw._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite slave model (write)")
|
self.log.info("AXI lite slave model (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2021 Alex Forencich")
|
self.log.info("Copyright (c) 2021-2025 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -60,8 +63,9 @@ class AxiLiteSlaveWrite(Reset):
|
|||||||
self.byte_lanes = self.width // self.byte_size
|
self.byte_lanes = self.width // self.byte_size
|
||||||
self.strb_mask = 2**self.byte_lanes-1
|
self.strb_mask = 2**self.byte_lanes-1
|
||||||
|
|
||||||
|
self.wstrb_present = hasattr(self.bus.w, "wstrb")
|
||||||
|
|
||||||
self.log.info("AXI lite slave model configuration:")
|
self.log.info("AXI lite slave model configuration:")
|
||||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
|
||||||
self.log.info(" Address width: %d bits", self.address_width)
|
self.log.info(" Address width: %d bits", self.address_width)
|
||||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||||
@@ -74,7 +78,8 @@ class AxiLiteSlaveWrite(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info(" %s: not present", sig)
|
self.log.info(" %s: not present", sig)
|
||||||
|
|
||||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
if self.wstrb_present:
|
||||||
|
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_lanes * self.byte_size == self.width
|
assert self.byte_lanes * self.byte_size == self.width
|
||||||
|
|
||||||
self._process_write_cr = None
|
self._process_write_cr = None
|
||||||
@@ -97,19 +102,22 @@ class AxiLiteSlaveWrite(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_write_cr is None:
|
if self._process_write_cr is None:
|
||||||
self._process_write_cr = cocotb.fork(self._process_write())
|
self._process_write_cr = cocotb.start_soon(self._process_write())
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
aw = await self.aw_channel.recv()
|
aw = await self.aw_channel.recv()
|
||||||
|
|
||||||
addr = (int(aw.awaddr) // self.byte_lanes) * self.byte_lanes
|
addr = (int(aw.awaddr) // self.byte_lanes) * self.byte_lanes
|
||||||
prot = AxiProt(getattr(aw, 'awprot', AxiProt.NONSECURE))
|
prot = AxiProt(int(getattr(aw, 'awprot', AxiProt.NONSECURE)))
|
||||||
|
|
||||||
w = await self.w_channel.recv()
|
w = await self.w_channel.recv()
|
||||||
|
|
||||||
data = int(w.wdata)
|
data = int(w.wdata)
|
||||||
strb = int(getattr(w, 'wstrb', self.strb_mask))
|
if self.wstrb_present:
|
||||||
|
strb = int(getattr(w, 'wstrb', self.strb_mask))
|
||||||
|
else:
|
||||||
|
strb = self.strb_mask
|
||||||
|
|
||||||
# generate operation list
|
# generate operation list
|
||||||
offset = 0
|
offset = 0
|
||||||
@@ -156,11 +164,14 @@ class AxiLiteSlaveRead(Reset):
|
|||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.target = target
|
self.target = target
|
||||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
if bus.ar._name:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
else:
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite slave model (read)")
|
self.log.info("AXI lite slave model (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2021 Alex Forencich")
|
self.log.info("Copyright (c) 2021-2025 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@@ -176,7 +187,6 @@ class AxiLiteSlaveRead(Reset):
|
|||||||
self.byte_lanes = self.width // self.byte_size
|
self.byte_lanes = self.width // self.byte_size
|
||||||
|
|
||||||
self.log.info("AXI lite slave model configuration:")
|
self.log.info("AXI lite slave model configuration:")
|
||||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
|
||||||
self.log.info(" Address width: %d bits", self.address_width)
|
self.log.info(" Address width: %d bits", self.address_width)
|
||||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||||
@@ -210,14 +220,14 @@ class AxiLiteSlaveRead(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._process_read_cr is None:
|
if self._process_read_cr is None:
|
||||||
self._process_read_cr = cocotb.fork(self._process_read())
|
self._process_read_cr = cocotb.start_soon(self._process_read())
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
ar = await self.ar_channel.recv()
|
ar = await self.ar_channel.recv()
|
||||||
|
|
||||||
addr = (int(ar.araddr) // self.byte_lanes) * self.byte_lanes
|
addr = (int(ar.araddr) // self.byte_lanes) * self.byte_lanes
|
||||||
prot = AxiProt(getattr(ar, 'arprot', AxiProt.NONSECURE))
|
prot = AxiProt(int(getattr(ar, 'arprot', AxiProt.NONSECURE)))
|
||||||
|
|
||||||
r = self.r_channel._transaction_obj()
|
r = self.r_channel._transaction_obj()
|
||||||
r.rresp = AxiResp.OKAY
|
r.rresp = AxiResp.OKAY
|
||||||
@@ -245,5 +255,5 @@ class AxiLiteSlave:
|
|||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.write_if = AxiLiteSlaveWrite(target, bus.write, clock, reset, reset_active_level)
|
self.write_if = AxiLiteSlaveWrite(bus.write, clock, reset, target, reset_active_level)
|
||||||
self.read_if = AxiLiteSlaveRead(target, bus.read, clock, reset, reset_active_level)
|
self.read_if = AxiLiteSlaveRead(bus.read, clock, reset, target, reset_active_level)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -30,6 +30,11 @@ from cocotb.triggers import RisingEdge, Timer, First, Event
|
|||||||
from cocotb.utils import get_sim_time
|
from cocotb.utils import get_sim_time
|
||||||
from cocotb_bus.bus import Bus
|
from cocotb_bus.bus import Bus
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cocotb.types import LogicArray
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .reset import Reset
|
from .reset import Reset
|
||||||
|
|
||||||
@@ -266,11 +271,14 @@ class AxiStreamBase(Reset):
|
|||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
self.reset = reset
|
||||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
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("AXI stream %s", self._type)
|
self.log.info("AXI stream %s", self._type)
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi 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-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@@ -282,12 +290,13 @@ class AxiStreamBase(Reset):
|
|||||||
self.idle_event = Event()
|
self.idle_event = Event()
|
||||||
self.idle_event.set()
|
self.idle_event.set()
|
||||||
self.active_event = Event()
|
self.active_event = Event()
|
||||||
|
self.wake_event = Event()
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
self.width = len(self.bus.tdata)
|
self.width = len(self.bus.tdata)
|
||||||
self.byte_lanes = 1
|
self.byte_lanes = self.width // 8
|
||||||
|
|
||||||
if self._valid_init is not None and hasattr(self.bus, "tvalid"):
|
if self._valid_init is not None and hasattr(self.bus, "tvalid"):
|
||||||
self.bus.tvalid.setimmediatevalue(self._valid_init)
|
self.bus.tvalid.setimmediatevalue(self._valid_init)
|
||||||
@@ -297,9 +306,13 @@ class AxiStreamBase(Reset):
|
|||||||
for sig in self._signals+self._optional_signals:
|
for sig in self._signals+self._optional_signals:
|
||||||
if hasattr(self.bus, sig):
|
if hasattr(self.bus, sig):
|
||||||
if self._init_x and sig not in ("tvalid", "tready"):
|
if self._init_x and sig not in ("tvalid", "tready"):
|
||||||
v = getattr(self.bus, sig).value
|
s = getattr(self.bus, sig)
|
||||||
v.binstr = 'x'*len(v)
|
try:
|
||||||
getattr(self.bus, sig).setimmediatevalue(v)
|
v = LogicArray("x"*len(s.value))
|
||||||
|
except NameError:
|
||||||
|
v = s.value
|
||||||
|
v.binstr = 'x'*len(v)
|
||||||
|
s.setimmediatevalue(v)
|
||||||
|
|
||||||
if hasattr(self.bus, "tkeep"):
|
if hasattr(self.bus, "tkeep"):
|
||||||
self.byte_lanes = len(self.bus.tkeep)
|
self.byte_lanes = len(self.bus.tkeep)
|
||||||
@@ -366,7 +379,7 @@ class AxiStreamBase(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._run_cr is None:
|
if self._run_cr is None:
|
||||||
self._run_cr = cocotb.fork(self._run())
|
self._run_cr = cocotb.start_soon(self._run())
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -376,10 +389,23 @@ class AxiStreamPause:
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.pause = False
|
self._pause = False
|
||||||
self._pause_generator = None
|
self._pause_generator = None
|
||||||
self._pause_cr = None
|
self._pause_cr = None
|
||||||
|
|
||||||
|
def _pause_update(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pause(self):
|
||||||
|
return self._pause
|
||||||
|
|
||||||
|
@pause.setter
|
||||||
|
def pause(self, val):
|
||||||
|
if self._pause != val:
|
||||||
|
self._pause_update(val)
|
||||||
|
self._pause = val
|
||||||
|
|
||||||
def set_pause_generator(self, generator=None):
|
def set_pause_generator(self, generator=None):
|
||||||
if self._pause_cr is not None:
|
if self._pause_cr is not None:
|
||||||
self._pause_cr.kill()
|
self._pause_cr.kill()
|
||||||
@@ -388,15 +414,17 @@ class AxiStreamPause:
|
|||||||
self._pause_generator = generator
|
self._pause_generator = generator
|
||||||
|
|
||||||
if self._pause_generator is not None:
|
if self._pause_generator is not None:
|
||||||
self._pause_cr = cocotb.fork(self._run_pause())
|
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||||
|
|
||||||
def clear_pause_generator(self):
|
def clear_pause_generator(self):
|
||||||
self.set_pause_generator(None)
|
self.set_pause_generator(None)
|
||||||
|
|
||||||
async def _run_pause(self):
|
async def _run_pause(self):
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
for val in self._pause_generator:
|
for val in self._pause_generator:
|
||||||
self.pause = val
|
self.pause = val
|
||||||
await RisingEdge(self.clock)
|
await clock_edge_event
|
||||||
|
|
||||||
|
|
||||||
class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||||
@@ -423,6 +451,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
|||||||
frame = AxiStreamFrame(frame)
|
frame = AxiStreamFrame(frame)
|
||||||
await self.queue.put(frame)
|
await self.queue.put(frame)
|
||||||
self.idle_event.clear()
|
self.idle_event.clear()
|
||||||
|
self.active_event.set()
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
@@ -432,6 +461,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
|||||||
frame = AxiStreamFrame(frame)
|
frame = AxiStreamFrame(frame)
|
||||||
self.queue.put_nowait(frame)
|
self.queue.put_nowait(frame)
|
||||||
self.idle_event.clear()
|
self.idle_event.clear()
|
||||||
|
self.active_event.set()
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
@@ -483,15 +513,25 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
|||||||
frame_offset = 0
|
frame_offset = 0
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
has_tready = hasattr(self.bus, "tready")
|
||||||
|
has_tvalid = hasattr(self.bus, "tvalid")
|
||||||
|
has_tlast = hasattr(self.bus, "tlast")
|
||||||
|
has_tkeep = hasattr(self.bus, "tkeep")
|
||||||
|
has_tid = hasattr(self.bus, "tid")
|
||||||
|
has_tdest = hasattr(self.bus, "tdest")
|
||||||
|
has_tuser = hasattr(self.bus, "tuser")
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
tready_sample = (not has_tready) or self.bus.tready.value
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||||
|
|
||||||
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
||||||
if frame is None and not self.queue.empty():
|
if not frame and not self.queue.empty():
|
||||||
frame = self.queue.get_nowait()
|
frame = self.queue.get_nowait()
|
||||||
self.dequeue_event.set()
|
self.dequeue_event.set()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
@@ -529,26 +569,29 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
|||||||
break
|
break
|
||||||
|
|
||||||
self.bus.tdata.value = tdata_val
|
self.bus.tdata.value = tdata_val
|
||||||
if hasattr(self.bus, "tvalid"):
|
if has_tvalid:
|
||||||
self.bus.tvalid.value = 1
|
self.bus.tvalid.value = 1
|
||||||
if hasattr(self.bus, "tlast"):
|
if has_tlast:
|
||||||
self.bus.tlast.value = tlast_val
|
self.bus.tlast.value = tlast_val
|
||||||
if hasattr(self.bus, "tkeep"):
|
if has_tkeep:
|
||||||
self.bus.tkeep.value = tkeep_val
|
self.bus.tkeep.value = tkeep_val
|
||||||
if hasattr(self.bus, "tid"):
|
if has_tid:
|
||||||
self.bus.tid.value = tid_val
|
self.bus.tid.value = tid_val
|
||||||
if hasattr(self.bus, "tdest"):
|
if has_tdest:
|
||||||
self.bus.tdest.value = tdest_val
|
self.bus.tdest.value = tdest_val
|
||||||
if hasattr(self.bus, "tuser"):
|
if has_tuser:
|
||||||
self.bus.tuser.value = tuser_val
|
self.bus.tuser.value = tuser_val
|
||||||
else:
|
else:
|
||||||
if hasattr(self.bus, "tvalid"):
|
if has_tvalid:
|
||||||
self.bus.tvalid.value = 0
|
self.bus.tvalid.value = 0
|
||||||
if hasattr(self.bus, "tlast"):
|
if has_tlast:
|
||||||
self.bus.tlast.value = 0
|
self.bus.tlast.value = 0
|
||||||
self.active = bool(frame)
|
self.active = bool(frame)
|
||||||
if not frame and self.queue.empty():
|
if not frame and self.queue.empty():
|
||||||
self.idle_event.set()
|
self.idle_event.set()
|
||||||
|
self.active_event.clear()
|
||||||
|
|
||||||
|
await self.active_event.wait()
|
||||||
|
|
||||||
|
|
||||||
class AxiStreamMonitor(AxiStreamBase):
|
class AxiStreamMonitor(AxiStreamBase):
|
||||||
@@ -567,11 +610,20 @@ class AxiStreamMonitor(AxiStreamBase):
|
|||||||
|
|
||||||
self.read_queue = []
|
self.read_queue = []
|
||||||
|
|
||||||
|
if hasattr(self.bus, "tvalid"):
|
||||||
|
cocotb.start_soon(self._run_tvalid_monitor())
|
||||||
|
if hasattr(self.bus, "tready"):
|
||||||
|
cocotb.start_soon(self._run_tready_monitor())
|
||||||
|
|
||||||
|
def _dequeue(self, frame):
|
||||||
|
pass
|
||||||
|
|
||||||
def _recv(self, frame, compact=True):
|
def _recv(self, frame, compact=True):
|
||||||
if self.queue.empty():
|
if self.queue.empty():
|
||||||
self.active_event.clear()
|
self.active_event.clear()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
self.queue_occupancy_frames -= 1
|
self.queue_occupancy_frames -= 1
|
||||||
|
self._dequeue(frame)
|
||||||
if compact:
|
if compact:
|
||||||
frame.compact()
|
frame.compact()
|
||||||
return frame
|
return frame
|
||||||
@@ -611,19 +663,45 @@ class AxiStreamMonitor(AxiStreamBase):
|
|||||||
else:
|
else:
|
||||||
await self.active_event.wait()
|
await self.active_event.wait()
|
||||||
|
|
||||||
|
async def _run_tvalid_monitor(self):
|
||||||
|
event = RisingEdge(self.bus.tvalid)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await event
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
|
async def _run_tready_monitor(self):
|
||||||
|
event = RisingEdge(self.bus.tready)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await event
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
has_tready = hasattr(self.bus, "tready")
|
||||||
|
has_tvalid = hasattr(self.bus, "tvalid")
|
||||||
|
has_tlast = hasattr(self.bus, "tlast")
|
||||||
|
has_tkeep = hasattr(self.bus, "tkeep")
|
||||||
|
has_tid = hasattr(self.bus, "tid")
|
||||||
|
has_tdest = hasattr(self.bus, "tdest")
|
||||||
|
has_tuser = hasattr(self.bus, "tuser")
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
wake_event = self.wake_event.wait()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
tready_sample = (not has_tready) or self.bus.tready.value
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||||
|
|
||||||
if tready_sample and tvalid_sample:
|
if tready_sample and tvalid_sample:
|
||||||
if frame is None:
|
if not frame:
|
||||||
if self.byte_size == 8:
|
if self.byte_size == 8:
|
||||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||||
else:
|
else:
|
||||||
@@ -632,17 +710,17 @@ class AxiStreamMonitor(AxiStreamBase):
|
|||||||
self.active = True
|
self.active = True
|
||||||
|
|
||||||
for offset in range(self.byte_lanes):
|
for offset in range(self.byte_lanes):
|
||||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
frame.tdata.append((int(self.bus.tdata.value) >> (offset * self.byte_size)) & self.byte_mask)
|
||||||
if hasattr(self.bus, "tkeep"):
|
if has_tkeep:
|
||||||
frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1)
|
frame.tkeep.append((int(self.bus.tkeep.value) >> offset) & 1)
|
||||||
if hasattr(self.bus, "tid"):
|
if has_tid:
|
||||||
frame.tid.append(self.bus.tid.value.integer)
|
frame.tid.append(int(self.bus.tid.value))
|
||||||
if hasattr(self.bus, "tdest"):
|
if has_tdest:
|
||||||
frame.tdest.append(self.bus.tdest.value.integer)
|
frame.tdest.append(int(self.bus.tdest.value))
|
||||||
if hasattr(self.bus, "tuser"):
|
if has_tuser:
|
||||||
frame.tuser.append(self.bus.tuser.value.integer)
|
frame.tuser.append(int(self.bus.tuser.value))
|
||||||
|
|
||||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
if not has_tlast or self.bus.tlast.value:
|
||||||
frame.sim_time_end = get_sim_time()
|
frame.sim_time_end = get_sim_time()
|
||||||
self.log.info("RX frame: %s", frame)
|
self.log.info("RX frame: %s", frame)
|
||||||
|
|
||||||
@@ -656,6 +734,9 @@ class AxiStreamMonitor(AxiStreamBase):
|
|||||||
else:
|
else:
|
||||||
self.active = bool(frame)
|
self.active = bool(frame)
|
||||||
|
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
|
||||||
|
|
||||||
class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||||
|
|
||||||
@@ -669,11 +750,11 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
|||||||
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
||||||
byte_size=None, byte_lanes=None, *args, **kwargs):
|
byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||||
|
|
||||||
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
|
||||||
|
|
||||||
self.queue_occupancy_limit_bytes = -1
|
self.queue_occupancy_limit_bytes = -1
|
||||||
self.queue_occupancy_limit_frames = -1
|
self.queue_occupancy_limit_frames = -1
|
||||||
|
|
||||||
|
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
||||||
|
|
||||||
def full(self):
|
def full(self):
|
||||||
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
||||||
return True
|
return True
|
||||||
@@ -689,19 +770,39 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
|||||||
if hasattr(self.bus, "tready"):
|
if hasattr(self.bus, "tready"):
|
||||||
self.bus.tready.value = 0
|
self.bus.tready.value = 0
|
||||||
|
|
||||||
|
def _pause_update(self, val):
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
|
def _dequeue(self, frame):
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = None
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
|
has_tready = hasattr(self.bus, "tready")
|
||||||
|
has_tvalid = hasattr(self.bus, "tvalid")
|
||||||
|
has_tlast = hasattr(self.bus, "tlast")
|
||||||
|
has_tkeep = hasattr(self.bus, "tkeep")
|
||||||
|
has_tid = hasattr(self.bus, "tid")
|
||||||
|
has_tdest = hasattr(self.bus, "tdest")
|
||||||
|
has_tuser = hasattr(self.bus, "tuser")
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
wake_event = self.wake_event.wait()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
pause_sample = bool(self.pause)
|
||||||
|
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
tready_sample = (not has_tready) or self.bus.tready.value
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||||
|
|
||||||
if tready_sample and tvalid_sample:
|
if tready_sample and tvalid_sample:
|
||||||
if frame is None:
|
if not frame:
|
||||||
if self.byte_size == 8:
|
if self.byte_size == 8:
|
||||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||||
else:
|
else:
|
||||||
@@ -710,17 +811,17 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
|||||||
self.active = True
|
self.active = True
|
||||||
|
|
||||||
for offset in range(self.byte_lanes):
|
for offset in range(self.byte_lanes):
|
||||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
frame.tdata.append((int(self.bus.tdata.value) >> (offset * self.byte_size)) & self.byte_mask)
|
||||||
if hasattr(self.bus, "tkeep"):
|
if has_tkeep:
|
||||||
frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1)
|
frame.tkeep.append((int(self.bus.tkeep.value) >> offset) & 1)
|
||||||
if hasattr(self.bus, "tid"):
|
if has_tid:
|
||||||
frame.tid.append(self.bus.tid.value.integer)
|
frame.tid.append(int(self.bus.tid.value))
|
||||||
if hasattr(self.bus, "tdest"):
|
if has_tdest:
|
||||||
frame.tdest.append(self.bus.tdest.value.integer)
|
frame.tdest.append(int(self.bus.tdest.value))
|
||||||
if hasattr(self.bus, "tuser"):
|
if has_tuser:
|
||||||
frame.tuser.append(self.bus.tuser.value.integer)
|
frame.tuser.append(int(self.bus.tuser.value))
|
||||||
|
|
||||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
if not has_tlast or self.bus.tlast.value:
|
||||||
frame.sim_time_end = get_sim_time()
|
frame.sim_time_end = get_sim_time()
|
||||||
self.log.info("RX frame: %s", frame)
|
self.log.info("RX frame: %s", frame)
|
||||||
|
|
||||||
@@ -734,5 +835,15 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
|||||||
else:
|
else:
|
||||||
self.active = bool(frame)
|
self.active = bool(frame)
|
||||||
|
|
||||||
if hasattr(self.bus, "tready"):
|
if has_tready:
|
||||||
self.bus.tready.value = (not self.full() and not self.pause)
|
paused = self.full() or pause_sample
|
||||||
|
|
||||||
|
self.bus.tready.value = not paused
|
||||||
|
|
||||||
|
if (not tvalid_sample or paused) and (pause_sample == bool(self.pause)):
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
else:
|
||||||
|
if not tvalid_sample:
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -22,27 +22,24 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import mmap
|
from .sparse_memory import SparseMemory
|
||||||
|
|
||||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
class Memory:
|
class Memory:
|
||||||
def __init__(self, size=1024, mem=None, **kwargs):
|
def __init__(self, size=2**64, mem=None, **kwargs):
|
||||||
if mem is not None:
|
if mem is not None:
|
||||||
self.mem = mem
|
self.mem = mem
|
||||||
else:
|
else:
|
||||||
self.mem = mmap.mmap(-1, size)
|
self.mem = SparseMemory(size)
|
||||||
self.size = len(self.mem)
|
self.size = len(self.mem)
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def read(self, address, length):
|
def read(self, address, length):
|
||||||
self.mem.seek(address)
|
return self.mem[address:address+length]
|
||||||
return self.mem.read(length)
|
|
||||||
|
|
||||||
def write(self, address, data):
|
def write(self, address, data):
|
||||||
self.mem.seek(address)
|
self.mem[address:address+len(data)] = data
|
||||||
self.mem.write(bytes(data))
|
|
||||||
|
|
||||||
def write_words(self, address, data, byteorder='little', ws=2):
|
def write_words(self, address, data, byteorder='little', ws=2):
|
||||||
words = data
|
words = data
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -23,7 +23,7 @@ THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, FallingEdge
|
from cocotb.triggers import Edge
|
||||||
|
|
||||||
|
|
||||||
class Reset:
|
class Reset:
|
||||||
@@ -33,7 +33,7 @@ class Reset:
|
|||||||
self._reset_state = True
|
self._reset_state = True
|
||||||
|
|
||||||
if reset_signal is not None:
|
if reset_signal is not None:
|
||||||
cocotb.fork(self._run_reset(reset_signal, bool(active_level)))
|
cocotb.start_soon(self._run_reset(reset_signal, bool(active_level)))
|
||||||
|
|
||||||
self._update_reset()
|
self._update_reset()
|
||||||
|
|
||||||
@@ -56,11 +56,14 @@ class Reset:
|
|||||||
|
|
||||||
async def _run_reset(self, reset_signal, active_level):
|
async def _run_reset(self, reset_signal, active_level):
|
||||||
while True:
|
while True:
|
||||||
if bool(reset_signal.value):
|
await reset_signal.value_change
|
||||||
await FallingEdge(reset_signal)
|
try:
|
||||||
self._ext_reset = not active_level
|
level = bool(int(reset_signal.value))
|
||||||
self._update_reset()
|
except ValueError:
|
||||||
else:
|
continue
|
||||||
await RisingEdge(reset_signal)
|
if level:
|
||||||
self._ext_reset = active_level
|
self._ext_reset = active_level
|
||||||
self._update_reset()
|
self._update_reset()
|
||||||
|
else:
|
||||||
|
self._ext_reset = not active_level
|
||||||
|
self._update_reset()
|
||||||
|
|||||||
111
cocotbext/axi/sparse_memory.py
Normal file
111
cocotbext/axi/sparse_memory.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 2023-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
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
|
class SparseMemory:
|
||||||
|
def __init__(self, size):
|
||||||
|
self.size = size
|
||||||
|
self.segs = {}
|
||||||
|
|
||||||
|
def read(self, address, length, **kwargs):
|
||||||
|
if address < 0 or address >= self.size:
|
||||||
|
raise ValueError("address out of range")
|
||||||
|
if length < 0:
|
||||||
|
raise ValueError("invalid length")
|
||||||
|
if address+length > self.size:
|
||||||
|
raise ValueError("operation out of range")
|
||||||
|
data = bytearray()
|
||||||
|
while length > 0:
|
||||||
|
block_offset = address & 0xfff
|
||||||
|
block_addr = address - block_offset
|
||||||
|
block_len = min(4096 - block_offset, length)
|
||||||
|
try:
|
||||||
|
block = self.segs[block_addr]
|
||||||
|
except KeyError:
|
||||||
|
block = b'\x00'*4096
|
||||||
|
data.extend(block[block_offset:block_offset+block_len])
|
||||||
|
address += block_len
|
||||||
|
length -= block_len
|
||||||
|
return bytes(data)
|
||||||
|
|
||||||
|
def write(self, address, data, **kwargs):
|
||||||
|
if address < 0 or address >= self.size:
|
||||||
|
raise ValueError("address out of range")
|
||||||
|
if address+len(data) > self.size:
|
||||||
|
raise ValueError("operation out of range")
|
||||||
|
offset = 0
|
||||||
|
length = len(data)
|
||||||
|
while length > 0:
|
||||||
|
block_offset = address & 0xfff
|
||||||
|
block_addr = address - block_offset
|
||||||
|
block_len = min(4096 - block_offset, length)
|
||||||
|
try:
|
||||||
|
block = self.segs[block_addr]
|
||||||
|
except KeyError:
|
||||||
|
block = bytearray(4096)
|
||||||
|
self.segs[block_addr] = block
|
||||||
|
block[block_offset:block_offset+block_len] = data[offset:offset+block_len]
|
||||||
|
address += block_len
|
||||||
|
offset += block_len
|
||||||
|
length -= block_len
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.segs.clear()
|
||||||
|
|
||||||
|
def hexdump(self, address, length, prefix=""):
|
||||||
|
hexdump(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def hexdump_lines(self, address, length, prefix=""):
|
||||||
|
return hexdump_lines(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def hexdump_str(self, address, length, prefix=""):
|
||||||
|
return hexdump_str(self.read(address, length), prefix=prefix, offset=address)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.size
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, int):
|
||||||
|
return self.read(key, 1)[0]
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
start, stop, step = key.indices(self.size)
|
||||||
|
if step == 1:
|
||||||
|
return self.read(start, stop-start)
|
||||||
|
else:
|
||||||
|
raise IndexError("specified step size is not supported")
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if isinstance(key, int):
|
||||||
|
self.write(key, [value])
|
||||||
|
elif isinstance(key, slice):
|
||||||
|
start, stop, step = key.indices(self.size)
|
||||||
|
if step == 1:
|
||||||
|
value = bytes(value)
|
||||||
|
if stop-start != len(value):
|
||||||
|
raise IndexError("slice assignment is wrong size")
|
||||||
|
return self.write(start, value)
|
||||||
|
else:
|
||||||
|
raise IndexError("specified step size is not supported")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -29,6 +29,11 @@ from cocotb.queue import Queue, QueueFull
|
|||||||
from cocotb.triggers import RisingEdge, Event, First, Timer
|
from cocotb.triggers import RisingEdge, Event, First, Timer
|
||||||
from cocotb_bus.bus import Bus
|
from cocotb_bus.bus import Bus
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cocotb.types import LogicArray
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from .reset import Reset
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
@@ -99,6 +104,7 @@ class StreamBase(Reset):
|
|||||||
self.idle_event = Event()
|
self.idle_event = Event()
|
||||||
self.idle_event.set()
|
self.idle_event.set()
|
||||||
self.active_event = Event()
|
self.active_event = Event()
|
||||||
|
self.wake_event = Event()
|
||||||
|
|
||||||
self.ready = None
|
self.ready = None
|
||||||
self.valid = None
|
self.valid = None
|
||||||
@@ -118,9 +124,13 @@ class StreamBase(Reset):
|
|||||||
if sig in self._signal_widths:
|
if sig in self._signal_widths:
|
||||||
assert len(getattr(self.bus, sig)) == self._signal_widths[sig]
|
assert len(getattr(self.bus, sig)) == self._signal_widths[sig]
|
||||||
if self._init_x and sig not in (self._valid_signal, self._ready_signal):
|
if self._init_x and sig not in (self._valid_signal, self._ready_signal):
|
||||||
v = getattr(self.bus, sig).value
|
s = getattr(self.bus, sig)
|
||||||
v.binstr = 'x'*len(v)
|
try:
|
||||||
getattr(self.bus, sig).setimmediatevalue(v)
|
v = LogicArray("x"*len(s.value))
|
||||||
|
except NameError:
|
||||||
|
v = s.value
|
||||||
|
v.binstr = 'x'*len(v)
|
||||||
|
s.setimmediatevalue(v)
|
||||||
|
|
||||||
self._run_cr = None
|
self._run_cr = None
|
||||||
|
|
||||||
@@ -153,7 +163,7 @@ class StreamBase(Reset):
|
|||||||
else:
|
else:
|
||||||
self.log.info("Reset de-asserted")
|
self.log.info("Reset de-asserted")
|
||||||
if self._run_cr is None:
|
if self._run_cr is None:
|
||||||
self._run_cr = cocotb.fork(self._run())
|
self._run_cr = cocotb.start_soon(self._run())
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@@ -163,10 +173,23 @@ class StreamPause:
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.pause = False
|
self._pause = False
|
||||||
self._pause_generator = None
|
self._pause_generator = None
|
||||||
self._pause_cr = None
|
self._pause_cr = None
|
||||||
|
|
||||||
|
def _pause_update(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pause(self):
|
||||||
|
return self._pause
|
||||||
|
|
||||||
|
@pause.setter
|
||||||
|
def pause(self, val):
|
||||||
|
if self._pause != val:
|
||||||
|
self._pause_update(val)
|
||||||
|
self._pause = val
|
||||||
|
|
||||||
def set_pause_generator(self, generator=None):
|
def set_pause_generator(self, generator=None):
|
||||||
if self._pause_cr is not None:
|
if self._pause_cr is not None:
|
||||||
self._pause_cr.kill()
|
self._pause_cr.kill()
|
||||||
@@ -175,15 +198,17 @@ class StreamPause:
|
|||||||
self._pause_generator = generator
|
self._pause_generator = generator
|
||||||
|
|
||||||
if self._pause_generator is not None:
|
if self._pause_generator is not None:
|
||||||
self._pause_cr = cocotb.fork(self._run_pause())
|
self._pause_cr = cocotb.start_soon(self._run_pause())
|
||||||
|
|
||||||
def clear_pause_generator(self):
|
def clear_pause_generator(self):
|
||||||
self.set_pause_generator(None)
|
self.set_pause_generator(None)
|
||||||
|
|
||||||
async def _run_pause(self):
|
async def _run_pause(self):
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
for val in self._pause_generator:
|
for val in self._pause_generator:
|
||||||
self.pause = val
|
self.pause = val
|
||||||
await RisingEdge(self.clock)
|
await clock_edge_event
|
||||||
|
|
||||||
|
|
||||||
class StreamSource(StreamBase, StreamPause):
|
class StreamSource(StreamBase, StreamPause):
|
||||||
@@ -204,12 +229,14 @@ class StreamSource(StreamBase, StreamPause):
|
|||||||
await self.dequeue_event.wait()
|
await self.dequeue_event.wait()
|
||||||
await self.queue.put(obj)
|
await self.queue.put(obj)
|
||||||
self.idle_event.clear()
|
self.idle_event.clear()
|
||||||
|
self.active_event.set()
|
||||||
|
|
||||||
def send_nowait(self, obj):
|
def send_nowait(self, obj):
|
||||||
if self.full():
|
if self.full():
|
||||||
raise QueueFull()
|
raise QueueFull()
|
||||||
self.queue.put_nowait(obj)
|
self.queue.put_nowait(obj)
|
||||||
self.idle_event.clear()
|
self.idle_event.clear()
|
||||||
|
self.active_event.set()
|
||||||
|
|
||||||
def full(self):
|
def full(self):
|
||||||
if self.queue_occupancy_limit > 0 and self.count() >= self.queue_occupancy_limit:
|
if self.queue_occupancy_limit > 0 and self.count() >= self.queue_occupancy_limit:
|
||||||
@@ -231,26 +258,34 @@ class StreamSource(StreamBase, StreamPause):
|
|||||||
self.valid.value = 0
|
self.valid.value = 0
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
|
has_valid = self.valid is not None
|
||||||
|
has_ready = self.ready is not None
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
ready_sample = self.ready is None or self.ready.value
|
ready_sample = not has_ready or self.ready.value
|
||||||
valid_sample = self.valid is None or self.valid.value
|
valid_sample = not has_valid or self.valid.value
|
||||||
|
|
||||||
if (ready_sample and valid_sample) or (not valid_sample):
|
if (ready_sample and valid_sample) or (not valid_sample):
|
||||||
if not self.queue.empty() and not self.pause:
|
if not self.queue.empty() and not self.pause:
|
||||||
self.bus.drive(self.queue.get_nowait())
|
self.bus.drive(self.queue.get_nowait())
|
||||||
self.dequeue_event.set()
|
self.dequeue_event.set()
|
||||||
if self.valid is not None:
|
if has_valid:
|
||||||
self.valid.value = 1
|
self.valid.value = 1
|
||||||
self.active = True
|
self.active = True
|
||||||
else:
|
else:
|
||||||
if self.valid is not None:
|
if has_valid:
|
||||||
self.valid.value = 0
|
self.valid.value = 0
|
||||||
self.active = not self.queue.empty()
|
self.active = not self.queue.empty()
|
||||||
if self.queue.empty():
|
if self.queue.empty():
|
||||||
self.idle_event.set()
|
self.idle_event.set()
|
||||||
|
self.active_event.clear()
|
||||||
|
|
||||||
|
await self.active_event.wait()
|
||||||
|
|
||||||
|
|
||||||
class StreamMonitor(StreamBase):
|
class StreamMonitor(StreamBase):
|
||||||
@@ -260,9 +295,21 @@ class StreamMonitor(StreamBase):
|
|||||||
_valid_init = None
|
_valid_init = None
|
||||||
_ready_init = None
|
_ready_init = None
|
||||||
|
|
||||||
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, *args, **kwargs):
|
||||||
|
super().__init__(bus, clock, reset, reset_active_level, *args, **kwargs)
|
||||||
|
|
||||||
|
if self.valid is not None:
|
||||||
|
cocotb.start_soon(self._run_valid_monitor())
|
||||||
|
if self.ready is not None:
|
||||||
|
cocotb.start_soon(self._run_ready_monitor())
|
||||||
|
|
||||||
|
def _dequeue(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
def _recv(self, item):
|
def _recv(self, item):
|
||||||
if self.queue.empty():
|
if self.queue.empty():
|
||||||
self.active_event.clear()
|
self.active_event.clear()
|
||||||
|
self._dequeue(item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
async def recv(self):
|
async def recv(self):
|
||||||
@@ -281,19 +328,43 @@ class StreamMonitor(StreamBase):
|
|||||||
else:
|
else:
|
||||||
await self.active_event.wait()
|
await self.active_event.wait()
|
||||||
|
|
||||||
async def _run(self):
|
async def _run_valid_monitor(self):
|
||||||
|
event = RisingEdge(self.valid)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
await event
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
|
async def _run_ready_monitor(self):
|
||||||
|
event = RisingEdge(self.ready)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await event
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
has_valid = self.valid is not None
|
||||||
|
has_ready = self.ready is not None
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
wake_event = self.wake_event.wait()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
ready_sample = self.ready is None or self.ready.value
|
ready_sample = not has_ready or self.ready.value
|
||||||
valid_sample = self.valid is None or self.valid.value
|
valid_sample = not has_valid or self.valid.value
|
||||||
|
|
||||||
if ready_sample and valid_sample:
|
if ready_sample and valid_sample:
|
||||||
obj = self._transaction_obj()
|
obj = self._transaction_obj()
|
||||||
self.bus.sample(obj)
|
self.bus.sample(obj)
|
||||||
self.queue.put_nowait(obj)
|
self.queue.put_nowait(obj)
|
||||||
self.active_event.set()
|
self.active_event.set()
|
||||||
|
else:
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
|
||||||
|
|
||||||
class StreamSink(StreamMonitor, StreamPause):
|
class StreamSink(StreamMonitor, StreamPause):
|
||||||
@@ -321,13 +392,28 @@ class StreamSink(StreamMonitor, StreamPause):
|
|||||||
if self.ready is not None:
|
if self.ready is not None:
|
||||||
self.ready.value = 0
|
self.ready.value = 0
|
||||||
|
|
||||||
|
def _pause_update(self, val):
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
|
def _dequeue(self, item):
|
||||||
|
self.wake_event.set()
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
|
has_valid = self.valid is not None
|
||||||
|
has_ready = self.ready is not None
|
||||||
|
|
||||||
|
clock_edge_event = RisingEdge(self.clock)
|
||||||
|
|
||||||
|
wake_event = self.wake_event.wait()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await RisingEdge(self.clock)
|
pause_sample = bool(self.pause)
|
||||||
|
|
||||||
|
await clock_edge_event
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
ready_sample = self.ready is None or self.ready.value
|
ready_sample = not has_ready or self.ready.value
|
||||||
valid_sample = self.valid is None or self.valid.value
|
valid_sample = not has_valid or self.valid.value
|
||||||
|
|
||||||
if ready_sample and valid_sample:
|
if ready_sample and valid_sample:
|
||||||
obj = self._transaction_obj()
|
obj = self._transaction_obj()
|
||||||
@@ -335,8 +421,18 @@ class StreamSink(StreamMonitor, StreamPause):
|
|||||||
self.queue.put_nowait(obj)
|
self.queue.put_nowait(obj)
|
||||||
self.active_event.set()
|
self.active_event.set()
|
||||||
|
|
||||||
if self.ready is not None:
|
if has_ready:
|
||||||
self.ready.value = (not self.full() and not self.pause)
|
paused = self.full() or pause_sample
|
||||||
|
|
||||||
|
self.ready.value = not paused
|
||||||
|
|
||||||
|
if (not valid_sample or paused) and (pause_sample == bool(self.pause)):
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
else:
|
||||||
|
if not valid_sample:
|
||||||
|
self.wake_event.clear()
|
||||||
|
await wake_event
|
||||||
|
|
||||||
|
|
||||||
def define_stream(name, signals, optional_signals=None, valid_signal=None, ready_signal=None, signal_widths=None):
|
def define_stream(name, signals, optional_signals=None, valid_signal=None, ready_signal=None, signal_widths=None):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.16"
|
__version__ = "0.1.29"
|
||||||
|
|||||||
33
setup.cfg
33
setup.cfg
@@ -13,7 +13,7 @@ project_urls =
|
|||||||
Source Code = https://github.com/alexforencich/cocotbext-axi
|
Source Code = https://github.com/alexforencich/cocotbext-axi
|
||||||
download_url = https://github.com/alexforencich/cocotbext-axi/tarball/master
|
download_url = https://github.com/alexforencich/cocotbext-axi/tarball/master
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long-description-content-type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
platforms = any
|
platforms = any
|
||||||
classifiers =
|
classifiers =
|
||||||
Development Status :: 3 - Alpha
|
Development Status :: 3 - Alpha
|
||||||
@@ -27,7 +27,7 @@ classifiers =
|
|||||||
packages = find_namespace:
|
packages = find_namespace:
|
||||||
python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
install_requires =
|
install_requires =
|
||||||
cocotb
|
cocotb >= 1.6.0
|
||||||
cocotb-bus
|
cocotb-bus
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
@@ -47,31 +47,40 @@ addopts =
|
|||||||
|
|
||||||
# tox configuration
|
# tox configuration
|
||||||
[tox:tox]
|
[tox:tox]
|
||||||
envlist = py36, py37, py38, py39
|
envlist = py38, py39, py310, py311, py312, py313
|
||||||
|
skip_missing_interpreters = true
|
||||||
|
minversion = 3.18.0
|
||||||
|
requires = virtualenv >= 16.1
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
3.6: py36
|
|
||||||
3.7: py37
|
|
||||||
3.8: py38
|
3.8: py38
|
||||||
3.9: py39
|
3.9: py39
|
||||||
|
3.10: py310
|
||||||
|
3.11: py311
|
||||||
|
3.12: py312
|
||||||
|
3.13: py313
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
setenv =
|
setenv =
|
||||||
COVERAGE=1
|
COVERAGE=1
|
||||||
|
usedevelop = True
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
pytest
|
pytest == 8.3.4
|
||||||
pytest-xdist
|
pytest-xdist == 3.6.1
|
||||||
cocotb-test
|
cocotb == 1.9.2
|
||||||
coverage
|
cocotb-bus == 0.2.1
|
||||||
pytest-cov
|
cocotb-test == 0.2.6
|
||||||
|
coverage == 7.0.5
|
||||||
|
pytest-cov == 4.0.0
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
pytest --cov=cocotbext --cov=tests --cov-branch -n auto
|
pytest --cov=cocotbext --cov=tests --cov-branch {posargs:-n auto --verbose}
|
||||||
bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append'
|
bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append'
|
||||||
|
coverage report
|
||||||
|
|
||||||
whitelist_externals =
|
allowlist_externals =
|
||||||
bash
|
bash
|
||||||
|
|
||||||
# combine if paths are different
|
# combine if paths are different
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Alex Forencich
|
# Copyright (c) 2020-2025 Alex Forencich
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
54
tests/apb/Makefile
Normal file
54
tests/apb/Makefile
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright (c) 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
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
TOPLEVEL_LANG = verilog
|
||||||
|
|
||||||
|
SIM ?= icarus
|
||||||
|
WAVES ?= 0
|
||||||
|
|
||||||
|
COCOTB_HDL_TIMEUNIT = 1ns
|
||||||
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
|
DUT = test_apb
|
||||||
|
COCOTB_TEST_MODULES = $(DUT)
|
||||||
|
COCOTB_TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(COCOTB_TEST_MODULES)
|
||||||
|
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||||
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
|
# module parameters
|
||||||
|
export PARAM_DATA_W := 32
|
||||||
|
export PARAM_ADDR_W := 32
|
||||||
|
export PARAM_STRB_W := $(shell expr $(PARAM_DATA_W) / 8 )
|
||||||
|
|
||||||
|
ifeq ($(SIM), icarus)
|
||||||
|
PLUSARGS += -fst
|
||||||
|
|
||||||
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
|
else ifeq ($(SIM), verilator)
|
||||||
|
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
|
||||||
332
tests/apb/test_apb.py
Normal file
332
tests/apb/test_apb.py
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
Copyright (c) 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
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
import cocotb_test.simulator
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import RisingEdge, Timer
|
||||||
|
from cocotb.regression import TestFactory
|
||||||
|
|
||||||
|
from cocotbext.axi import ApbBus, ApbMaster, ApbRam
|
||||||
|
|
||||||
|
|
||||||
|
class TB:
|
||||||
|
def __init__(self, dut):
|
||||||
|
self.dut = dut
|
||||||
|
|
||||||
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
|
self.apb_master = ApbMaster(ApbBus.from_prefix(dut, "apb"), dut.clk, dut.rst)
|
||||||
|
self.apb_ram = ApbRam(ApbBus.from_prefix(dut, "apb"), dut.clk, dut.rst, size=2**16)
|
||||||
|
|
||||||
|
def set_idle_generator(self, generator=None):
|
||||||
|
if generator:
|
||||||
|
self.apb_master.set_pause_generator(generator())
|
||||||
|
|
||||||
|
def set_backpressure_generator(self, generator=None):
|
||||||
|
if generator:
|
||||||
|
self.apb_ram.set_pause_generator(generator())
|
||||||
|
|
||||||
|
async def cycle_reset(self):
|
||||||
|
self.dut.rst.setimmediatevalue(0)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst.value = 1
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
self.dut.rst.value = 0
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
await RisingEdge(self.dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_write(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_lanes = tb.apb_master.byte_lanes
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
tb.set_idle_generator(idle_inserter)
|
||||||
|
tb.set_backpressure_generator(backpressure_inserter)
|
||||||
|
|
||||||
|
for length in range(1, byte_lanes*2):
|
||||||
|
for offset in range(byte_lanes):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x1000
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
|
||||||
|
tb.apb_ram.write(addr-128, b'\xaa'*(length+256))
|
||||||
|
|
||||||
|
await tb.apb_master.write(addr, test_data)
|
||||||
|
|
||||||
|
tb.log.debug("%s", tb.apb_ram.hexdump_str((addr & ~0xf)-16, (((addr & 0xf)+length-1) & ~0xf)+48))
|
||||||
|
|
||||||
|
assert tb.apb_ram.read(addr, length) == test_data
|
||||||
|
assert tb.apb_ram.read(addr-1, 1) == b'\xaa'
|
||||||
|
assert tb.apb_ram.read(addr+length, 1) == b'\xaa'
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_lanes = tb.apb_master.byte_lanes
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
tb.set_idle_generator(idle_inserter)
|
||||||
|
tb.set_backpressure_generator(backpressure_inserter)
|
||||||
|
|
||||||
|
for length in range(1, byte_lanes*2):
|
||||||
|
for offset in range(byte_lanes):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x1000
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
|
||||||
|
tb.apb_ram.write(addr, test_data)
|
||||||
|
|
||||||
|
data = await tb.apb_master.read(addr, length)
|
||||||
|
|
||||||
|
assert data.data == test_data
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_write_words(dut):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_lanes = tb.apb_master.byte_lanes
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
for length in list(range(1, 4)):
|
||||||
|
for offset in list(range(byte_lanes)):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x1000
|
||||||
|
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
event = tb.apb_master.init_write(addr, test_data)
|
||||||
|
await event.wait()
|
||||||
|
assert tb.apb_ram.read(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
await tb.apb_master.write(addr, test_data)
|
||||||
|
assert tb.apb_ram.read(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x1001 for x in range(length)]
|
||||||
|
await tb.apb_master.write_words(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_words(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x10200201 for x in range(length)]
|
||||||
|
await tb.apb_master.write_dwords(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_dwords(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x1020304004030201 for x in range(length)]
|
||||||
|
await tb.apb_master.write_qwords(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_qwords(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = 0x01*length
|
||||||
|
await tb.apb_master.write_byte(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_byte(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x1001*length
|
||||||
|
await tb.apb_master.write_word(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_word(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x10200201*length
|
||||||
|
await tb.apb_master.write_dword(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_dword(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x1020304004030201*length
|
||||||
|
await tb.apb_master.write_qword(addr, test_data)
|
||||||
|
assert tb.apb_ram.read_qword(addr) == test_data
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_test_read_words(dut):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
byte_lanes = tb.apb_master.byte_lanes
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
for length in list(range(1, 4)):
|
||||||
|
for offset in list(range(byte_lanes)):
|
||||||
|
tb.log.info("length %d, offset %d", length, offset)
|
||||||
|
addr = offset+0x1000
|
||||||
|
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
tb.apb_ram.write(addr, test_data)
|
||||||
|
event = tb.apb_master.init_read(addr, length)
|
||||||
|
await event.wait()
|
||||||
|
assert event.data.data == test_data
|
||||||
|
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
tb.apb_ram.write(addr, test_data)
|
||||||
|
assert (await tb.apb_master.read(addr, length)).data == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x1001 for x in range(length)]
|
||||||
|
tb.apb_ram.write_words(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_words(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x10200201 for x in range(length)]
|
||||||
|
tb.apb_ram.write_dwords(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_dwords(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = [x * 0x1020304004030201 for x in range(length)]
|
||||||
|
tb.apb_ram.write_qwords(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_qwords(addr, length) == test_data
|
||||||
|
|
||||||
|
test_data = 0x01*length
|
||||||
|
tb.apb_ram.write_byte(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_byte(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x1001*length
|
||||||
|
tb.apb_ram.write_word(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_word(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x10200201*length
|
||||||
|
tb.apb_ram.write_dword(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_dword(addr) == test_data
|
||||||
|
|
||||||
|
test_data = 0x1020304004030201*length
|
||||||
|
tb.apb_ram.write_qword(addr, test_data)
|
||||||
|
assert await tb.apb_master.read_qword(addr) == test_data
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None):
|
||||||
|
|
||||||
|
tb = TB(dut)
|
||||||
|
|
||||||
|
await tb.cycle_reset()
|
||||||
|
|
||||||
|
tb.set_idle_generator(idle_inserter)
|
||||||
|
tb.set_backpressure_generator(backpressure_inserter)
|
||||||
|
|
||||||
|
async def worker(master, offset, aperture, count=16):
|
||||||
|
for k in range(count):
|
||||||
|
length = random.randint(1, min(32, aperture))
|
||||||
|
addr = offset+random.randint(0, aperture-length)
|
||||||
|
test_data = bytearray([x % 256 for x in range(length)])
|
||||||
|
|
||||||
|
await Timer(random.randint(1, 100), 'ns')
|
||||||
|
|
||||||
|
await master.write(addr, test_data)
|
||||||
|
|
||||||
|
await Timer(random.randint(1, 100), 'ns')
|
||||||
|
|
||||||
|
data = await master.read(addr, length)
|
||||||
|
assert data.data == test_data
|
||||||
|
|
||||||
|
workers = []
|
||||||
|
|
||||||
|
for k in range(16):
|
||||||
|
workers.append(cocotb.start_soon(worker(tb.apb_master, k*0x1000, 0x1000, count=16)))
|
||||||
|
|
||||||
|
while workers:
|
||||||
|
await workers.pop(0).join()
|
||||||
|
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
await RisingEdge(dut.clk)
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_pause():
|
||||||
|
return itertools.cycle([1, 1, 1, 0])
|
||||||
|
|
||||||
|
|
||||||
|
if getattr(cocotb, 'top', None) is not None:
|
||||||
|
|
||||||
|
for test in [run_test_write, run_test_read]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.add_option("idle_inserter", [None, cycle_pause])
|
||||||
|
factory.add_option("backpressure_inserter", [None, cycle_pause])
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
for test in [run_test_write_words, run_test_read_words]:
|
||||||
|
|
||||||
|
factory = TestFactory(test)
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
factory = TestFactory(run_stress_test)
|
||||||
|
factory.generate_tests()
|
||||||
|
|
||||||
|
|
||||||
|
# cocotb-test
|
||||||
|
|
||||||
|
tests_dir = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("data_w", [8, 16, 32])
|
||||||
|
def test_apb(request, data_w):
|
||||||
|
dut = "test_apb"
|
||||||
|
module = os.path.splitext(os.path.basename(__file__))[0]
|
||||||
|
toplevel = dut
|
||||||
|
|
||||||
|
verilog_sources = [
|
||||||
|
os.path.join(os.path.dirname(__file__), f"{dut}.v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
parameters = {}
|
||||||
|
|
||||||
|
parameters['DATA_W'] = data_w
|
||||||
|
parameters['ADDR_W'] = 32
|
||||||
|
parameters['STRB_W'] = parameters['DATA_W'] // 8
|
||||||
|
|
||||||
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
|
cocotb_test.simulator.run(
|
||||||
|
python_search=[tests_dir],
|
||||||
|
verilog_sources=verilog_sources,
|
||||||
|
toplevel=toplevel,
|
||||||
|
module=module,
|
||||||
|
parameters=parameters,
|
||||||
|
sim_build=sim_build,
|
||||||
|
extra_env=extra_env,
|
||||||
|
)
|
||||||
54
tests/apb/test_apb.v
Normal file
54
tests/apb/test_apb.v
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 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
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Language: Verilog 2001
|
||||||
|
|
||||||
|
`timescale 1ns / 1ns
|
||||||
|
|
||||||
|
/*
|
||||||
|
* APB test module
|
||||||
|
*/
|
||||||
|
module test_apb #
|
||||||
|
(
|
||||||
|
parameter DATA_W = 32,
|
||||||
|
parameter ADDR_W = 16,
|
||||||
|
parameter STRB_W = (DATA_W/8)
|
||||||
|
)
|
||||||
|
(
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
inout wire [ADDR_W-1:0] apb_paddr,
|
||||||
|
inout wire [2:0] apb_pprot,
|
||||||
|
inout wire apb_psel,
|
||||||
|
inout wire apb_penable,
|
||||||
|
inout wire apb_pwrite,
|
||||||
|
inout wire [DATA_W-1:0] apb_pwdata,
|
||||||
|
inout wire [STRB_W-1:0] apb_pstrb,
|
||||||
|
inout wire apb_pready,
|
||||||
|
inout wire [DATA_W-1:0] apb_prdata,
|
||||||
|
inout wire apb_pslverr
|
||||||
|
);
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Alex Forencich
|
# Copyright (c) 2020-2025 Alex Forencich
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,66 +27,34 @@ COCOTB_HDL_TIMEUNIT = 1ns
|
|||||||
COCOTB_HDL_TIMEPRECISION = 1ns
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
DUT = test_axi
|
DUT = test_axi
|
||||||
TOPLEVEL = $(DUT)
|
COCOTB_TEST_MODULES = $(DUT)
|
||||||
MODULE = $(DUT)
|
COCOTB_TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(COCOTB_TEST_MODULES)
|
||||||
|
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||||
VERILOG_SOURCES += $(DUT).v
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
# module parameters
|
# module parameters
|
||||||
export PARAM_DATA_WIDTH ?= 32
|
export PARAM_DATA_WIDTH := 32
|
||||||
export PARAM_ADDR_WIDTH ?= 32
|
export PARAM_ADDR_WIDTH := 32
|
||||||
export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
export PARAM_STRB_WIDTH := $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||||
export PARAM_ID_WIDTH ?= 8
|
export PARAM_ID_WIDTH := 8
|
||||||
export PARAM_AWUSER_WIDTH ?= 1
|
export PARAM_AWUSER_WIDTH := 1
|
||||||
export PARAM_WUSER_WIDTH ?= 1
|
export PARAM_WUSER_WIDTH := 1
|
||||||
export PARAM_BUSER_WIDTH ?= 1
|
export PARAM_BUSER_WIDTH := 1
|
||||||
export PARAM_ARUSER_WIDTH ?= 1
|
export PARAM_ARUSER_WIDTH := 1
|
||||||
export PARAM_RUSER_WIDTH ?= 1
|
export PARAM_RUSER_WIDTH := 1
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).AWUSER_WIDTH=$(PARAM_AWUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).WUSER_WIDTH=$(PARAM_WUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).BUSER_WIDTH=$(PARAM_BUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ARUSER_WIDTH=$(PARAM_ARUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).RUSER_WIDTH=$(PARAM_RUSER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
|
||||||
COMPILE_ARGS += -s iverilog_dump
|
|
||||||
endif
|
|
||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
|
||||||
COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
COMPILE_ARGS += -GID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -GAWUSER_WIDTH=$(PARAM_AWUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GWUSER_WIDTH=$(PARAM_WUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GBUSER_WIDTH=$(PARAM_BUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GARUSER_WIDTH=$(PARAM_ARUSER_WIDTH)
|
|
||||||
COMPILE_ARGS += -GRUSER_WIDTH=$(PARAM_RUSER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
VERILATOR_TRACE = 1
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
|
||||||
echo 'module iverilog_dump();' > $@
|
|
||||||
echo 'initial begin' >> $@
|
|
||||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
|
||||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
|
||||||
echo 'end' >> $@
|
|
||||||
echo 'endmodule' >> $@
|
|
||||||
|
|
||||||
clean::
|
|
||||||
@rm -rf iverilog_dump.v
|
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -45,7 +45,7 @@ class TB:
|
|||||||
self.log = logging.getLogger("cocotb.tb")
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
self.log.setLevel(logging.DEBUG)
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.axi_master = AxiMaster(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst)
|
self.axi_master = AxiMaster(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst)
|
||||||
self.axi_ram = AxiRam(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst, size=2**16)
|
self.axi_ram = AxiRam(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst, size=2**16)
|
||||||
@@ -283,7 +283,7 @@ async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None):
|
|||||||
workers = []
|
workers = []
|
||||||
|
|
||||||
for k in range(16):
|
for k in range(16):
|
||||||
workers.append(cocotb.fork(worker(tb.axi_master, k*0x1000, 0x1000, count=16)))
|
workers.append(cocotb.start_soon(worker(tb.axi_master, k*0x1000, 0x1000, count=16)))
|
||||||
|
|
||||||
while workers:
|
while workers:
|
||||||
await workers.pop(0).join()
|
await workers.pop(0).join()
|
||||||
@@ -296,7 +296,7 @@ def cycle_pause():
|
|||||||
return itertools.cycle([1, 1, 1, 0])
|
return itertools.cycle([1, 1, 1, 0])
|
||||||
|
|
||||||
|
|
||||||
if cocotb.SIM_NAME:
|
if getattr(cocotb, 'top', None) is not None:
|
||||||
|
|
||||||
data_width = len(cocotb.top.axi_wdata)
|
data_width = len(cocotb.top.axi_wdata)
|
||||||
byte_lanes = data_width // 8
|
byte_lanes = data_width // 8
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Alex Forencich
|
# Copyright (c) 2020-2025 Alex Forencich
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,48 +27,28 @@ COCOTB_HDL_TIMEUNIT = 1ns
|
|||||||
COCOTB_HDL_TIMEPRECISION = 1ns
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
DUT = test_axil
|
DUT = test_axil
|
||||||
TOPLEVEL = $(DUT)
|
COCOTB_TEST_MODULES = $(DUT)
|
||||||
MODULE = $(DUT)
|
COCOTB_TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(COCOTB_TEST_MODULES)
|
||||||
|
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||||
VERILOG_SOURCES += $(DUT).v
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
# module parameters
|
# module parameters
|
||||||
export PARAM_DATA_WIDTH ?= 32
|
export PARAM_DATA_WIDTH := 32
|
||||||
export PARAM_ADDR_WIDTH ?= 32
|
export PARAM_ADDR_WIDTH := 32
|
||||||
export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
export PARAM_STRB_WIDTH := $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
|
||||||
COMPILE_ARGS += -s iverilog_dump
|
|
||||||
endif
|
|
||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
|
||||||
COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
|
||||||
COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
VERILATOR_TRACE = 1
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
|
||||||
echo 'module iverilog_dump();' > $@
|
|
||||||
echo 'initial begin' >> $@
|
|
||||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
|
||||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
|
||||||
echo 'end' >> $@
|
|
||||||
echo 'endmodule' >> $@
|
|
||||||
|
|
||||||
clean::
|
|
||||||
@rm -rf iverilog_dump.v
|
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -45,7 +45,7 @@ class TB:
|
|||||||
self.log = logging.getLogger("cocotb.tb")
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
self.log.setLevel(logging.DEBUG)
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst)
|
self.axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst)
|
||||||
self.axil_ram = AxiLiteRam(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst, size=2**16)
|
self.axil_ram = AxiLiteRam(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst, size=2**16)
|
||||||
@@ -272,7 +272,7 @@ async def run_stress_test(dut, idle_inserter=None, backpressure_inserter=None):
|
|||||||
workers = []
|
workers = []
|
||||||
|
|
||||||
for k in range(16):
|
for k in range(16):
|
||||||
workers.append(cocotb.fork(worker(tb.axil_master, k*0x1000, 0x1000, count=16)))
|
workers.append(cocotb.start_soon(worker(tb.axil_master, k*0x1000, 0x1000, count=16)))
|
||||||
|
|
||||||
while workers:
|
while workers:
|
||||||
await workers.pop(0).join()
|
await workers.pop(0).join()
|
||||||
@@ -285,7 +285,7 @@ def cycle_pause():
|
|||||||
return itertools.cycle([1, 1, 1, 0])
|
return itertools.cycle([1, 1, 1, 0])
|
||||||
|
|
||||||
|
|
||||||
if cocotb.SIM_NAME:
|
if getattr(cocotb, 'top', None) is not None:
|
||||||
|
|
||||||
for test in [run_test_write, run_test_read]:
|
for test in [run_test_write, run_test_read]:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Alex Forencich
|
# Copyright (c) 2020-2025 Alex Forencich
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -27,54 +27,30 @@ COCOTB_HDL_TIMEUNIT = 1ns
|
|||||||
COCOTB_HDL_TIMEPRECISION = 1ns
|
COCOTB_HDL_TIMEPRECISION = 1ns
|
||||||
|
|
||||||
DUT = test_axis
|
DUT = test_axis
|
||||||
TOPLEVEL = $(DUT)
|
COCOTB_TEST_MODULES = $(DUT)
|
||||||
MODULE = $(DUT)
|
COCOTB_TOPLEVEL = $(DUT)
|
||||||
|
MODULE = $(COCOTB_TEST_MODULES)
|
||||||
|
TOPLEVEL = $(COCOTB_TOPLEVEL)
|
||||||
VERILOG_SOURCES += $(DUT).v
|
VERILOG_SOURCES += $(DUT).v
|
||||||
|
|
||||||
# module parameters
|
# module parameters
|
||||||
export PARAM_DATA_WIDTH ?= 8
|
export PARAM_DATA_WIDTH := 8
|
||||||
export PARAM_KEEP_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
export PARAM_KEEP_WIDTH := $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||||
export PARAM_ID_WIDTH ?= 8
|
export PARAM_ID_WIDTH := 8
|
||||||
export PARAM_DEST_WIDTH ?= 8
|
export PARAM_DEST_WIDTH := 8
|
||||||
export PARAM_USER_WIDTH ?= 1
|
export PARAM_USER_WIDTH := 1
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).KEEP_WIDTH=$(PARAM_KEEP_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).ID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).DEST_WIDTH=$(PARAM_DEST_WIDTH)
|
|
||||||
COMPILE_ARGS += -P $(TOPLEVEL).USER_WIDTH=$(PARAM_USER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
|
||||||
VERILOG_SOURCES += iverilog_dump.v
|
|
||||||
COMPILE_ARGS += -s iverilog_dump
|
|
||||||
endif
|
|
||||||
else ifeq ($(SIM), verilator)
|
else ifeq ($(SIM), verilator)
|
||||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||||
|
|
||||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
|
||||||
COMPILE_ARGS += -GKEEP_WIDTH=$(PARAM_KEEP_WIDTH)
|
|
||||||
COMPILE_ARGS += -GID_WIDTH=$(PARAM_ID_WIDTH)
|
|
||||||
COMPILE_ARGS += -GDEST_WIDTH=$(PARAM_DEST_WIDTH)
|
|
||||||
COMPILE_ARGS += -GUSER_WIDTH=$(PARAM_USER_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(WAVES), 1)
|
ifeq ($(WAVES), 1)
|
||||||
COMPILE_ARGS += --trace-fst
|
COMPILE_ARGS += --trace-fst
|
||||||
|
VERILATOR_TRACE = 1
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
|
||||||
echo 'module iverilog_dump();' > $@
|
|
||||||
echo 'initial begin' >> $@
|
|
||||||
echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@
|
|
||||||
echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@
|
|
||||||
echo 'end' >> $@
|
|
||||||
echo 'endmodule' >> $@
|
|
||||||
|
|
||||||
clean::
|
|
||||||
@rm -rf iverilog_dump.v
|
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -45,7 +45,7 @@ class TB:
|
|||||||
self.log = logging.getLogger("cocotb.tb")
|
self.log = logging.getLogger("cocotb.tb")
|
||||||
self.log.setLevel(logging.DEBUG)
|
self.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.start_soon(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||||
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||||
@@ -135,7 +135,7 @@ def incrementing_payload(length):
|
|||||||
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
return bytearray(itertools.islice(itertools.cycle(range(256)), length))
|
||||||
|
|
||||||
|
|
||||||
if cocotb.SIM_NAME:
|
if getattr(cocotb, 'top', None) is not None:
|
||||||
|
|
||||||
factory = TestFactory(run_test)
|
factory = TestFactory(run_test)
|
||||||
factory.add_option("payload_lengths", [size_list])
|
factory.add_option("payload_lengths", [size_list])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
|
||||||
Copyright (c) 2020 Alex Forencich
|
Copyright (c) 2020-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
Copyright (c) 2021 Alex Forencich
|
Copyright (c) 2021-2025 Alex Forencich
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
Reference in New Issue
Block a user