Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
8
.github/workflows/regression-tests.yml
vendored
8
.github/workflows/regression-tests.yml
vendored
@@ -5,17 +5,17 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
name: Python ${{matrix.python-version}}
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -155,7 +155,7 @@ It is also possible to extend these modules; operation can be customized by over
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
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_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)
|
||||
@@ -186,12 +186,12 @@ Multi-port memories can be constructed by passing the `mem` object of the first
|
||||
* _clock_: clock signal
|
||||
* _reset_: reset signal (optional)
|
||||
* _reset_active_level_: reset active level (optional, default `True`)
|
||||
* _size_: memory size in bytes (optional, default 1024)
|
||||
* _mem_: mmap object to use (optional, overrides _size_)
|
||||
* _size_: memory size in bytes (optional, default `2**64`)
|
||||
* _mem_: `mmap` or `SparseMemory` backing object to use (optional, overrides _size_)
|
||||
|
||||
#### Attributes:
|
||||
|
||||
* _mem_: directly access shared `mmap` object
|
||||
* _mem_: directly access shared `mmap` or `SparseMemory` backing object
|
||||
|
||||
#### 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.
|
||||
|
||||
`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`.
|
||||
|
||||
`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.
|
||||
|
||||
from cocotbext.axi import AddressSpace, MemoryRegion
|
||||
from cocotbext.axi import AddressSpace, SparseMemoryRegion
|
||||
from cocotbext.axi import AxiBus, AxiLiteMaster, AxiSlave
|
||||
|
||||
# system address space
|
||||
address_space = AddressSpace(2**32)
|
||||
|
||||
# RAM
|
||||
ram = MemoryRegion(2**24)
|
||||
ram = SparseMemoryRegion(2**24)
|
||||
address_space.register_region(ram, 0x0000_0000)
|
||||
ram_pool = address_space.create_window_pool(0x0000_0000, 2**20)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from .version import __version__
|
||||
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
||||
|
||||
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 .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
|
||||
@@ -25,6 +25,7 @@ THE SOFTWARE.
|
||||
import mmap
|
||||
|
||||
from .buddy_allocator import BuddyAllocator
|
||||
from .sparse_memory import SparseMemory
|
||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||
|
||||
|
||||
@@ -216,6 +217,35 @@ class MemoryRegion(Region):
|
||||
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):
|
||||
def __init__(self, obj, size, **kwargs):
|
||||
super().__init__(size, **kwargs)
|
||||
|
||||
@@ -291,6 +291,9 @@ class AxiMasterWrite(Region, Reset):
|
||||
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:
|
||||
@@ -357,6 +360,9 @@ class AxiMasterWrite(Region, Reset):
|
||||
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:
|
||||
@@ -476,7 +482,7 @@ class AxiMasterWrite(Region, Reset):
|
||||
|
||||
cycles = (len(cmd.data) + (cmd.address % num_bytes) + num_bytes-1) // num_bytes
|
||||
|
||||
cur_addr = aligned_addr
|
||||
cur_addr = cmd.address
|
||||
offset = 0
|
||||
cycle_offset = aligned_addr-word_addr
|
||||
n = 0
|
||||
@@ -563,6 +569,11 @@ class AxiMasterWrite(Region, Reset):
|
||||
|
||||
await self.w_channel.send(w)
|
||||
|
||||
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
|
||||
|
||||
@@ -577,8 +588,7 @@ class AxiMasterWrite(Region, Reset):
|
||||
|
||||
bid = int(getattr(b, 'bid', 0))
|
||||
|
||||
if self.active_id[bid] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {bid}")
|
||||
assert self.active_id[bid] > 0, "unexpected burst ID"
|
||||
|
||||
self.tag_context_manager.put_resp(bid, b)
|
||||
|
||||
@@ -591,7 +601,7 @@ class AxiMasterWrite(Region, Reset):
|
||||
for burst_length in cmd.burst_list:
|
||||
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))
|
||||
|
||||
if burst_resp != AxiResp.OKAY:
|
||||
@@ -600,8 +610,7 @@ class AxiMasterWrite(Region, Reset):
|
||||
if burst_user is not None:
|
||||
user.append(burst_user)
|
||||
|
||||
if self.active_id[bid] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {bid}")
|
||||
assert self.active_id[bid] > 0, "unexpected burst ID"
|
||||
|
||||
self.active_id[bid] -= 1
|
||||
|
||||
@@ -714,6 +723,9 @@ class AxiMasterRead(Region, Reset):
|
||||
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:
|
||||
@@ -768,6 +780,9 @@ class AxiMasterRead(Region, Reset):
|
||||
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:
|
||||
@@ -872,7 +887,7 @@ class AxiMasterRead(Region, Reset):
|
||||
|
||||
burst_list = []
|
||||
|
||||
cur_addr = aligned_addr
|
||||
cur_addr = cmd.address
|
||||
n = 0
|
||||
|
||||
burst_length = 0
|
||||
@@ -917,6 +932,11 @@ class AxiMasterRead(Region, Reset):
|
||||
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)
|
||||
|
||||
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)
|
||||
@@ -925,27 +945,14 @@ class AxiMasterRead(Region, Reset):
|
||||
self.current_read_command = None
|
||||
|
||||
async def _process_read_resp(self):
|
||||
burst = []
|
||||
cur_rid = None
|
||||
|
||||
while True:
|
||||
r = await self.r_channel.recv()
|
||||
|
||||
rid = int(getattr(r, 'rid', 0))
|
||||
|
||||
if cur_rid is not None and cur_rid != rid:
|
||||
raise Exception(f"ID not constant within burst (expected {cur_rid}, got {rid})")
|
||||
assert self.active_id[rid] > 0, "unexpected burst ID"
|
||||
|
||||
if self.active_id[rid] <= 0:
|
||||
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
|
||||
self.tag_context_manager.put_resp(rid, r)
|
||||
|
||||
async def _process_read_resp_id(self, context, cmd):
|
||||
rid = context.current_tag
|
||||
@@ -966,14 +973,18 @@ class AxiMasterRead(Region, Reset):
|
||||
first = True
|
||||
|
||||
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:
|
||||
raise Exception(f"Burst length incorrect (ID {rid}, expected {burst_length}, got {len(burst)}")
|
||||
assert self.active_id[rid] > 0, "unexpected burst ID"
|
||||
|
||||
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_resp = AxiResp(getattr(r, "rresp", AxiResp.OKAY))
|
||||
cycle_resp = AxiResp(int(getattr(r, "rresp", AxiResp.OKAY)))
|
||||
cycle_user = int(getattr(r, "ruser", 0))
|
||||
|
||||
if cycle_resp != AxiResp.OKAY:
|
||||
|
||||
@@ -27,7 +27,7 @@ from .memory import 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)
|
||||
|
||||
async def _write(self, address, data):
|
||||
@@ -35,7 +35,7 @@ class AxiRamWrite(AxiSlaveWrite, 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)
|
||||
|
||||
async def _read(self, address, length):
|
||||
@@ -43,7 +43,7 @@ class AxiRamRead(AxiSlaveRead, 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.read_if = None
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ class AxiSlaveWrite(Reset):
|
||||
addr = int(aw.awaddr)
|
||||
length = int(getattr(aw, 'awlen', 0))
|
||||
size = int(getattr(aw, 'awsize', self.max_burst_size))
|
||||
burst = AxiBurstType(getattr(aw, 'awburst', AxiBurstType.INCR))
|
||||
prot = AxiProt(getattr(aw, 'awprot', AxiProt.NONSECURE))
|
||||
burst = AxiBurstType(int(getattr(aw, 'awburst', AxiBurstType.INCR)))
|
||||
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",
|
||||
awid, addr, length, size, prot)
|
||||
@@ -275,8 +275,8 @@ class AxiSlaveRead(Reset):
|
||||
addr = int(ar.araddr)
|
||||
length = int(getattr(ar, 'arlen', 0))
|
||||
size = int(getattr(ar, 'arsize', self.max_burst_size))
|
||||
burst = AxiBurstType(getattr(ar, 'arburst', AxiBurstType.INCR))
|
||||
prot = AxiProt(getattr(ar, 'arprot', AxiProt.NONSECURE))
|
||||
burst = AxiBurstType(int(getattr(ar, 'arburst', AxiBurstType.INCR)))
|
||||
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",
|
||||
arid, addr, length, size, prot)
|
||||
|
||||
@@ -159,6 +159,9 @@ class AxiLiteMasterWrite(Region, Reset):
|
||||
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")
|
||||
|
||||
@@ -182,6 +185,9 @@ class AxiLiteMasterWrite(Region, Reset):
|
||||
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")
|
||||
|
||||
@@ -286,6 +292,9 @@ class AxiLiteMasterWrite(Region, Reset):
|
||||
offset += 1
|
||||
|
||||
aw = self.aw_channel._transaction_obj()
|
||||
if k == 0:
|
||||
aw.awaddr = cmd.address
|
||||
else:
|
||||
aw.awaddr = word_addr + k*self.byte_lanes
|
||||
aw.awprot = cmd.prot
|
||||
|
||||
@@ -311,7 +320,7 @@ class AxiLiteMasterWrite(Region, Reset):
|
||||
for k in range(cmd.cycles):
|
||||
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:
|
||||
resp = cycle_resp
|
||||
@@ -398,6 +407,12 @@ class AxiLiteMasterRead(Region, Reset):
|
||||
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")
|
||||
|
||||
@@ -416,6 +431,12 @@ class AxiLiteMasterRead(Region, Reset):
|
||||
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")
|
||||
|
||||
@@ -494,6 +515,9 @@ class AxiLiteMasterRead(Region, Reset):
|
||||
|
||||
for k in range(cycles):
|
||||
ar = self.ar_channel._transaction_obj()
|
||||
if k == 0:
|
||||
ar.araddr = cmd.address
|
||||
else:
|
||||
ar.araddr = word_addr + k*self.byte_lanes
|
||||
ar.arprot = cmd.prot
|
||||
|
||||
@@ -517,7 +541,7 @@ class AxiLiteMasterRead(Region, Reset):
|
||||
r = await self.r_channel.recv()
|
||||
|
||||
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:
|
||||
resp = cycle_resp
|
||||
|
||||
@@ -27,7 +27,7 @@ from .memory import 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)
|
||||
|
||||
async def _write(self, address, data):
|
||||
@@ -35,7 +35,7 @@ class AxiLiteRamWrite(AxiLiteSlaveWrite, 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)
|
||||
|
||||
async def _read(self, address, length):
|
||||
@@ -43,7 +43,7 @@ class AxiLiteRamRead(AxiLiteSlaveRead, 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.read_if = None
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ class AxiLiteSlaveWrite(Reset):
|
||||
self.wstrb_present = hasattr(self.bus.w, "wstrb")
|
||||
|
||||
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(" Byte size: %d bits", self.byte_size)
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
@@ -182,7 +181,6 @@ class AxiLiteSlaveRead(Reset):
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
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(" Byte size: %d bits", self.byte_size)
|
||||
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()
|
||||
|
||||
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.rresp = AxiResp.OKAY
|
||||
@@ -251,5 +249,5 @@ class AxiLiteSlave:
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.write_if = AxiLiteSlaveWrite(target, bus.write, clock, reset, reset_active_level)
|
||||
self.read_if = AxiLiteSlaveRead(target, bus.read, clock, reset, reset_active_level)
|
||||
self.write_if = AxiLiteSlaveWrite(bus.write, clock, reset, target, reset_active_level)
|
||||
self.read_if = AxiLiteSlaveRead(bus.read, clock, reset, target, reset_active_level)
|
||||
|
||||
@@ -282,12 +282,13 @@ class AxiStreamBase(Reset):
|
||||
self.idle_event = Event()
|
||||
self.idle_event.set()
|
||||
self.active_event = Event()
|
||||
self.wake_event = Event()
|
||||
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
|
||||
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"):
|
||||
self.bus.tvalid.setimmediatevalue(self._valid_init)
|
||||
@@ -376,10 +377,23 @@ class AxiStreamPause:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.pause = False
|
||||
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()
|
||||
@@ -425,6 +439,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||
frame = AxiStreamFrame(frame)
|
||||
await self.queue.put(frame)
|
||||
self.idle_event.clear()
|
||||
self.active_event.set()
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
@@ -434,6 +449,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||
frame = AxiStreamFrame(frame)
|
||||
self.queue.put_nowait(frame)
|
||||
self.idle_event.clear()
|
||||
self.active_event.set()
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
@@ -485,17 +501,25 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||
frame_offset = 0
|
||||
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:
|
||||
await clock_edge_event
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
tready_sample = (not has_tready) or self.bus.tready.value
|
||||
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||
|
||||
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()
|
||||
self.dequeue_event.set()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
@@ -533,26 +557,29 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||
break
|
||||
|
||||
self.bus.tdata.value = tdata_val
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
if has_tvalid:
|
||||
self.bus.tvalid.value = 1
|
||||
if hasattr(self.bus, "tlast"):
|
||||
if has_tlast:
|
||||
self.bus.tlast.value = tlast_val
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
if has_tkeep:
|
||||
self.bus.tkeep.value = tkeep_val
|
||||
if hasattr(self.bus, "tid"):
|
||||
if has_tid:
|
||||
self.bus.tid.value = tid_val
|
||||
if hasattr(self.bus, "tdest"):
|
||||
if has_tdest:
|
||||
self.bus.tdest.value = tdest_val
|
||||
if hasattr(self.bus, "tuser"):
|
||||
if has_tuser:
|
||||
self.bus.tuser.value = tuser_val
|
||||
else:
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
if has_tvalid:
|
||||
self.bus.tvalid.value = 0
|
||||
if hasattr(self.bus, "tlast"):
|
||||
if has_tlast:
|
||||
self.bus.tlast.value = 0
|
||||
self.active = bool(frame)
|
||||
if not frame and self.queue.empty():
|
||||
self.idle_event.set()
|
||||
self.active_event.clear()
|
||||
|
||||
await self.active_event.wait()
|
||||
|
||||
|
||||
class AxiStreamMonitor(AxiStreamBase):
|
||||
@@ -571,11 +598,20 @@ class AxiStreamMonitor(AxiStreamBase):
|
||||
|
||||
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):
|
||||
if self.queue.empty():
|
||||
self.active_event.clear()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
self.queue_occupancy_frames -= 1
|
||||
self._dequeue(frame)
|
||||
if compact:
|
||||
frame.compact()
|
||||
return frame
|
||||
@@ -615,21 +651,45 @@ class AxiStreamMonitor(AxiStreamBase):
|
||||
else:
|
||||
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):
|
||||
frame = None
|
||||
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:
|
||||
await clock_edge_event
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
tready_sample = (not has_tready) or self.bus.tready.value
|
||||
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||
|
||||
if tready_sample and tvalid_sample:
|
||||
if frame is None:
|
||||
if not frame:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
@@ -639,16 +699,16 @@ class AxiStreamMonitor(AxiStreamBase):
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
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)
|
||||
if hasattr(self.bus, "tid"):
|
||||
if has_tid:
|
||||
frame.tid.append(self.bus.tid.value.integer)
|
||||
if hasattr(self.bus, "tdest"):
|
||||
if has_tdest:
|
||||
frame.tdest.append(self.bus.tdest.value.integer)
|
||||
if hasattr(self.bus, "tuser"):
|
||||
if has_tuser:
|
||||
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()
|
||||
self.log.info("RX frame: %s", frame)
|
||||
|
||||
@@ -662,6 +722,9 @@ class AxiStreamMonitor(AxiStreamBase):
|
||||
else:
|
||||
self.active = bool(frame)
|
||||
|
||||
self.wake_event.clear()
|
||||
await wake_event
|
||||
|
||||
|
||||
class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
|
||||
@@ -675,11 +738,11 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
||||
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_frames = -1
|
||||
|
||||
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
||||
|
||||
def full(self):
|
||||
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
||||
return True
|
||||
@@ -695,21 +758,39 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
if hasattr(self.bus, "tready"):
|
||||
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):
|
||||
frame = None
|
||||
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:
|
||||
pause_sample = self.pause
|
||||
|
||||
await clock_edge_event
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
tready_sample = (not has_tready) or self.bus.tready.value
|
||||
tvalid_sample = (not has_tvalid) or self.bus.tvalid.value
|
||||
|
||||
if tready_sample and tvalid_sample:
|
||||
if frame is None:
|
||||
if not frame:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
@@ -719,16 +800,16 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
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)
|
||||
if hasattr(self.bus, "tid"):
|
||||
if has_tid:
|
||||
frame.tid.append(self.bus.tid.value.integer)
|
||||
if hasattr(self.bus, "tdest"):
|
||||
if has_tdest:
|
||||
frame.tdest.append(self.bus.tdest.value.integer)
|
||||
if hasattr(self.bus, "tuser"):
|
||||
if has_tuser:
|
||||
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()
|
||||
self.log.info("RX frame: %s", frame)
|
||||
|
||||
@@ -742,5 +823,9 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
else:
|
||||
self.active = bool(frame)
|
||||
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready.value = (not self.full() and not self.pause)
|
||||
if has_tready:
|
||||
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
|
||||
|
||||
@@ -22,27 +22,24 @@ THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import mmap
|
||||
|
||||
from .sparse_memory import SparseMemory
|
||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(self, size=1024, mem=None, **kwargs):
|
||||
def __init__(self, size=2**64, mem=None, **kwargs):
|
||||
if mem is not None:
|
||||
self.mem = mem
|
||||
else:
|
||||
self.mem = mmap.mmap(-1, size)
|
||||
self.mem = SparseMemory(size)
|
||||
self.size = len(self.mem)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def read(self, address, length):
|
||||
self.mem.seek(address)
|
||||
return self.mem.read(length)
|
||||
return self.mem[address:address+length]
|
||||
|
||||
def write(self, address, data):
|
||||
self.mem.seek(address)
|
||||
self.mem.write(bytes(data))
|
||||
self.mem[address:address+len(data)] = data
|
||||
|
||||
def write_words(self, address, data, byteorder='little', ws=2):
|
||||
words = data
|
||||
|
||||
111
cocotbext/axi/sparse_memory.py
Normal file
111
cocotbext/axi/sparse_memory.py
Normal 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")
|
||||
@@ -99,6 +99,7 @@ class StreamBase(Reset):
|
||||
self.idle_event = Event()
|
||||
self.idle_event.set()
|
||||
self.active_event = Event()
|
||||
self.wake_event = Event()
|
||||
|
||||
self.ready = None
|
||||
self.valid = None
|
||||
@@ -163,10 +164,23 @@ class StreamPause:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.pause = False
|
||||
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()
|
||||
@@ -206,12 +220,14 @@ class StreamSource(StreamBase, StreamPause):
|
||||
await self.dequeue_event.wait()
|
||||
await self.queue.put(obj)
|
||||
self.idle_event.clear()
|
||||
self.active_event.set()
|
||||
|
||||
def send_nowait(self, obj):
|
||||
if self.full():
|
||||
raise QueueFull()
|
||||
self.queue.put_nowait(obj)
|
||||
self.idle_event.clear()
|
||||
self.active_event.set()
|
||||
|
||||
def full(self):
|
||||
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()
|
||||
if self.queue.empty():
|
||||
self.idle_event.set()
|
||||
self.active_event.clear()
|
||||
|
||||
await self.active_event.wait()
|
||||
|
||||
|
||||
class StreamMonitor(StreamBase):
|
||||
@@ -264,9 +283,21 @@ class StreamMonitor(StreamBase):
|
||||
_valid_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):
|
||||
if self.queue.empty():
|
||||
self.active_event.clear()
|
||||
self._dequeue(item)
|
||||
return item
|
||||
|
||||
async def recv(self):
|
||||
@@ -285,9 +316,25 @@ class StreamMonitor(StreamBase):
|
||||
else:
|
||||
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):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
wake_event = self.wake_event.wait()
|
||||
|
||||
while True:
|
||||
await clock_edge_event
|
||||
|
||||
@@ -300,6 +347,9 @@ class StreamMonitor(StreamBase):
|
||||
self.bus.sample(obj)
|
||||
self.queue.put_nowait(obj)
|
||||
self.active_event.set()
|
||||
else:
|
||||
self.wake_event.clear()
|
||||
await wake_event
|
||||
|
||||
|
||||
class StreamSink(StreamMonitor, StreamPause):
|
||||
@@ -327,10 +377,20 @@ class StreamSink(StreamMonitor, StreamPause):
|
||||
if self.ready is not None:
|
||||
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):
|
||||
clock_edge_event = RisingEdge(self.clock)
|
||||
|
||||
wake_event = self.wake_event.wait()
|
||||
|
||||
while True:
|
||||
pause_sample = self.pause
|
||||
|
||||
await clock_edge_event
|
||||
|
||||
# read handshake signals
|
||||
@@ -344,7 +404,11 @@ class StreamSink(StreamMonitor, StreamPause):
|
||||
self.active_event.set()
|
||||
|
||||
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):
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.18"
|
||||
__version__ = "0.1.24"
|
||||
|
||||
23
setup.cfg
23
setup.cfg
@@ -47,14 +47,13 @@ addopts =
|
||||
|
||||
# tox configuration
|
||||
[tox:tox]
|
||||
envlist = py36, py37, py38, py39, py310
|
||||
envlist = py37, py38, py39, py310
|
||||
skip_missing_interpreters = true
|
||||
minversion = 3.2.0
|
||||
minversion = 3.18.0
|
||||
requires = virtualenv >= 16.1
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.6: py36
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
@@ -63,19 +62,23 @@ python =
|
||||
[testenv]
|
||||
setenv =
|
||||
COVERAGE=1
|
||||
usedevelop = True
|
||||
|
||||
deps =
|
||||
pytest
|
||||
pytest-xdist
|
||||
cocotb-test
|
||||
coverage
|
||||
pytest-cov
|
||||
pytest == 7.2.1
|
||||
pytest-xdist == 3.1.0
|
||||
cocotb == 1.7.2
|
||||
cocotb-bus == 0.2.1
|
||||
cocotb-test == 0.2.4
|
||||
coverage == 7.0.5
|
||||
pytest-cov == 4.0.0
|
||||
|
||||
commands =
|
||||
pytest --cov=cocotbext --cov=tests --cov-branch -n auto
|
||||
pytest --cov=cocotbext --cov=tests --cov-branch {posargs:-n auto --verbose}
|
||||
bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append'
|
||||
coverage report
|
||||
|
||||
whitelist_externals =
|
||||
allowlist_externals =
|
||||
bash
|
||||
|
||||
# combine if paths are different
|
||||
|
||||
@@ -45,15 +45,7 @@ export PARAM_RUSER_WIDTH ?= 1
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||
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)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
@@ -62,15 +54,7 @@ ifeq ($(SIM), icarus)
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH -Wno-CASEINCOMPLETE
|
||||
|
||||
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)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
|
||||
@@ -39,9 +39,7 @@ export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||
COMPILE_ARGS += -P $(TOPLEVEL).ADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
||||
COMPILE_ARGS += -P $(TOPLEVEL).STRB_WIDTH=$(PARAM_STRB_WIDTH)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
@@ -50,9 +48,7 @@ ifeq ($(SIM), icarus)
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||
|
||||
COMPILE_ARGS += -GDATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||
COMPILE_ARGS += -GADDR_WIDTH=$(PARAM_ADDR_WIDTH)
|
||||
COMPILE_ARGS += -GSTRB_WIDTH=$(PARAM_STRB_WIDTH)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
|
||||
@@ -41,11 +41,7 @@ export PARAM_USER_WIDTH ?= 1
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
COMPILE_ARGS += -P $(TOPLEVEL).DATA_WIDTH=$(PARAM_DATA_WIDTH)
|
||||
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)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
VERILOG_SOURCES += iverilog_dump.v
|
||||
@@ -54,11 +50,7 @@ ifeq ($(SIM), icarus)
|
||||
else ifeq ($(SIM), verilator)
|
||||
COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH
|
||||
|
||||
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)
|
||||
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
|
||||
|
||||
ifeq ($(WAVES), 1)
|
||||
COMPILE_ARGS += --trace-fst
|
||||
|
||||
Reference in New Issue
Block a user