21 Commits

Author SHA1 Message Date
Alex Forencich
c4873ad14c Release v0.1.10 2021-03-24 21:03:16 -07:00
Alex Forencich
77a40bdc8f Limit channel queue depth 2021-03-24 17:55:07 -07:00
Alex Forencich
f991096272 Separate processing coroutines for each ID 2021-03-24 17:07:16 -07:00
Alex Forencich
9e28bd7fbb Revert back to cocotb.fork 2021-03-24 16:20:08 -07:00
Alex Forencich
8c74f747a4 Update readme 2021-03-22 23:19:14 -07:00
Alex Forencich
a285f008ca Refactor reset handling code 2021-03-22 22:02:53 -07:00
Alex Forencich
c677ab245c Reset more internal state 2021-03-22 22:02:06 -07:00
Alex Forencich
11f9db8b06 Add test cases for init_read and init_write 2021-03-22 21:22:13 -07:00
Alex Forencich
344ec8d4ce Return event object from init_read and init_write; remove get_write_resp and get_read_data 2021-03-22 21:21:34 -07:00
Alex Forencich
4ff390481e Extract parameter values from cocotb.top 2021-03-22 15:51:07 -07:00
Alex Forencich
4bee96ea9a Enforce max queue depth on streaming sources 2021-03-21 22:24:59 -07:00
Alex Forencich
a66dfea6f7 Factor out common recv code; throw QueueEmpty exception in get_nowait 2021-03-21 21:02:28 -07:00
Alex Forencich
f1a89e6c12 Trigger transmit complete events when flushing queue to prevent deadlocks 2021-03-21 18:46:30 -07:00
Alex Forencich
11205bde46 Handle dropped transmit frames during reset 2021-03-21 18:39:35 -07:00
Alex Forencich
bce364eef5 Ensure idle event is set when queue is empty 2021-03-21 18:39:10 -07:00
Alex Forencich
e934b69776 Use start_soon instead of fork 2021-03-21 12:22:22 -07:00
Alex Forencich
7fb8c4e28b Reset processing on assert edge only to permit operations to be queued while reset is asserted 2021-03-21 12:13:19 -07:00
Alex Forencich
156fada616 Store commands currently being processed so they can be released when the processing coroutines are killed 2021-03-21 12:04:30 -07:00
Alex Forencich
d88ba7caf3 Warn when operations are dropped during reset 2021-03-21 11:41:25 -07:00
Alex Forencich
6c66776518 Use start_soon instead of fork 2021-03-21 11:40:25 -07:00
Alex Forencich
a71678c7e3 Bump to dev version 2021-03-17 18:32:42 -07:00
11 changed files with 435 additions and 299 deletions

View File

@@ -42,36 +42,27 @@ To use these modules, import the one you need and connect it to the DUT:
axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst) axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)
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, as appropriate. These objects are containers for the interface signals and include class methods to automate connections.
Once the module is instantiated, read and write operations can be initiated in a few different ways. Once the module is instantiated, read and write operations can be initiated in a couple of different ways.
First, non-blocking operations can be started with `init_read()` and `init_write()`. These methods will queue up a read or write operation to be carried out over the interface. The result of the operation can be retrieved with `get_read_data()` and `get_write_resp()`. To monitor the status of the module, `idle()`, `wait()`, `wait_read()`, and `wait_write()` can be used. For example: First, blocking operations can be carried out with `read()` and `write()` and their associated word-access wrappers. Multiple concurrent operations started from different coroutines are handled correctly. For example:
axi_master.init_write(0x0000, b'test')
await axi_master.wait()
resp = axi_master.get_write_resp()
axi_master.init_read(0x0000, 4)
await axi_master.wait()
data = axi_master.get_read_data()
Alternatively, an event object can be provided as an argument to `init_read()` and `init_write()`, and the result can be retrieved from `Event.data`. For example:
event = Event()
axi_master.init_write(0x0000, b'test', event=event)
await event.wait()
resp = event.data
event = Event()
axi_master.init_read(0x0000, 4, event=event)
await event.wait()
resp = event.data
Second, blocking operations can be carried out with `read()` and `write()` and their associated word-access wrappers. Multiple concurrent operations started from different coroutines are handled correctly. For example:
await axi_master.write(0x0000, b'test') await axi_master.write(0x0000, b'test')
data = await axi_master.read(0x0000, 4) data = await axi_master.read(0x0000, 4)
`read()`, `write()`, `get_read_data()`, and `get_write_resp()` return `namedtuple` objects containing _address_, _data_ or _length_, and _resp_. `read()` and `write()` return `namedtuple` objects containing _address_, _data_ or _length_, and _resp_. This is the preferred style, and this is the only style supported by the word-access wrappers.
Alternatively, operations can be initiated with non-blocking `init_read()` and `init_write()`. These functions return `Event` objects which are triggered when the operation completes, and the result can be retrieved from `Event.data`. For example:
write_op = axi_master.init_write(0x0000, b'test')
await write_op.wait()
resp = write_op.data
read_op = axi_master.init_read(0x0000, 4)
await read_op.wait()
resp = read_op.data
With this method, it is possible to start multiple concurrent operations from the same coroutine. It is also possible to use the events with `Combine`, `First`, and `with_timeout`.
#### `AxiMaster` and `AxiLiteMaster` constructor parameters #### `AxiMaster` and `AxiLiteMaster` constructor parameters
@@ -86,16 +77,12 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
#### Methods #### Methods
* `init_read(address, length, ...)`: initiate reading _length_ bytes, starting at _address_ * `init_read(address, length, ...)`: initiate reading _length_ bytes, starting at _address_. Returns an `Event` object.
* `init_write(address, data, ...)`: initiate writing _data_ (bytes), starting from _address_ * `init_write(address, data, ...)`: initiate writing _data_ (bytes), starting from _address_. Returns an `Event` object.
* `idle()`: returns _True_ when there are no outstanding operations in progress * `idle()`: returns _True_ when there are no outstanding operations in progress
* `wait()`: blocking wait until all outstanding operations complete * `wait()`: blocking wait until all outstanding operations complete
* `wait_read()`: wait until all outstanding read operations complete * `wait_read()`: wait until all outstanding read operations complete
* `wait_write()`: wait until all outstanding write operations complete * `wait_write()`: wait until all outstanding write operations complete
* `read_data_ready()`: determine if any read read data is available
* `get_read_data()`: fetch first available read data
* `write_resp_ready()`: determine if any write response is available
* `get_write_resp()`: fetch first available write response
* `read(address, length, ...)`: read _length_ bytes, starting at _address_ * `read(address, length, ...)`: read _length_ bytes, starting at _address_
* `read_words(address, count, byteorder='little', ws=2, ...)`: read _count_ _ws_-byte words, starting at _address_ * `read_words(address, count, byteorder='little', ws=2, ...)`: read _count_ _ws_-byte words, starting at _address_
* `read_dwords(address, count, byteorder='little', ...)`: read _count_ 4-byte dwords, starting at _address_ * `read_dwords(address, count, byteorder='little', ...)`: read _count_ 4-byte dwords, starting at _address_
@@ -125,16 +112,16 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
* _region_: AXI region field, default `0` * _region_: AXI region field, default `0`
* _user_: AXI user signal (awuser/aruser), default `0` * _user_: AXI user signal (awuser/aruser), default `0`
* _wuser_: AXI wuser signal, default `0` (write-related methods only) * _wuser_: AXI wuser signal, default `0` (write-related methods only)
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None` (`init_read()` and `init_write()` only). If provided, the event will be triggered when the operation completes and the result returned via `Event.data` instead of `get_read_data()` or `get_write_resp()`. * _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
#### Additional optional arguments for `AxiLiteMaster` #### Additional optional arguments for `AxiLiteMaster`
* _prot_: AXI protection flags, default `AxiProt.NONSECURE` * _prot_: AXI protection flags, default `AxiProt.NONSECURE`
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None` (`init_read()` and `init_write()` only). If provided, the event will be triggered when the operation completes and the result returned via `Event.data` instead of `get_read_data()` or `get_write_resp()`. * _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
#### `AxiBus` and `AxiLiteBus` objects #### `AxiBus` and `AxiLiteBus` objects
The `AxiBus`, `AxiLiteBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are extensions of `cocotb.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate. The `AxiBus`, `AxiLiteBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are currently extensions of `cocotb_bus.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate.
### AXI and AXI lite RAM ### AXI and AXI lite RAM
@@ -270,7 +257,7 @@ Note: _byte_size_, _byte_lanes_, `len(tdata)`, and `len(tkeep)` are all related,
* `read_nowait(count)`: read _count_ bytes from buffer (non-blocking) (sink/monitor) * `read_nowait(count)`: read _count_ bytes from buffer (non-blocking) (sink/monitor)
* `count()`: returns the number of items in the queue (all) * `count()`: returns the number of items in the queue (all)
* `empty()`: returns _True_ if the queue is empty (all) * `empty()`: returns _True_ if the queue is empty (all)
* `full()`: returns _True_ if the queue occupancy limits are met (sink) * `full()`: returns _True_ if the queue occupancy limits are met (source/sink)
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
* `clear()`: drop all data in queue (all) * `clear()`: drop all data in queue (all)
* `wait()`: wait for idle (source) * `wait()`: wait for idle (source)

