28 Commits

Author SHA1 Message Date
Alex Forencich
50cf2af49f Release v0.1.22
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-24 01:31:54 -07:00
Alex Forencich
ddfa1e3c92 Update readme
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-24 01:28:25 -07:00
Alex Forencich
5e8b246159 Use slices to access memory contents to support both mmap and SparseMemory
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-24 01:23:33 -07:00
Alex Forencich
62c2eef4ec Add SparseMemoryRegion object
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-23 23:44:29 -07:00
Alex Forencich
ad6012aea5 Update memory models to use SparseMemory
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-23 23:43:53 -07:00
Alex Forencich
432bd81011 Add sparse memory model
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-03-23 19:27:57 -07:00
Alex Forencich
bde123e05f Add transfer length checks
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-02-27 16:38:34 -08:00
Alex Forencich
8604017159 For FIXED burst type, issue all bursts with the same starting address
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-02-27 16:36:04 -08:00
Alex Forencich
e21b9ffcc8 Update ubuntu version in CI
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-02-12 17:31:31 -08:00
Alex Forencich
47cd74eb6c Rework parameter handling in makefiles
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-29 19:50:53 -08:00
Alex Forencich
4bf5945aa3 Bump to dev version
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 18:11:44 -08:00
Alex Forencich
f3a7652362 Release v0.1.20
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 17:55:54 -08:00
Alex Forencich
a84ce5447d Put sinks to sleep when idle
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 17:46:46 -08:00
Alex Forencich
1c03ec4697 Pass through full address for unaligned operations
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-25 16:27:14 -08:00
Alex Forencich
824eba793d Update package versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-24 12:47:15 -08:00
Alex Forencich
a0aad34698 Fix path issue so latest coverage works
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 20:57:30 -08:00
Alex Forencich
ede6270ed7 Put source to sleep when there is no data to send
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:49:16 -08:00
Alex Forencich
cd1a8b47a5 Fix init sequence
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:48:46 -08:00
Alex Forencich
be6d490adb Cache signal presence
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:48:23 -08:00
Alex Forencich
39686b849a Update github actions versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:30:32 -08:00
Alex Forencich
706051cb89 Fix tox config and lock package versions
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-20 15:29:11 -08:00
Alex Forencich
3e4f8d7e92 Python 3.6 is EOL; remove from CI tests
Signed-off-by: Alex Forencich <alex@alexforencich.com>
2023-01-18 14:55:25 -08:00
Leon Woestenberg
afae9e69ff Fix AxiStreamFrame default for self.byte_lanes from 1 to all.
If I connect a AXIS source to an AXIS sink, the #byte_lanes is incorrectly 1 rather than all lanes enabled.
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "sink"), dut.clk, dut.reset)

Root cause is AxiStreamFrame assumes byte width 1 without TKEEP, but it should default to self.width // 8
because the AXIS specification mentions "when TKEEP is absent, TKEEP defaults to all bits HIGH" and "The width of the data payload is an integer number of bytes."

Fix: https://github.com/alexforencich/cocotbext-axi/blob/master/cocotbext/axi/axis.py#L290

self.byte_lanes = 1
self.byte_lanes = self.width // 8

Relevant AXIS Specification:
https://developer.arm.com/documentation/ihi0051/a/Default-Signaling-Requirements/Default-value-signaling/Optional-TKEEP-and-TSTRB?lang=en

Signed-off-by: Leon Woestenberg <leon@sidebranch.com>
2023-01-18 12:55:19 -08:00
Alex Forencich
035c1ba803 Support interleaved read data in AXI master 2022-02-01 00:25:01 -08:00
Alex Forencich
873bb1a034 Explicit cast to integer before converting to enum or flag type 2022-01-07 12:52:41 -08:00
Alex Forencich
2d70e5cbe5 Fix AxiLiteSlave wrapper 2022-01-04 15:29:04 -08:00
Alex Forencich
35d9742ae8 Remove extraneous code 2022-01-04 15:28:48 -08:00
Alex Forencich
0f20e2e9bf Bump to dev version 2021-12-28 20:08:44 -08:00
19 changed files with 438 additions and 150 deletions

