17 Commits

Author SHA1 Message Date
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
9 changed files with 243 additions and 92 deletions

View File

@@ -9,13 +9,13 @@ jobs:
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

@@ -476,7 +476,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 +563,10 @@ class AxiMasterWrite(Region, Reset):
await self.w_channel.send(w) await self.w_channel.send(w)
cur_addr += num_bytes if 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 +580,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 +593,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 +602,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
@@ -872,7 +873,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 +918,10 @@ 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 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 +929,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 +957,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

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

@@ -286,7 +286,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 +314,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
@@ -494,7 +497,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 +523,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

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

@@ -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.20"

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