View File

@@ -59,17 +59,21 @@ class AxiMasterWrite(Reset):
self.log.info("https://github.com/alexforencich/cocotbext-axi") self.log.info("https://github.com/alexforencich/cocotbext-axi")
self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level) self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level)
self.aw_channel.queue_occupancy_limit = 2
self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level) self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level)
self.w_channel.queue_occupancy_limit = 2
self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level) self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level)
self.b_channel.queue_occupancy_limit = 2
self.write_command_queue = Queue() self.write_command_queue = Queue()
self.write_resp_queue = Queue() self.current_write_command = None
self.id_count = 2**len(self.aw_channel.bus.awid) self.id_count = 2**len(self.aw_channel.bus.awid)
self.cur_id = 0 self.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
self.int_write_resp_command_queue = Queue() self.int_write_resp_command_queue = [Queue() for k in range(self.id_count)]
self.current_write_resp_command = [None for k in range(self.id_count)]
self.int_write_resp_queue_list = [Queue() for k in range(self.id_count)] self.int_write_resp_queue_list = [Queue() for k in range(self.id_count)]
self.in_flight_operations = 0 self.in_flight_operations = 0
@@ -100,13 +104,17 @@ class AxiMasterWrite(Reset):
self._process_write_cr = None self._process_write_cr = None
self._process_write_resp_cr = None self._process_write_resp_cr = None
self._process_write_resp_id_cr = None
self._init_reset(reset, reset_active_level) self._init_reset(reset, reset_active_level)
def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL,
cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None): cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None):
if event is not None and not isinstance(event, Event): if event is None:
event = Event()
if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
if awid is None or awid < 0: if awid is None or awid < 0:
@@ -138,6 +146,8 @@ class AxiMasterWrite(Reset):
cache, prot, qos, region, user, wuser, event) cache, prot, qos, region, user, wuser, event)
self.write_command_queue.put_nowait(cmd) self.write_command_queue.put_nowait(cmd)
return event
def idle(self): def idle(self):
return not self.in_flight_operations return not self.in_flight_operations
@@ -145,18 +155,9 @@ class AxiMasterWrite(Reset):
while not self.idle(): while not self.idle():
await self._idle.wait() await self._idle.wait()
def write_resp_ready(self):
return not self.write_resp_queue.empty()
def get_write_resp(self):
if not self.write_resp_queue.empty():
return self.write_resp_queue.get_nowait()
return None
async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None,
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0):
event = Event() event = self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser)
self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)
await event.wait() await event.wait()
return event.data return event.data
@@ -206,36 +207,62 @@ class AxiMasterWrite(Reset):
if self._process_write_resp_cr is not None: if self._process_write_resp_cr is not None:
self._process_write_resp_cr.kill() self._process_write_resp_cr.kill()
self._process_write_resp_cr = None self._process_write_resp_cr = None
if self._process_write_resp_id_cr is not None:
for cr in self._process_write_resp_id_cr:
cr.kill()
self._process_write_resp_id_cr = None
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
def flush_cmd(cmd):
self.log.warning("Flushed write operation during reset: %s", cmd)
if cmd.event:
cmd.event.set(None)
while not self.write_command_queue.empty():
cmd = self.write_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_write_command:
cmd = self.current_write_command
self.current_write_command = None
flush_cmd(cmd)
for q in self.int_write_resp_command_queue:
while not q.empty():
cmd = q.get_nowait()
flush_cmd(cmd)
for k in range(len(self.current_write_resp_command)):
if self.current_write_resp_command[k]:
cmd = self.current_write_resp_command[k]
self.current_write_resp_command[k] = None
flush_cmd(cmd)
for q in self.int_write_resp_queue_list:
while not q.empty():
q.get_nowait()
self.cur_id = 0
self.active_id = Counter()
self.in_flight_operations = 0
self._idle.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_write_cr is None: if self._process_write_cr is None:
self._process_write_cr = cocotb.fork(self._process_write()) self._process_write_cr = cocotb.fork(self._process_write())
if self._process_write_resp_cr is None: if self._process_write_resp_cr is None:
self._process_write_resp_cr = cocotb.fork(self._process_write_resp()) self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
if self._process_write_resp_id_cr is None:
self.aw_channel.clear() self._process_write_resp_id_cr = [cocotb.fork(self._process_write_resp_id(i)) for i in range(self.id_count)]
self.w_channel.clear()
self.b_channel.clear()
while not self.write_command_queue.empty():
cmd = self.write_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.int_write_resp_command_queue.empty():
cmd = self.int_write_resp_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.write_resp_queue.empty():
self.write_resp_queue.get_nowait()
self.in_flight_operations = 0
self._idle.set()
async def _process_write(self): async def _process_write(self):
while True: while True:
cmd = await self.write_command_queue.get() cmd = await self.write_command_queue.get()
self.current_write_command = cmd
num_bytes = 2**cmd.size num_bytes = 2**cmd.size
@@ -292,7 +319,7 @@ class AxiMasterWrite(Reset):
# split on 4k address boundary # split on 4k address boundary
burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes
burst_list.append((awid, burst_length)) burst_list.append(burst_length)
aw = self.aw_channel._transaction_obj() aw = self.aw_channel._transaction_obj()
aw.awid = awid aw.awid = awid
@@ -334,27 +361,31 @@ class AxiMasterWrite(Reset):
cycle_offset = (cycle_offset + num_bytes) % self.byte_width cycle_offset = (cycle_offset + num_bytes) % self.byte_width
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)
await self.int_write_resp_command_queue.put(resp_cmd) await self.int_write_resp_command_queue[awid].put(resp_cmd)
self.current_write_command = None
async def _process_write_resp(self): async def _process_write_resp(self):
while True: while True:
cmd = await self.int_write_resp_command_queue.get() b = await self.b_channel.recv()
bid = int(b.bid)
if self.active_id[bid] <= 0:
raise Exception(f"Unexpected burst ID {bid}")
await self.int_write_resp_queue_list[bid].put(b)
async def _process_write_resp_id(self, bid):
while True:
cmd = await self.int_write_resp_command_queue[bid].get()
self.current_write_resp_command[bid] = cmd
resp = AxiResp.OKAY resp = AxiResp.OKAY
user = [] user = []
for bid, burst_length in cmd.burst_list: for burst_length in cmd.burst_list:
while self.int_write_resp_queue_list[bid].empty(): b = await self.int_write_resp_queue_list[bid].get()
b = await self.b_channel.recv()
i = int(b.bid)
if self.active_id[i] <= 0:
raise Exception(f"Unexpected burst ID {bid}")
self.int_write_resp_queue_list[i].put_nowait(b)
b = self.int_write_resp_queue_list[bid].get_nowait()
burst_id = int(b.bid) burst_id = int(b.bid)
burst_resp = AxiResp(b.bresp) burst_resp = AxiResp(b.bresp)
@@ -378,10 +409,9 @@ class AxiMasterWrite(Reset):
write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user) write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user)
if cmd.event is not None: cmd.event.set(write_resp)
cmd.event.set(write_resp)
else: self.current_write_resp_command[bid] = None
self.write_resp_queue.put_nowait(write_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
@@ -399,16 +429,19 @@ class AxiMasterRead(Reset):
self.log.info("https://github.com/alexforencich/cocotbext-axi") self.log.info("https://github.com/alexforencich/cocotbext-axi")
self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level) self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level)
self.ar_channel.queue_occupancy_limit = 2
self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level) self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level)
self.r_channel.queue_occupancy_limit = 2
self.read_command_queue = Queue() self.read_command_queue = Queue()
self.read_data_queue = Queue() self.current_read_command = None
self.id_count = 2**len(self.ar_channel.bus.arid) self.id_count = 2**len(self.ar_channel.bus.arid)
self.cur_id = 0 self.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
self.int_read_resp_command_queue = Queue() self.int_read_resp_command_queue = [Queue() for k in range(self.id_count)]
self.current_read_resp_command = [None for k in range(self.id_count)]
self.int_read_resp_queue_list = [Queue() for k in range(self.id_count)] self.int_read_resp_queue_list = [Queue() for k in range(self.id_count)]
self.in_flight_operations = 0 self.in_flight_operations = 0
@@ -437,13 +470,17 @@ class AxiMasterRead(Reset):
self._process_read_cr = None self._process_read_cr = None
self._process_read_resp_cr = None self._process_read_resp_cr = None
self._process_read_resp_id_cr = None
self._init_reset(reset, reset_active_level) self._init_reset(reset, reset_active_level)
def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None): lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
if event is not None and not isinstance(event, Event): if event is None:
event = Event()
if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
if length < 0: if length < 0:
@@ -470,6 +507,8 @@ class AxiMasterRead(Reset):
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event) cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
self.read_command_queue.put_nowait(cmd) self.read_command_queue.put_nowait(cmd)
return event
def idle(self): def idle(self):
return not self.in_flight_operations return not self.in_flight_operations
@@ -477,18 +516,9 @@ class AxiMasterRead(Reset):
while not self.idle(): while not self.idle():
await self._idle.wait() await self._idle.wait()
def read_data_ready(self):
return not self.read_data_queue.empty()
def get_read_data(self):
if not self.read_data_queue.empty():
return self.read_data_queue.get_nowait()
return None
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0):
event = Event() event = self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user)
self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
await event.wait() await event.wait()
return event.data return event.data
@@ -538,35 +568,61 @@ class AxiMasterRead(Reset):
if self._process_read_resp_cr is not None: if self._process_read_resp_cr is not None:
self._process_read_resp_cr.kill() self._process_read_resp_cr.kill()
self._process_read_resp_cr = None self._process_read_resp_cr = None
if self._process_read_resp_id_cr is not None:
for cr in self._process_read_resp_id_cr:
cr.kill()
self._process_read_resp_id_cr = None
self.ar_channel.clear()
self.r_channel.clear()
def flush_cmd(cmd):
self.log.warning("Flushed read operation during reset: %s", cmd)
if cmd.event:
cmd.event.set(None)
while not self.read_command_queue.empty():
cmd = self.read_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_read_command:
cmd = self.current_read_command
self.current_read_command = None
flush_cmd(cmd)
for q in self.int_read_resp_command_queue:
while not q.empty():
cmd = q.get_nowait()
flush_cmd(cmd)
for k in range(len(self.current_read_resp_command)):
if self.current_read_resp_command[k]:
cmd = self.current_read_resp_command[k]
self.current_read_resp_command[k] = None
flush_cmd(cmd)
for q in self.int_read_resp_queue_list:
while not q.empty():
q.get_nowait()
self.cur_id = 0
self.active_id = Counter()
self.in_flight_operations = 0
self._idle.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_read_cr is None: if self._process_read_cr is None:
self._process_read_cr = cocotb.fork(self._process_read()) self._process_read_cr = cocotb.fork(self._process_read())
if self._process_read_resp_cr is None: if self._process_read_resp_cr is None:
self._process_read_resp_cr = cocotb.fork(self._process_read_resp()) self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
if self._process_read_resp_id_cr is None:
self.ar_channel.clear() self._process_read_resp_id_cr = [cocotb.fork(self._process_read_resp_id(i)) for i in range(self.id_count)]
self.r_channel.clear()
while not self.read_command_queue.empty():
cmd = self.read_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.int_read_resp_command_queue.empty():
cmd = self.int_read_resp_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.read_data_queue.empty():
self.read_data_queue.get_nowait()
self.in_flight_operations = 0
self._idle.set()
async def _process_read(self): async def _process_read(self):
while True: while True:
cmd = await self.read_command_queue.get() cmd = await self.read_command_queue.get()
self.current_read_command = cmd
num_bytes = 2**cmd.size num_bytes = 2**cmd.size
@@ -600,7 +656,7 @@ class AxiMasterRead(Reset):
# split on 4k address boundary # split on 4k address boundary
burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes
burst_list.append((arid, burst_length)) burst_list.append(burst_length)
ar = self.r_channel._transaction_obj() ar = self.r_channel._transaction_obj()
ar.arid = arid ar.arid = arid
@@ -624,11 +680,25 @@ class AxiMasterRead(Reset):
cur_addr += num_bytes 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)
await self.int_read_resp_command_queue.put(resp_cmd) await self.int_read_resp_command_queue[arid].put(resp_cmd)
self.current_read_command = None
async def _process_read_resp(self): async def _process_read_resp(self):
while True: while True:
cmd = await self.int_read_resp_command_queue.get() r = await self.r_channel.recv()
rid = int(r.rid)
if self.active_id[rid] <= 0:
raise Exception(f"Unexpected burst ID {rid}")
await self.int_read_resp_queue_list[rid].put(r)
async def _process_read_resp_id(self, rid):
while True:
cmd = await self.int_read_resp_command_queue[rid].get()
self.current_read_resp_command[rid] = cmd
num_bytes = 2**cmd.size num_bytes = 2**cmd.size
@@ -645,19 +715,9 @@ class AxiMasterRead(Reset):
first = True first = True
for rid, burst_length in cmd.burst_list: for burst_length in cmd.burst_list:
for k in range(burst_length): for k in range(burst_length):
while self.int_read_resp_queue_list[rid].empty(): r = await self.int_read_resp_queue_list[rid].get()
r = await self.r_channel.recv()
i = int(r.rid)
if self.active_id[i] <= 0:
raise Exception(f"Unexpected burst ID {rid}")
self.int_read_resp_queue_list[i].put_nowait(r)
r = self.int_read_resp_queue_list[rid].get_nowait()
cycle_id = int(r.rid) cycle_id = int(r.rid)
cycle_data = int(r.rdata) cycle_data = int(r.rdata)
@@ -700,10 +760,9 @@ class AxiMasterRead(Reset):
read_resp = AxiReadResp(cmd.address, data, resp, user) read_resp = AxiReadResp(cmd.address, data, resp, user)
if cmd.event is not None: cmd.event.set(read_resp)
cmd.event.set(read_resp)
else: self.current_read_resp_command[rid] = None
self.read_data_queue.put_nowait(read_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
@@ -721,11 +780,11 @@ class AxiMaster:
def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None): lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
self.read_if.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event) return self.read_if.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL,
cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None): cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None):
self.write_if.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event) return self.write_if.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)
def idle(self): def idle(self):
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle()) return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())
@@ -741,18 +800,6 @@ class AxiMaster:
async def wait_write(self): async def wait_write(self):
await self.write_if.wait() await self.write_if.wait()
def read_data_ready(self):
return self.read_if.read_data_ready()
def get_read_data(self):
return self.read_if.get_read_data()
def write_resp_ready(self):
return self.write_if.write_resp_ready()
def get_write_resp(self):
return self.write_if.get_write_resp()
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0):
return await self.read_if.read(address, length, arid, return await self.read_if.read(address, length, arid,

View File

@@ -45,8 +45,11 @@ class AxiRamWrite(Memory, Reset):
super().__init__(size, mem, *args, **kwargs) super().__init__(size, mem, *args, **kwargs)
self.aw_channel = AxiAWSink(bus.aw, clock, reset, reset_active_level) self.aw_channel = AxiAWSink(bus.aw, clock, reset, reset_active_level)
self.aw_channel.queue_occupancy_limit = 2
self.w_channel = AxiWSink(bus.w, clock, reset, reset_active_level) self.w_channel = AxiWSink(bus.w, clock, reset, reset_active_level)
self.w_channel.queue_occupancy_limit = 2
self.b_channel = AxiBSource(bus.b, clock, reset, reset_active_level) self.b_channel = AxiBSource(bus.b, clock, reset, reset_active_level)
self.b_channel.queue_occupancy_limit = 2
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
@@ -75,15 +78,15 @@ class AxiRamWrite(Memory, Reset):
if self._process_write_cr is not None: if self._process_write_cr is not None:
self._process_write_cr.kill() self._process_write_cr.kill()
self._process_write_cr = None self._process_write_cr = None
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_write_cr is None: if self._process_write_cr is None:
self._process_write_cr = cocotb.fork(self._process_write()) self._process_write_cr = cocotb.fork(self._process_write())
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
async def _process_write(self): async def _process_write(self):
while True: while True:
aw = await self.aw_channel.recv() aw = await self.aw_channel.recv()
@@ -168,7 +171,9 @@ class AxiRamRead(Memory, Reset):
super().__init__(size, mem, *args, **kwargs) super().__init__(size, mem, *args, **kwargs)
self.ar_channel = AxiARSink(bus.ar, clock, reset, reset_active_level) self.ar_channel = AxiARSink(bus.ar, clock, reset, reset_active_level)
self.ar_channel.queue_occupancy_limit = 2
self.r_channel = AxiRSource(bus.r, clock, reset, reset_active_level) self.r_channel = AxiRSource(bus.r, clock, reset, reset_active_level)
self.r_channel.queue_occupancy_limit = 2
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
@@ -195,14 +200,14 @@ class AxiRamRead(Memory, Reset):
if self._process_read_cr is not None: if self._process_read_cr is not None:
self._process_read_cr.kill() self._process_read_cr.kill()
self._process_read_cr = None self._process_read_cr = None
self.ar_channel.clear()
self.r_channel.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_read_cr is None: if self._process_read_cr is None:
self._process_read_cr = cocotb.fork(self._process_read()) self._process_read_cr = cocotb.fork(self._process_read())
self.ar_channel.clear()
self.r_channel.clear()
async def _process_read(self): async def _process_read(self):
while True: while True:
ar = await self.ar_channel.recv() ar = await self.ar_channel.recv()

View File

@@ -55,13 +55,17 @@ class AxiLiteMasterWrite(Reset):
self.log.info("https://github.com/alexforencich/cocotbext-axi") self.log.info("https://github.com/alexforencich/cocotbext-axi")
self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level) self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level)
self.aw_channel.queue_occupancy_limit = 2
self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level) self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level)
self.w_channel.queue_occupancy_limit = 2
self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level) self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level)
self.b_channel.queue_occupancy_limit = 2
self.write_command_queue = Queue() self.write_command_queue = Queue()
self.write_resp_queue = Queue() self.current_write_command = None
self.int_write_resp_command_queue = Queue() self.int_write_resp_command_queue = Queue()
self.current_write_resp_command = None
self.in_flight_operations = 0 self.in_flight_operations = 0
self._idle = Event() self._idle = Event()
@@ -86,7 +90,10 @@ class AxiLiteMasterWrite(Reset):
self._init_reset(reset, reset_active_level) self._init_reset(reset, reset_active_level)
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None): def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
if event is not None and not isinstance(event, Event): if event is None:
event = Event()
if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
self.in_flight_operations += 1 self.in_flight_operations += 1
@@ -94,6 +101,8 @@ class AxiLiteMasterWrite(Reset):
self.write_command_queue.put_nowait(AxiLiteWriteCmd(address, bytearray(data), prot, event)) self.write_command_queue.put_nowait(AxiLiteWriteCmd(address, bytearray(data), prot, event))
return event
def idle(self): def idle(self):
return not self.in_flight_operations return not self.in_flight_operations
@@ -101,17 +110,8 @@ class AxiLiteMasterWrite(Reset):
while not self.idle(): while not self.idle():
await self._idle.wait() await self._idle.wait()
def write_resp_ready(self):
return not self.write_resp_queue.empty()
def get_write_resp(self):
if not self.write_resp_queue.empty():
return self.write_resp_queue.get_nowait()
return None
async def write(self, address, data, prot=AxiProt.NONSECURE): async def write(self, address, data, prot=AxiProt.NONSECURE):
event = Event() event = self.init_write(address, data, prot)
self.init_write(address, data, prot, event)
await event.wait() await event.wait()
return event.data return event.data
@@ -149,6 +149,36 @@ class AxiLiteMasterWrite(Reset):
if self._process_write_resp_cr is not None: if self._process_write_resp_cr is not None:
self._process_write_resp_cr.kill() self._process_write_resp_cr.kill()
self._process_write_resp_cr = None self._process_write_resp_cr = None
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
def flush_cmd(cmd):
self.log.warning("Flushed write operation during reset: %s", cmd)
if cmd.event:
cmd.event.set(None)
while not self.write_command_queue.empty():
cmd = self.write_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_write_command:
cmd = self.current_write_command
self.current_write_command = None
flush_cmd(cmd)
while not self.int_write_resp_command_queue.empty():
cmd = self.int_write_resp_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_write_resp_command:
cmd = self.current_write_resp_command
self.current_write_resp_command = None
flush_cmd(cmd)
self.in_flight_operations = 0
self._idle.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_write_cr is None: if self._process_write_cr is None:
@@ -156,29 +186,10 @@ class AxiLiteMasterWrite(Reset):
if self._process_write_resp_cr is None: if self._process_write_resp_cr is None:
self._process_write_resp_cr = cocotb.fork(self._process_write_resp()) self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
while not self.write_command_queue.empty():
cmd = self.write_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.int_write_resp_command_queue.empty():
cmd = self.int_write_resp_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.write_resp_queue.empty():
self.write_resp_queue.get_nowait()
self.in_flight_operations = 0
self._idle.set()
async def _process_write(self): async def _process_write(self):
while True: while True:
cmd = await self.write_command_queue.get() cmd = await self.write_command_queue.get()
self.current_write_command = cmd
word_addr = (cmd.address // self.byte_width) * self.byte_width word_addr = (cmd.address // self.byte_width) * self.byte_width
@@ -226,9 +237,12 @@ class AxiLiteMasterWrite(Reset):
await self.aw_channel.send(aw) await self.aw_channel.send(aw)
await self.w_channel.send(w) await self.w_channel.send(w)
self.current_write_command = None
async def _process_write_resp(self): async def _process_write_resp(self):
while True: while True:
cmd = await self.int_write_resp_command_queue.get() cmd = await self.int_write_resp_command_queue.get()
self.current_write_resp_command = cmd
resp = AxiResp.OKAY resp = AxiResp.OKAY
@@ -245,10 +259,9 @@ class AxiLiteMasterWrite(Reset):
write_resp = AxiLiteWriteResp(cmd.address, cmd.length, resp) write_resp = AxiLiteWriteResp(cmd.address, cmd.length, resp)
if cmd.event is not None: cmd.event.set(write_resp)
cmd.event.set(write_resp)
else: self.current_write_resp_command = None
self.write_resp_queue.put_nowait(write_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
@@ -266,12 +279,15 @@ class AxiLiteMasterRead(Reset):
self.log.info("https://github.com/alexforencich/cocotbext-axi") self.log.info("https://github.com/alexforencich/cocotbext-axi")
self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level) self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level)
self.ar_channel.queue_occupancy_limit = 2
self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level) self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level)
self.r_channel.queue_occupancy_limit = 2
self.read_command_queue = Queue() self.read_command_queue = Queue()
self.read_data_queue = Queue() self.current_read_command = None
self.int_read_resp_command_queue = Queue() self.int_read_resp_command_queue = Queue()
self.current_read_resp_command = None
self.in_flight_operations = 0 self.in_flight_operations = 0
self._idle = Event() self._idle = Event()
@@ -294,7 +310,10 @@ class AxiLiteMasterRead(Reset):
self._init_reset(reset, reset_active_level) self._init_reset(reset, reset_active_level)
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None): def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
if event is not None and not isinstance(event, Event): if event is None:
event = Event()
if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
self.in_flight_operations += 1 self.in_flight_operations += 1
@@ -302,6 +321,8 @@ class AxiLiteMasterRead(Reset):
self.read_command_queue.put_nowait(AxiLiteReadCmd(address, length, prot, event)) self.read_command_queue.put_nowait(AxiLiteReadCmd(address, length, prot, event))
return event
def idle(self): def idle(self):
return not self.in_flight_operations return not self.in_flight_operations
@@ -309,17 +330,8 @@ class AxiLiteMasterRead(Reset):
while not self.idle(): while not self.idle():
await self._idle.wait() await self._idle.wait()
def read_data_ready(self):
return not self.read_data_queue.empty()
def get_read_data(self):
if not self.read_data_queue.empty():
return self.read_data_queue.get_nowait()
return None
async def read(self, address, length, prot=AxiProt.NONSECURE): async def read(self, address, length, prot=AxiProt.NONSECURE):
event = Event() event = self.init_read(address, length, prot)
self.init_read(address, length, prot, event)
await event.wait() await event.wait()
return event.data return event.data
@@ -357,6 +369,35 @@ class AxiLiteMasterRead(Reset):
if self._process_read_resp_cr is not None: if self._process_read_resp_cr is not None:
self._process_read_resp_cr.kill() self._process_read_resp_cr.kill()
self._process_read_resp_cr = None self._process_read_resp_cr = None
self.ar_channel.clear()
self.r_channel.clear()
def flush_cmd(cmd):
self.log.warning("Flushed read operation during reset: %s", cmd)
if cmd.event:
cmd.event.set(None)
while not self.read_command_queue.empty():
cmd = self.read_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_read_command:
cmd = self.current_read_command
self.current_read_command = None
flush_cmd(cmd)
while not self.int_read_resp_command_queue.empty():
cmd = self.int_read_resp_command_queue.get_nowait()
flush_cmd(cmd)
if self.current_read_resp_command:
cmd = self.current_read_resp_command
self.current_read_resp_command = None
flush_cmd(cmd)
self.in_flight_operations = 0
self._idle.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_read_cr is None: if self._process_read_cr is None:
@@ -364,28 +405,10 @@ class AxiLiteMasterRead(Reset):
if self._process_read_resp_cr is None: if self._process_read_resp_cr is None:
self._process_read_resp_cr = cocotb.fork(self._process_read_resp()) self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
self.ar_channel.clear()
self.r_channel.clear()
while not self.read_command_queue.empty():
cmd = self.read_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.int_read_resp_command_queue.empty():
cmd = self.int_read_resp_command_queue.get_nowait()
if cmd.event:
cmd.event.set(None)
while not self.read_data_queue.empty():
self.read_data_queue.get_nowait()
self.in_flight_operations = 0
self._idle.set()
async def _process_read(self): async def _process_read(self):
while True: while True:
cmd = await self.read_command_queue.get() cmd = await self.read_command_queue.get()
self.current_read_command = cmd
word_addr = (cmd.address // self.byte_width) * self.byte_width word_addr = (cmd.address // self.byte_width) * self.byte_width
@@ -404,9 +427,12 @@ class AxiLiteMasterRead(Reset):
await self.ar_channel.send(ar) await self.ar_channel.send(ar)
self.current_read_command = None
async def _process_read_resp(self): async def _process_read_resp(self):
while True: while True:
cmd = await self.int_read_resp_command_queue.get() cmd = await self.int_read_resp_command_queue.get()
self.current_read_resp_command = cmd
start_offset = cmd.address % self.byte_width start_offset = cmd.address % self.byte_width
end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1 end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1
@@ -440,10 +466,9 @@ class AxiLiteMasterRead(Reset):
read_resp = AxiLiteReadResp(cmd.address, data, resp) read_resp = AxiLiteReadResp(cmd.address, data, resp)
if cmd.event is not None: cmd.event.set(read_resp)
cmd.event.set(read_resp)
else: self.current_read_resp_command = None
self.read_data_queue.put_nowait(read_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
@@ -460,10 +485,10 @@ class AxiLiteMaster:
self.read_if = AxiLiteMasterRead(bus.read, clock, reset, reset_active_level) self.read_if = AxiLiteMasterRead(bus.read, clock, reset, reset_active_level)
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None): def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
self.read_if.init_read(address, length, prot, event) return self.read_if.init_read(address, length, prot, event)
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None): def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
self.write_if.init_write(address, data, prot, event) return self.write_if.init_write(address, data, prot, event)
def idle(self): def idle(self):
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle()) return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())
@@ -479,18 +504,6 @@ class AxiLiteMaster:
async def wait_write(self): async def wait_write(self):
await self.write_if.wait() await self.write_if.wait()
def read_data_ready(self):
return self.read_if.read_data_ready()
def get_read_data(self):
return self.read_if.get_read_data()
def write_resp_ready(self):
return self.write_if.write_resp_ready()
def get_write_resp(self):
return self.write_if.get_write_resp()
async def read(self, address, length, prot=AxiProt.NONSECURE): async def read(self, address, length, prot=AxiProt.NONSECURE):
return await self.read_if.read(address, length, prot) return await self.read_if.read(address, length, prot)