View File

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

View File

@@ -155,7 +155,7 @@ It is also possible to extend these modules; operation can be customized by over
### AXI and AXI lite RAM ### AXI and AXI lite 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` 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. 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.
@@ -163,7 +163,7 @@ 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` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
@@ -175,7 +175,7 @@ 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)
@@ -186,12 +186,12 @@ Multi-port memories can be constructed by passing the `mem` object of the first
* _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)

View File

@@ -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

View File

@@ -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
@@ -216,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)

View File

@@ -291,6 +291,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:
@@ -357,6 +360,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:
@@ -476,7 +482,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
@@ -563,7 +569,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)
@@ -577,8 +588,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)
@@ -591,7 +601,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:
@@ -600,8 +610,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
@@ -714,6 +723,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:
@@ -768,6 +780,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:
@@ -872,7 +887,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
@@ -917,7 +932,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)
@@ -925,27 +945,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
@@ -966,14 +973,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:

View File

@@ -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

View File

@@ -115,8 +115,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)
@@ -275,8 +275,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)

View File

@@ -182,6 +182,9 @@ 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")
@@ -286,7 +289,10 @@ 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: if not self.wstrb_present and strb != self.strb_mask:
@@ -311,7 +317,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
@@ -416,6 +422,12 @@ 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")
@@ -494,7 +506,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)
@@ -517,7 +532,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

View File

@@ -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

View File

@@ -63,7 +63,6 @@ class AxiLiteSlaveWrite(Reset):
self.wstrb_present = hasattr(self.bus.w, "wstrb") 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)
@@ -107,7 +106,7 @@ class AxiLiteSlaveWrite(Reset):
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()
@@ -182,7 +181,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)
@@ -223,7 +221,7 @@ class AxiLiteSlaveRead(Reset):
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
@@ -251,5 +249,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)

View File

@@ -282,12 +282,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)
@@ -376,10 +377,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()
@@ -425,6 +439,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
@@ -434,6 +449,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
@@ -485,17 +501,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) clock_edge_event = RisingEdge(self.clock)
while True: while True:
await clock_edge_event 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)
@@ -533,26 +557,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):
@@ -571,11 +598,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
@@ -615,21 +651,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) clock_edge_event = RisingEdge(self.clock)
wake_event = self.wake_event.wait()
while True: while True:
await clock_edge_event 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:
@@ -639,16 +699,16 @@ class AxiStreamMonitor(AxiStreamBase):
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((self.bus.tdata.value.integer >> (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((self.bus.tkeep.value.integer >> offset) & 1)
if hasattr(self.bus, "tid"): if has_tid:
frame.tid.append(self.bus.tid.value.integer) frame.tid.append(self.bus.tid.value.integer)
if hasattr(self.bus, "tdest"): if has_tdest:
frame.tdest.append(self.bus.tdest.value.integer) frame.tdest.append(self.bus.tdest.value.integer)
if hasattr(self.bus, "tuser"): if has_tuser:
frame.tuser.append(self.bus.tuser.value.integer) frame.tuser.append(self.bus.tuser.value.integer)
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)
@@ -662,6 +722,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):
@@ -675,11 +738,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
@@ -695,21 +758,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) clock_edge_event = RisingEdge(self.clock)
wake_event = self.wake_event.wait()
while True: while True:
pause_sample = self.pause
await clock_edge_event 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:
@@ -719,16 +800,16 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
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((self.bus.tdata.value.integer >> (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((self.bus.tkeep.value.integer >> offset) & 1)
if hasattr(self.bus, "tid"): if has_tid:
frame.tid.append(self.bus.tid.value.integer) frame.tid.append(self.bus.tid.value.integer)
if hasattr(self.bus, "tdest"): if has_tdest:
frame.tdest.append(self.bus.tdest.value.integer) frame.tdest.append(self.bus.tdest.value.integer)
if hasattr(self.bus, "tuser"): if has_tuser:
frame.tuser.append(self.bus.tuser.value.integer) frame.tuser.append(self.bus.tuser.value.integer)
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)
@@ -742,5 +823,9 @@ 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) self.bus.tready.value = (not self.full() and not pause_sample)
if not tvalid_sample or (self.pause and pause_sample) or self.full():
self.wake_event.clear()
await wake_event

View File

@@ -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

View File

@@ -0,0 +1,111 @@
"""
Copyright (c) 2023 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")

View File

@@ -99,6 +99,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
@@ -163,10 +164,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()
@@ -206,12 +220,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:
@@ -255,6 +271,9 @@ class StreamSource(StreamBase, StreamPause):
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):
@@ -264,9 +283,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):
@@ -285,9 +316,25 @@ class StreamMonitor(StreamBase):
else: else:
await self.active_event.wait() await self.active_event.wait()
async def _run_valid_monitor(self):
event = RisingEdge(self.valid)
while True:
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): async def _run(self):
clock_edge_event = RisingEdge(self.clock) clock_edge_event = RisingEdge(self.clock)
wake_event = self.wake_event.wait()
while True: while True:
await clock_edge_event await clock_edge_event
@@ -300,6 +347,9 @@ class StreamMonitor(StreamBase):
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):
@@ -327,10 +377,20 @@ 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):
clock_edge_event = RisingEdge(self.clock) clock_edge_event = RisingEdge(self.clock)
wake_event = self.wake_event.wait()
while True: while True:
pause_sample = self.pause
await clock_edge_event await clock_edge_event
# read handshake signals # read handshake signals
@@ -344,7 +404,11 @@ class StreamSink(StreamMonitor, StreamPause):
self.active_event.set() self.active_event.set()
if self.ready is not None: if self.ready is not None:
self.ready.value = (not self.full() and not self.pause) self.ready.value = (not self.full() and not pause_sample)
if not valid_sample or (self.pause and pause_sample) or self.full():
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):

View File

@@ -1 +1 @@
__version__ = "0.1.18" __version__ = "0.1.22"

View File

@@ -47,14 +47,13 @@ addopts =
# tox configuration # tox configuration
[tox:tox] [tox:tox]
envlist = py36, py37, py38, py39, py310 envlist = py37, py38, py39, py310
skip_missing_interpreters = true skip_missing_interpreters = true
minversion = 3.2.0 minversion = 3.18.0
requires = virtualenv >= 16.1 requires = virtualenv >= 16.1
[gh-actions] [gh-actions]
python = python =
3.6: py36
3.7: py37 3.7: py37
3.8: py38 3.8: py38
3.9: py39 3.9: py39
@@ -63,19 +62,23 @@ python =
[testenv] [testenv]
setenv = setenv =
COVERAGE=1 COVERAGE=1
usedevelop = True
deps = deps =
pytest pytest == 7.2.1
pytest-xdist pytest-xdist == 3.1.0
cocotb-test cocotb == 1.7.2
coverage cocotb-bus == 0.2.1
pytest-cov cocotb-test == 0.2.4
coverage == 7.0.5
pytest-cov == 4.0.0
commands = commands =
pytest --cov=cocotbext --cov=tests --cov-branch -n auto pytest --cov=cocotbext --cov=tests --cov-branch {posargs:-n auto --verbose}
bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append' bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append'
coverage report
whitelist_externals = allowlist_externals =
bash bash
# combine if paths are different # combine if paths are different

View File

@@ -45,15 +45,7 @@ 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 $(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) ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v VERILOG_SOURCES += iverilog_dump.v
@@ -62,15 +54,7 @@ ifeq ($(SIM), icarus)
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
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

View File

@@ -39,9 +39,7 @@ 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 $(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) ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v VERILOG_SOURCES += iverilog_dump.v
@@ -50,9 +48,7 @@ ifeq ($(SIM), icarus)
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
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

View File

@@ -41,11 +41,7 @@ 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 $(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) ifeq ($(WAVES), 1)
VERILOG_SOURCES += iverilog_dump.v VERILOG_SOURCES += iverilog_dump.v
@@ -54,11 +50,7 @@ ifeq ($(SIM), icarus)
else ifeq ($(SIM), verilator) else ifeq ($(SIM), verilator)
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH) COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
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