View File

@@ -45,8 +45,11 @@ class AxiLiteRamWrite(Memory, Reset):
super().__init__(size, mem, *args, **kwargs) super().__init__(size, mem, *args, **kwargs)
self.aw_channel = AxiLiteAWSink(bus.aw, clock, reset, reset_active_level) self.aw_channel = AxiLiteAWSink(bus.aw, clock, reset, reset_active_level)
self.aw_channel.queue_occupancy_limit = 2
self.w_channel = AxiLiteWSink(bus.w, clock, reset, reset_active_level) self.w_channel = AxiLiteWSink(bus.w, clock, reset, reset_active_level)
self.w_channel.queue_occupancy_limit = 2
self.b_channel = AxiLiteBSource(bus.b, clock, reset, reset_active_level) self.b_channel = AxiLiteBSource(bus.b, clock, reset, reset_active_level)
self.b_channel.queue_occupancy_limit = 2
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
@@ -72,15 +75,15 @@ class AxiLiteRamWrite(Memory, Reset):
if self._process_write_cr is not None: if self._process_write_cr is not None:
self._process_write_cr.kill() self._process_write_cr.kill()
self._process_write_cr = None self._process_write_cr = None
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_write_cr is None: if self._process_write_cr is None:
self._process_write_cr = cocotb.fork(self._process_write()) self._process_write_cr = cocotb.fork(self._process_write())
self.aw_channel.clear()
self.w_channel.clear()
self.b_channel.clear()
async def _process_write(self): async def _process_write(self):
while True: while True:
aw = await self.aw_channel.recv() aw = await self.aw_channel.recv()
@@ -126,7 +129,9 @@ class AxiLiteRamRead(Memory, Reset):
super().__init__(size, mem, *args, **kwargs) super().__init__(size, mem, *args, **kwargs)
self.ar_channel = AxiLiteARSink(bus.ar, clock, reset, reset_active_level) self.ar_channel = AxiLiteARSink(bus.ar, clock, reset, reset_active_level)
self.ar_channel.queue_occupancy_limit = 2
self.r_channel = AxiLiteRSource(bus.r, clock, reset, reset_active_level) self.r_channel = AxiLiteRSource(bus.r, clock, reset, reset_active_level)
self.r_channel.queue_occupancy_limit = 2
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
@@ -150,14 +155,14 @@ class AxiLiteRamRead(Memory, Reset):
if self._process_read_cr is not None: if self._process_read_cr is not None:
self._process_read_cr.kill() self._process_read_cr.kill()
self._process_read_cr = None self._process_read_cr = None
self.ar_channel.clear()
self.r_channel.clear()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._process_read_cr is None: if self._process_read_cr is None:
self._process_read_cr = cocotb.fork(self._process_read()) self._process_read_cr = cocotb.fork(self._process_read())
self.ar_channel.clear()
self.r_channel.clear()
async def _process_read(self): async def _process_read(self):
while True: while True:
ar = await self.ar_channel.recv() ar = await self.ar_channel.recv()

View File

@@ -25,7 +25,7 @@ THE SOFTWARE.
import logging import logging
import cocotb import cocotb
from cocotb.queue import Queue from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Timer, First, Event from cocotb.triggers import RisingEdge, Timer, First, Event
from cocotb.utils import get_sim_time from cocotb.utils import get_sim_time
from cocotb_bus.bus import Bus from cocotb_bus.bus import Bus
@@ -277,6 +277,8 @@ class AxiStreamBase(Reset):
self.active = False self.active = False
self.queue = Queue() self.queue = Queue()
self.dequeue_event = Event()
self.current_frame = None
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event() self.active_event = Event()
@@ -353,7 +355,10 @@ class AxiStreamBase(Reset):
def clear(self): def clear(self):
while not self.queue.empty(): while not self.queue.empty():
self.queue.get_nowait() frame = self.queue.get_nowait()
frame.sim_time_end = None
frame.handle_tx_complete()
self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear() self.active_event.clear()
self.queue_occupancy_bytes = 0 self.queue_occupancy_bytes = 0
@@ -365,13 +370,16 @@ class AxiStreamBase(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
if self.queue.empty():
self.idle_event.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
raise NotImplementedError() raise NotImplementedError()
@@ -412,7 +420,18 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
_valid_init = 0 _valid_init = 0
_ready_init = None _ready_init = None
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
async def send(self, frame): async def send(self, frame):
while self.full():
self.dequeue_event.clear()
await self.dequeue_event.wait()
frame = AxiStreamFrame(frame) frame = AxiStreamFrame(frame)
await self.queue.put(frame) await self.queue.put(frame)
self.idle_event.clear() self.idle_event.clear()
@@ -420,6 +439,8 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
self.queue_occupancy_frames += 1 self.queue_occupancy_frames += 1
def send_nowait(self, frame): def send_nowait(self, frame):
if self.full():
raise QueueFull()
frame = AxiStreamFrame(frame) frame = AxiStreamFrame(frame)
self.queue.put_nowait(frame) self.queue.put_nowait(frame)
self.idle_event.clear() self.idle_event.clear()
@@ -432,6 +453,14 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
def write_nowait(self, data): def write_nowait(self, data):
self.send_nowait(data) self.send_nowait(data)
def full(self):
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
return True
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
@@ -441,19 +470,25 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
def _handle_reset(self, state): def _handle_reset(self, state):
super()._handle_reset(state) super()._handle_reset(state)
self.bus.tdata <= 0 if state:
if hasattr(self.bus, "tvalid"): self.bus.tdata <= 0
self.bus.tvalid <= 0 if hasattr(self.bus, "tvalid"):
if hasattr(self.bus, "tlast"): self.bus.tvalid <= 0
self.bus.tlast <= 0 if hasattr(self.bus, "tlast"):
if hasattr(self.bus, "tkeep"): self.bus.tlast <= 0
self.bus.tkeep <= 0 if hasattr(self.bus, "tkeep"):
if hasattr(self.bus, "tid"): self.bus.tkeep <= 0
self.bus.tid <= 0 if hasattr(self.bus, "tid"):
if hasattr(self.bus, "tdest"): self.bus.tid <= 0
self.bus.tdest <= 0 if hasattr(self.bus, "tdest"):
if hasattr(self.bus, "tuser"): self.bus.tdest <= 0
self.bus.tuser <= 0 if hasattr(self.bus, "tuser"):
self.bus.tuser <= 0
if self.current_frame:
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
self.current_frame.handle_tx_complete()
self.current_frame = None
async def _run(self): async def _run(self):
frame = None frame = None
@@ -469,8 +504,10 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
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 frame is None and not self.queue.empty():
frame = self.queue.get_nowait() frame = self.queue.get_nowait()
self.dequeue_event.set()
self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1 self.queue_occupancy_frames -= 1
self.current_frame = frame
frame.sim_time_start = get_sim_time() frame.sim_time_start = get_sim_time()
frame.sim_time_end = None frame.sim_time_end = None
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
@@ -497,6 +534,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()
frame = None frame = None
self.current_frame = None
break break
self.bus.tdata <= tdata_val self.bus.tdata <= tdata_val
@@ -538,8 +576,7 @@ class AxiStreamMonitor(AxiStreamBase):
self.read_queue = [] self.read_queue = []
async def recv(self, compact=True): def _recv(self, frame, compact=True):
frame = await self.queue.get()
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)
@@ -548,17 +585,13 @@ class AxiStreamMonitor(AxiStreamBase):
frame.compact() frame.compact()
return frame return frame
async def recv(self, compact=True):
frame = await self.queue.get()
return self._recv(frame, compact)
def recv_nowait(self, compact=True): def recv_nowait(self, compact=True):
if not self.queue.empty(): frame = self.queue.get_nowait()
frame = self.queue.get_nowait() return self._recv(frame, compact)
if self.queue.empty():
self.active_event.clear()
self.queue_occupancy_bytes -= len(frame)
self.queue_occupancy_frames -= 1
if compact:
frame.compact()
return frame
return None
async def read(self, count=-1): async def read(self, count=-1):
while not self.read_queue: while not self.read_queue:
@@ -659,8 +692,9 @@ class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
def _handle_reset(self, state): def _handle_reset(self, state):
super()._handle_reset(state) super()._handle_reset(state)
if hasattr(self.bus, "tready"): if state:
self.bus.tready <= 0 if hasattr(self.bus, "tready"):
self.bus.tready <= 0
async def _run(self): async def _run(self):
frame = None frame = None

View File

@@ -25,7 +25,7 @@ THE SOFTWARE.
import logging import logging
import cocotb import cocotb
from cocotb.queue import Queue from cocotb.queue import Queue, QueueFull
from cocotb.triggers import RisingEdge, Event, First, Timer from cocotb.triggers import RisingEdge, Event, First, Timer
from cocotb_bus.bus import Bus from cocotb_bus.bus import Bus
@@ -95,6 +95,7 @@ class StreamBase(Reset):
self.active = False self.active = False
self.queue = Queue() self.queue = Queue()
self.dequeue_event = Event()
self.idle_event = Event() self.idle_event = Event()
self.idle_event.set() self.idle_event.set()
self.active_event = Event() self.active_event = Event()
@@ -134,6 +135,7 @@ class StreamBase(Reset):
def clear(self): def clear(self):
while not self.queue.empty(): while not self.queue.empty():
self.queue.get_nowait() self.queue.get_nowait()
self.dequeue_event.set()
self.idle_event.set() self.idle_event.set()
self.active_event.clear() self.active_event.clear()
@@ -143,13 +145,16 @@ class StreamBase(Reset):
if self._run_cr is not None: if self._run_cr is not None:
self._run_cr.kill() self._run_cr.kill()
self._run_cr = None self._run_cr = None
self.active = False
if self.queue.empty():
self.idle_event.set()
else: else:
self.log.info("Reset de-asserted") self.log.info("Reset de-asserted")
if self._run_cr is None: if self._run_cr is None:
self._run_cr = cocotb.fork(self._run()) self._run_cr = cocotb.fork(self._run())
self.active = False
async def _run(self): async def _run(self):
raise NotImplementedError() raise NotImplementedError()
@@ -188,14 +193,30 @@ class StreamSource(StreamBase, StreamPause):
_valid_init = 0 _valid_init = 0
_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)
self.queue_occupancy_limit = -1
async def send(self, obj): async def send(self, obj):
while self.full():
self.dequeue_event.clear()
await self.dequeue_event.wait()
await self.queue.put(obj) await self.queue.put(obj)
self.idle_event.clear() self.idle_event.clear()
def send_nowait(self, obj): def send_nowait(self, obj):
if self.full():
raise QueueFull()
self.queue.put_nowait(obj) self.queue.put_nowait(obj)
self.idle_event.clear() self.idle_event.clear()
def full(self):
if self.queue_occupancy_limit > 0 and self.count() >= self.queue_occupancy_limit:
return True
else:
return False
def idle(self): def idle(self):
return self.empty() and not self.active return self.empty() and not self.active
@@ -205,8 +226,9 @@ class StreamSource(StreamBase, StreamPause):
def _handle_reset(self, state): def _handle_reset(self, state):
super()._handle_reset(state) super()._handle_reset(state)
if self.valid is not None: if state:
self.valid <= 0 if self.valid is not None:
self.valid <= 0
async def _run(self): async def _run(self):
while True: while True:
@@ -219,6 +241,7 @@ class StreamSource(StreamBase, StreamPause):
if (ready_sample and valid_sample) or (not valid_sample): if (ready_sample and valid_sample) or (not valid_sample):
if not self.queue.empty() and not self.pause: if not self.queue.empty() and not self.pause:
self.bus.drive(self.queue.get_nowait()) self.bus.drive(self.queue.get_nowait())
self.dequeue_event.set()
if self.valid is not None: if self.valid is not None:
self.valid <= 1 self.valid <= 1
self.active = True self.active = True
@@ -237,19 +260,18 @@ class StreamMonitor(StreamBase):
_valid_init = None _valid_init = None
_ready_init = None _ready_init = None
async def recv(self): def _recv(self, item):
item = await self.queue.get()
if self.queue.empty(): if self.queue.empty():
self.active_event.clear() self.active_event.clear()
return item return item
async def recv(self):
item = await self.queue.get()
return self._recv(item)
def recv_nowait(self): def recv_nowait(self):
if not self.queue.empty(): item = self.queue.get_nowait()
item = self.queue.get_nowait() return self._recv(item)
if self.queue.empty():
self.active_event.clear()
return item
return None
async def wait(self, timeout=0, timeout_unit=None): async def wait(self, timeout=0, timeout_unit=None):
if not self.empty(): if not self.empty():
@@ -295,8 +317,9 @@ class StreamSink(StreamMonitor, StreamPause):
def _handle_reset(self, state): def _handle_reset(self, state):
super()._handle_reset(state) super()._handle_reset(state)
if self.ready is not None: if state:
self.ready <= 0 if self.ready is not None:
self.ready <= 0
async def _run(self): async def _run(self):
while True: while True:

View File

@@ -1 +1 @@
__version__ = "0.1.8" __version__ = "0.1.10"

View File

@@ -160,6 +160,11 @@ async def run_test_write_words(dut):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
event = tb.axi_master.init_write(addr, test_data)
await event.wait()
assert tb.axi_ram.read(addr, length) == test_data
test_data = bytearray([x % 256 for x in range(length)]) test_data = bytearray([x % 256 for x in range(length)])
await tb.axi_master.write(addr, test_data) await tb.axi_master.write(addr, test_data)
assert tb.axi_ram.read(addr, length) == test_data assert tb.axi_ram.read(addr, length) == test_data
@@ -209,6 +214,12 @@ async def run_test_read_words(dut):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
tb.axi_ram.write(addr, test_data)
event = tb.axi_master.init_read(addr, length)
await event.wait()
assert event.data.data == test_data
test_data = bytearray([x % 256 for x in range(length)]) test_data = bytearray([x % 256 for x in range(length)])
tb.axi_ram.write(addr, test_data) tb.axi_ram.write(addr, test_data)
assert (await tb.axi_master.read(addr, length)).data == test_data assert (await tb.axi_master.read(addr, length)).data == test_data
@@ -287,7 +298,7 @@ def cycle_pause():
if cocotb.SIM_NAME: if cocotb.SIM_NAME:
data_width = int(os.getenv("PARAM_DATA_WIDTH")) data_width = len(cocotb.top.axi_wdata)
byte_width = data_width // 8 byte_width = data_width // 8
max_burst_size = (byte_width-1).bit_length() max_burst_size = (byte_width-1).bit_length()

View File

@@ -149,6 +149,11 @@ async def run_test_write_words(dut):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
event = tb.axil_master.init_write(addr, test_data)
await event.wait()
assert tb.axil_ram.read(addr, length) == test_data
test_data = bytearray([x % 256 for x in range(length)]) test_data = bytearray([x % 256 for x in range(length)])
await tb.axil_master.write(addr, test_data) await tb.axil_master.write(addr, test_data)
assert tb.axil_ram.read(addr, length) == test_data assert tb.axil_ram.read(addr, length) == test_data
@@ -198,6 +203,12 @@ async def run_test_read_words(dut):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
test_data = bytearray([x % 256 for x in range(length)])
tb.axil_ram.write(addr, test_data)
event = tb.axil_master.init_read(addr, length)
await event.wait()
assert event.data.data == test_data
test_data = bytearray([x % 256 for x in range(length)]) test_data = bytearray([x % 256 for x in range(length)])
tb.axil_ram.write(addr, test_data) tb.axil_ram.write(addr, test_data)
assert (await tb.axil_master.read(addr, length)).data == test_data assert (await tb.axil_master.read(addr, length)).data == test_data

View File

@@ -126,7 +126,7 @@ def cycle_pause():
def size_list(): def size_list():
data_width = int(os.getenv("PARAM_DATA_WIDTH")) data_width = len(cocotb.top.axis_tdata)
byte_width = data_width // 8 byte_width = data_width // 8
return list(range(1, byte_width*4+1)) + [512] + [1]*64 return list(range(1, byte_width*4+1)) + [512] + [1]*64