13 Commits

Author SHA1 Message Date
Alex Forencich
b8919a095b Release v0.1.12 2021-04-12 22:46:32 -07:00
Alex Forencich
bc7edec289 Make resp and prot signals optional 2021-04-12 22:04:22 -07:00
Alex Forencich
e7c3a31eb0 Improve handling for optional signals 2021-04-12 21:24:33 -07:00
Alex Forencich
ce907ffbb9 Print out signal summary 2021-04-12 19:29:41 -07:00
Alex Forencich
95e2d5800d Store parameters 2021-04-12 19:27:38 -07:00
Alex Forencich
82853b31ff Rename byte_width to byte_lanes 2021-04-12 15:08:30 -07:00
Alex Forencich
8bbabd92df Update readme 2021-04-12 15:07:26 -07:00
Alex Forencich
c060f6c963 Transmit data without using pop 2021-04-12 13:53:28 -07:00
Alex Forencich
a767e00ce5 Transmit frames without using pop 2021-04-12 13:49:20 -07:00
Alex Forencich
d1d7313b98 Add tag context manager to AXI master to reuse per-ID processing components 2021-04-08 19:03:46 -07:00
Alex Forencich
01b43b97f2 Update readme 2021-04-08 19:01:27 -07:00
Alex Forencich
b5b6df84fe Improve burst handling in AXI master 2021-03-25 18:03:36 -07:00
Alex Forencich
babe69f4d3 Bump to dev version 2021-03-24 21:50:10 -07:00
11 changed files with 457 additions and 258 deletions

View File

@@ -46,12 +46,12 @@ The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object
Once the module is instantiated, read and write operations can be initiated in a couple of different ways. Once the module is instantiated, read and write operations can be initiated in a couple of different ways.
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: First, operations can be carried out with async blocking `read()`, `write()`, and their associated word-access wrappers. Multiple concurrent operations started from different coroutines are handled correctly, with results returned in the order that the operations complete. 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()` 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. Additional parameters can be specified to control sideband signals and burst settings. The transfer will be split into one or more bursts according to the AXI specification. All bursts generated from the same call to `read()` or `write()` will use the same ID, which will be automatically generated if not specified. `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: 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:
@@ -141,6 +141,7 @@ Once the module is instantiated, the memory contents can be accessed in a couple
axi_ram.write(0x0000, b'test') axi_ram.write(0x0000, b'test')
data = axi_ram.read(0x0000, 4) data = axi_ram.read(0x0000, 4)
axi_ram.hexdump(0x0000, 4, prefix="RAM")
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM: Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
@@ -242,8 +243,8 @@ Note: _byte_size_, _byte_lanes_, `len(tdata)`, and `len(tkeep)` are all related,
* _pause_: stall the interface (deassert `tready` or `tvalid`) (source/sink only) * _pause_: stall the interface (deassert `tready` or `tvalid`) (source/sink only)
* _queue_occupancy_bytes_: number of bytes in queue (all) * _queue_occupancy_bytes_: number of bytes in queue (all)
* _queue_occupancy_frames_: number of frames in queue (all) * _queue_occupancy_frames_: number of frames in queue (all)
* _queue_occupancy_limit_bytes_: max number of bytes in queue allowed before tready deassert (sink only) * _queue_occupancy_limit_bytes_: max number of bytes in queue allowed before backpressure is applied (source/sink only)
* _queue_occupancy_limit_frames_: max number of frames in queue allowed before tready deassert (sink only) * _queue_occupancy_limit_frames_: max number of frames in queue allowed before backpressure is applied (source/sink only)
#### Methods #### Methods

View File

@@ -26,8 +26,8 @@ from .stream import define_stream
# Write address channel # Write address channel
AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW", AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awprot", "awvalid", "awready"], signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awvalid", "awready"],
optional_signals=["awlock", "awcache", "awqos", "awregion", "awuser"], optional_signals=["awlock", "awcache", "awprot", "awqos", "awregion", "awuser"],
signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1, signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1,
"awcache": 4, "awprot": 3, "awqos": 4, "awregion": 4} "awcache": 4, "awprot": 3, "awqos": 4, "awregion": 4}
) )
@@ -41,23 +41,23 @@ AxiWBus, AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("Axi
# Write response channel # Write response channel
AxiBBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB", AxiBBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
signals=["bid", "bresp", "bvalid", "bready"], signals=["bid", "bvalid", "bready"],
optional_signals=["buser"], optional_signals=["bresp", "buser"],
signal_widths={"bresp": 2} signal_widths={"bresp": 2}
) )
# Read address channel # Read address channel
AxiARBus, AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR", AxiARBus, AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arprot", "arvalid", "arready"], signals=["arid", "araddr", "arlen", "arsize", "arburst", "arvalid", "arready"],
optional_signals=["arlock", "arcache", "arqos", "arregion", "aruser"], optional_signals=["arlock", "arcache", "arprot", "arqos", "arregion", "aruser"],
signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1, signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1,
"arcache": 4, "arprot": 3, "arqos": 4, "arregion": 4} "arcache": 4, "arprot": 3, "arqos": 4, "arregion": 4}
) )
# Read data channel # Read data channel
AxiRBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR", AxiRBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
signals=["rid", "rdata", "rresp", "rlast", "rvalid", "rready"], signals=["rid", "rdata", "rlast", "rvalid", "rready"],
optional_signals=["ruser"], optional_signals=["rresp", "ruser"],
signal_widths={"rresp": 2, "rlast": 1} signal_widths={"rresp": 2, "rlast": 1}
) )

View File

@@ -49,8 +49,97 @@ AxiReadRespCmd = namedtuple("AxiReadRespCmd", ["address", "length", "size", "cyc
AxiReadResp = namedtuple("AxiReadResp", ["address", "data", "resp", "user"]) AxiReadResp = namedtuple("AxiReadResp", ["address", "data", "resp", "user"])
class TagContext:
def __init__(self, manager):
self.current_tag = 0
self._cmd_queue = Queue()
self._current_cmd = None
self._resp_queue = Queue()
self._cr = None
self._manager = manager
async def get_resp(self):
return await self._resp_queue.get()
def get_resp_nowait(self):
return self._resp_queue.get_nowait()
def _start(self):
if self._cr is None:
self._cr = cocotb.fork(self._process_queue())
def _flush(self):
flushed_cmds = []
if self._cr is not None:
self._cr.kill()
self._cr = None
self._manager._set_idle(self)
if self._current_cmd is not None:
flushed_cmds.append(self._current_cmd)
self._current_cmd = None
while not self._cmd_queue.empty():
flushed_cmds.append(self._cmd_queue.get_nowait())
while not self._resp_queue.empty():
self._resp_queue.get_nowait()
return flushed_cmds
async def _process_queue(self):
while True:
cmd = await self._cmd_queue.get()
self._current_cmd = cmd
await self._manager._process(self, cmd)
self._current_cmd = None
if self._cmd_queue.empty() and self._resp_queue.empty():
self._manager._set_idle(self)
class TagContextManager:
def __init__(self, process):
self._context_list = []
self._context_idle_list = []
self._context_mapping = {}
self._process = process
def _get_context(self, tag):
if tag in self._context_mapping:
return self._context_mapping[tag]
elif self._context_idle_list:
context = self._context_idle_list.pop()
else:
context = TagContext(self)
self._context_list.append(context)
context._start()
context.current_tag = tag
self._context_mapping[tag] = context
return context
def start_cmd(self, tag, cmd):
context = self._get_context(tag)
context._cmd_queue.put_nowait(cmd)
def put_resp(self, tag, resp):
context = self._get_context(tag)
context._resp_queue.put_nowait(resp)
def _set_idle(self, context):
if context.current_tag in self._context_mapping:
del self._context_mapping[context.current_tag]
self._context_idle_list.append(context)
context.current_tag = None
def flush(self):
flushed_cmds = []
for c in self._context_list:
flushed_cmds.extend(c._flush())
return flushed_cmds
class AxiMasterWrite(Reset): class AxiMasterWrite(Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256): def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
self.log.info("AXI master (write)") self.log.info("AXI master (write)")
@@ -72,9 +161,7 @@ class AxiMasterWrite(Reset):
self.cur_id = 0 self.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
self.int_write_resp_command_queue = [Queue() for k in range(self.id_count)] self.tag_context_manager = TagContextManager(self._process_write_resp_id)
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.in_flight_operations = 0 self.in_flight_operations = 0
self._idle = Event() self._idle = Event()
@@ -82,29 +169,45 @@ class AxiMasterWrite(Reset):
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.strb_mask = 2**self.byte_width-1 self.strb_mask = 2**self.byte_lanes-1
self.max_burst_len = max(min(max_burst_len, 256), 1) self.max_burst_len = max(min(max_burst_len, 256), 1)
self.max_burst_size = (self.byte_width-1).bit_length() self.max_burst_size = (self.byte_lanes-1).bit_length()
self.awlock_present = hasattr(self.bus.aw, "awlock")
self.awcache_present = hasattr(self.bus.aw, "awcache")
self.awprot_present = hasattr(self.bus.aw, "awprot")
self.awqos_present = hasattr(self.bus.aw, "awqos")
self.awregion_present = hasattr(self.bus.aw, "awregion")
self.awuser_present = hasattr(self.bus.aw, "awuser")
self.wuser_present = hasattr(self.bus.w, "wuser")
self.buser_present = hasattr(self.bus.b, "buser")
self.log.info("AXI master configuration:") self.log.info("AXI master configuration:")
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid)) self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size) self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size)
self.log.info(" Max burst length: %d cycles (%d bytes)", self.log.info(" Max burst length: %d cycles (%d bytes)",
self.max_burst_len, self.max_burst_len*self.byte_width) self.max_burst_len, self.max_burst_len*self.byte_lanes)
assert self.byte_width == len(self.w_channel.bus.wstrb) self.log.info("AXI master signals:")
assert self.byte_width * self.byte_size == self.width for bus in (self.bus.aw, self.bus.w, self.bus.b):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
assert self.byte_lanes * self.byte_size == self.width
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid) assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
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)
@@ -132,6 +235,27 @@ class AxiMasterWrite(Reset):
lock = AxiLockType(lock) lock = AxiLockType(lock)
prot = AxiProt(prot) prot = AxiProt(prot)
if not self.awlock_present and lock != AxiLockType.NORMAL:
raise ValueError("awlock sideband signal value specified, but signal is not connected")
if not self.awcache_present and cache != 0b0011:
raise ValueError("awcache sideband signal value specified, but signal is not connected")
if not self.awprot_present and prot != AxiProt.NONSECURE:
raise ValueError("awprot sideband signal value specified, but signal is not connected")
if not self.awqos_present and qos != 0:
raise ValueError("awqos sideband signal value specified, but signal is not connected")
if not self.awregion_present and region != 0:
raise ValueError("awregion sideband signal value specified, but signal is not connected")
if not self.awuser_present and user != 0:
raise ValueError("awuser sideband signal value specified, but signal is not connected")
if not self.wuser_present and wuser != 0:
raise ValueError("wuser sideband signal value specified, but signal is not connected")
if wuser is None: if wuser is None:
wuser = 0 wuser = 0
elif isinstance(wuser, int): elif isinstance(wuser, int):
@@ -207,10 +331,6 @@ 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.aw_channel.clear()
self.w_channel.clear() self.w_channel.clear()
@@ -230,20 +350,8 @@ class AxiMasterWrite(Reset):
self.current_write_command = None self.current_write_command = None
flush_cmd(cmd) flush_cmd(cmd)
for q in self.int_write_resp_command_queue: for cmd in self.tag_context_manager.flush():
while not q.empty(): flush_cmd(cmd)
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.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
@@ -256,8 +364,6 @@ class AxiMasterWrite(Reset):
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._process_write_resp_id_cr = [cocotb.fork(self._process_write_resp_id(i)) for i in range(self.id_count)]
async def _process_write(self): async def _process_write(self):
while True: while True:
@@ -267,10 +373,10 @@ class AxiMasterWrite(Reset):
num_bytes = 2**cmd.size num_bytes = 2**cmd.size
aligned_addr = (cmd.address // num_bytes) * num_bytes aligned_addr = (cmd.address // num_bytes) * num_bytes
word_addr = (cmd.address // self.byte_width) * self.byte_width word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
start_offset = cmd.address % self.byte_width start_offset = cmd.address % self.byte_lanes
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_width) + 1 end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_lanes) + 1
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
@@ -303,7 +409,7 @@ class AxiMasterWrite(Reset):
if k == cycles-1: if k == cycles-1:
stop = end_offset stop = end_offset
strb = (self.strb_mask << start) & self.strb_mask & (self.strb_mask >> (self.byte_width - stop)) strb = (self.strb_mask << start) & self.strb_mask & (self.strb_mask >> (self.byte_lanes - stop))
val = 0 val = 0
for j in range(start, stop): for j in range(start, stop):
@@ -350,18 +456,18 @@ class AxiMasterWrite(Reset):
if isinstance(wuser, int): if isinstance(wuser, int):
w.wuser = wuser w.wuser = wuser
else: else:
if wuser: if wuser and k < len(wuser):
w.wuser = wuser.pop(0) w.wuser = wuser[k]
else: else:
w.wuser = 0 w.wuser = 0
await self.w_channel.send(w) await self.w_channel.send(w)
cur_addr += num_bytes cur_addr += num_bytes
cycle_offset = (cycle_offset + num_bytes) % self.byte_width 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)
await self.int_write_resp_command_queue[awid].put(resp_cmd) self.tag_context_manager.start_cmd(awid, resp_cmd)
self.current_write_command = None self.current_write_command = None
@@ -374,53 +480,54 @@ class AxiMasterWrite(Reset):
if self.active_id[bid] <= 0: if self.active_id[bid] <= 0:
raise Exception(f"Unexpected burst ID {bid}") raise Exception(f"Unexpected burst ID {bid}")
await self.int_write_resp_queue_list[bid].put(b) self.tag_context_manager.put_resp(bid, b)
async def _process_write_resp_id(self, bid): async def _process_write_resp_id(self, context, cmd):
while True: bid = context.current_tag
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 burst_length in cmd.burst_list: for burst_length in cmd.burst_list:
b = await self.int_write_resp_queue_list[bid].get() b = await context.get_resp()
burst_id = int(b.bid) burst_resp = AxiResp(b.bresp)
burst_resp = AxiResp(b.bresp) burst_user = int(b.buser)
burst_user = int(b.buser)
if burst_resp != AxiResp.OKAY: if burst_resp != AxiResp.OKAY:
resp = burst_resp resp = burst_resp
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: if self.active_id[bid] <= 0:
raise Exception(f"Unexpected burst ID {bid}") raise Exception(f"Unexpected burst ID {bid}")
self.active_id[bid] -= 1 self.active_id[bid] -= 1
self.log.info("Write burst complete bid: 0x%x bresp: %s", burst_id, burst_resp) self.log.info("Write burst complete bid: 0x%x bresp: %s", bid, burst_resp)
self.log.info("Write complete addr: 0x%08x prot: %s resp: %s length: %d", if not self.buser_present:
cmd.address, cmd.prot, resp, cmd.length) user = None
write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user) self.log.info("Write complete addr: 0x%08x prot: %s resp: %s length: %d",
cmd.address, cmd.prot, resp, cmd.length)
cmd.event.set(write_resp) write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user)
self.current_write_resp_command[bid] = None cmd.event.set(write_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
if self.in_flight_operations == 0: if self.in_flight_operations == 0:
self._idle.set() self._idle.set()
class AxiMasterRead(Reset): class AxiMasterRead(Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256): def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
self.log.info("AXI master (read)") self.log.info("AXI master (read)")
@@ -440,9 +547,7 @@ class AxiMasterRead(Reset):
self.cur_id = 0 self.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
self.int_read_resp_command_queue = [Queue() for k in range(self.id_count)] self.tag_context_manager = TagContextManager(self._process_read_resp_id)
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.in_flight_operations = 0 self.in_flight_operations = 0
self._idle = Event() self._idle = Event()
@@ -450,27 +555,42 @@ class AxiMasterRead(Reset):
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.max_burst_len = max(min(max_burst_len, 256), 1) self.max_burst_len = max(min(max_burst_len, 256), 1)
self.max_burst_size = (self.byte_width-1).bit_length() self.max_burst_size = (self.byte_lanes-1).bit_length()
self.arlock_present = hasattr(self.bus.ar, "arlock")
self.arcache_present = hasattr(self.bus.ar, "arcache")
self.arprot_present = hasattr(self.bus.ar, "arprot")
self.arqos_present = hasattr(self.bus.ar, "arqos")
self.arregion_present = hasattr(self.bus.ar, "arregion")
self.aruser_present = hasattr(self.bus.ar, "aruser")
self.ruser_present = hasattr(self.bus.r, "ruser")
self.log.info("AXI master configuration:") self.log.info("AXI master configuration:")
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid)) self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size) self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size)
self.log.info(" Max burst length: %d cycles (%d bytes)", self.log.info(" Max burst length: %d cycles (%d bytes)",
self.max_burst_len, self.max_burst_len*self.byte_width) self.max_burst_len, self.max_burst_len*self.byte_lanes)
assert self.byte_width * self.byte_size == self.width self.log.info("AXI master signals:")
for bus in (self.bus.ar, self.bus.r):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes * self.byte_size == self.width
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid) assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
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)
@@ -501,6 +621,24 @@ class AxiMasterRead(Reset):
lock = AxiLockType(lock) lock = AxiLockType(lock)
prot = AxiProt(prot) prot = AxiProt(prot)
if not self.arlock_present and lock != AxiLockType.NORMAL:
raise ValueError("arlock sideband signal value specified, but signal is not connected")
if not self.arcache_present and cache != 0b0011:
raise ValueError("arcache sideband signal value specified, but signal is not connected")
if not self.arprot_present and prot != AxiProt.NONSECURE:
raise ValueError("arprot sideband signal value specified, but signal is not connected")
if not self.arqos_present and qos != 0:
raise ValueError("arqos sideband signal value specified, but signal is not connected")
if not self.arregion_present and region != 0:
raise ValueError("arregion sideband signal value specified, but signal is not connected")
if not self.aruser_present and user != 0:
raise ValueError("aruser sideband signal value specified, but signal is not connected")
self.in_flight_operations += 1 self.in_flight_operations += 1
self._idle.clear() self._idle.clear()
@@ -568,10 +706,6 @@ 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.ar_channel.clear()
self.r_channel.clear() self.r_channel.clear()
@@ -590,20 +724,8 @@ class AxiMasterRead(Reset):
self.current_read_command = None self.current_read_command = None
flush_cmd(cmd) flush_cmd(cmd)
for q in self.int_read_resp_command_queue: for cmd in self.tag_context_manager.flush():
while not q.empty(): flush_cmd(cmd)
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.cur_id = 0
self.active_id = Counter() self.active_id = Counter()
@@ -616,8 +738,6 @@ class AxiMasterRead(Reset):
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._process_read_resp_id_cr = [cocotb.fork(self._process_read_resp_id(i)) for i in range(self.id_count)]
async def _process_read(self): async def _process_read(self):
while True: while True:
@@ -680,94 +800,101 @@ 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[arid].put(resp_cmd) self.tag_context_manager.start_cmd(arid, resp_cmd)
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(r.rid) rid = int(r.rid)
if cur_rid is not None and cur_rid != rid:
raise Exception(f"ID not constant within burst (expected {cur_rid}, got {rid})")
if self.active_id[rid] <= 0: if self.active_id[rid] <= 0:
raise Exception(f"Unexpected burst ID {rid}") raise Exception(f"Unexpected burst ID {rid}")
await self.int_read_resp_queue_list[rid].put(r) burst.append(r)
cur_rid = rid
async def _process_read_resp_id(self, rid): if int(r.rlast):
while True: self.tag_context_manager.put_resp(rid, burst)
cmd = await self.int_read_resp_command_queue[rid].get() burst = []
self.current_read_resp_command[rid] = cmd cur_rid = None
num_bytes = 2**cmd.size async def _process_read_resp_id(self, context, cmd):
rid = context.current_tag
aligned_addr = (cmd.address // num_bytes) * num_bytes num_bytes = 2**cmd.size
word_addr = (cmd.address // self.byte_width) * self.byte_width
start_offset = cmd.address % self.byte_width aligned_addr = (cmd.address // num_bytes) * num_bytes
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
cycle_offset = aligned_addr - word_addr start_offset = cmd.address % self.byte_lanes
data = bytearray()
resp = AxiResp.OKAY cycle_offset = aligned_addr - word_addr
user = [] data = bytearray()
first = True resp = AxiResp.OKAY
user = []
for burst_length in cmd.burst_list: first = True
for k in range(burst_length):
r = await self.int_read_resp_queue_list[rid].get()
cycle_id = int(r.rid) for burst_length in cmd.burst_list:
cycle_data = int(r.rdata) burst = await context.get_resp()
cycle_resp = AxiResp(r.rresp)
cycle_last = int(r.rlast)
cycle_user = int(r.ruser)
if cycle_resp != AxiResp.OKAY: if len(burst) != burst_length:
resp = cycle_resp raise Exception(f"Burst length incorrect (ID {rid}, expected {burst_length}, got {len(burst)}")
if cycle_user is not None: for r in burst:
user.append(cycle_user) cycle_data = int(r.rdata)
cycle_resp = AxiResp(r.rresp)
cycle_user = int(r.ruser)
start = cycle_offset if cycle_resp != AxiResp.OKAY:
stop = cycle_offset+num_bytes resp = cycle_resp
if first: if cycle_user is not None:
start = start_offset user.append(cycle_user)
assert cycle_last == (k == burst_length - 1) start = cycle_offset
stop = cycle_offset+num_bytes
for j in range(start, stop): if first:
data.append((cycle_data >> j*8) & 0xff) start = start_offset
cycle_offset = (cycle_offset + num_bytes) % self.byte_width for j in range(start, stop):
data.append((cycle_data >> j*8) & 0xff)
first = False cycle_offset = (cycle_offset + num_bytes) % self.byte_lanes
if self.active_id[rid] <= 0: first = False
raise Exception(f"Unexpected burst ID {rid}")
self.active_id[rid] -= 1 self.active_id[rid] -= 1
self.log.info("Read burst complete rid: 0x%x rresp: %s", cycle_id, resp) self.log.info("Read burst complete rid: 0x%x rresp: %s", rid, resp)
data = data[:cmd.length] data = data[:cmd.length]
self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s", if not self.ruser_present:
cmd.address, cmd.prot, resp, ' '.join((f'{c:02x}' for c in data))) user = None
read_resp = AxiReadResp(cmd.address, data, resp, user) self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s",
cmd.address, cmd.prot, resp, ' '.join((f'{c:02x}' for c in data)))
cmd.event.set(read_resp) read_resp = AxiReadResp(cmd.address, data, resp, user)
self.current_read_resp_command[rid] = None cmd.event.set(read_resp)
self.in_flight_operations -= 1 self.in_flight_operations -= 1
if self.in_flight_operations == 0: if self.in_flight_operations == 0:
self._idle.set() self._idle.set()
class AxiMaster: class AxiMaster:

View File

@@ -35,6 +35,9 @@ from .reset import Reset
class AxiRamWrite(Memory, Reset): class AxiRamWrite(Memory, Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs): def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
self.log.info("AXI RAM model (write)") self.log.info("AXI RAM model (write)")
@@ -53,18 +56,26 @@ class AxiRamWrite(Memory, Reset):
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.strb_mask = 2**self.byte_width-1 self.strb_mask = 2**self.byte_lanes-1
self.log.info("AXI RAM model configuration:") self.log.info("AXI RAM model configuration:")
self.log.info(" Memory size: %d bytes", len(self.mem)) self.log.info(" Memory size: %d bytes", len(self.mem))
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid)) self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width == len(self.w_channel.bus.wstrb) self.log.info("AXI RAM model signals:")
assert self.byte_width * self.byte_size == self.width for bus in (self.bus.aw, self.bus.w, self.bus.b):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
assert self.byte_lanes * self.byte_size == self.width
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid) assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
@@ -102,7 +113,7 @@ class AxiRamWrite(Memory, Reset):
awid, addr, length, size, prot) awid, addr, length, size, prot)
num_bytes = 2**size num_bytes = 2**size
assert 0 < num_bytes <= self.byte_width assert 0 < num_bytes <= self.byte_lanes
aligned_addr = (addr // num_bytes) * num_bytes aligned_addr = (addr // num_bytes) * num_bytes
length += 1 length += 1
@@ -120,7 +131,7 @@ class AxiRamWrite(Memory, Reset):
cur_addr = aligned_addr cur_addr = aligned_addr
for n in range(length): for n in range(length):
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width cur_word_addr = (cur_addr // self.byte_lanes) * self.byte_lanes
w = await self.w_channel.recv() w = await self.w_channel.recv()
@@ -132,12 +143,12 @@ class AxiRamWrite(Memory, Reset):
self.mem.seek(cur_word_addr % self.size) self.mem.seek(cur_word_addr % self.size)
data = data.to_bytes(self.byte_width, 'little') data = data.to_bytes(self.byte_lanes, 'little')
self.log.debug("Write word awid: 0x%x addr: 0x%08x wstrb: 0x%02x data: %s", self.log.debug("Write word awid: 0x%x addr: 0x%08x wstrb: 0x%02x data: %s",
awid, cur_addr, strb, ' '.join((f'{c:02x}' for c in data))) awid, cur_addr, strb, ' '.join((f'{c:02x}' for c in data)))
for i in range(self.byte_width): for i in range(self.byte_lanes):
if strb & (1 << i): if strb & (1 << i):
self.mem.write(data[i:i+1]) self.mem.write(data[i:i+1])
else: else:
@@ -161,6 +172,9 @@ class AxiRamWrite(Memory, Reset):
class AxiRamRead(Memory, Reset): class AxiRamRead(Memory, Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs): def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
self.log.info("AXI RAM model (read)") self.log.info("AXI RAM model (read)")
@@ -177,16 +191,24 @@ class AxiRamRead(Memory, Reset):
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.log.info("AXI RAM model configuration:") self.log.info("AXI RAM model configuration:")
self.log.info(" Memory size: %d bytes", len(self.mem)) self.log.info(" Memory size: %d bytes", len(self.mem))
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid)) self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width * self.byte_size == self.width self.log.info("AXI RAM model signals:")
for bus in (self.bus.ar, self.bus.r):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes * self.byte_size == self.width
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid) assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
@@ -223,7 +245,7 @@ class AxiRamRead(Memory, Reset):
arid, addr, length, size, prot) arid, addr, length, size, prot)
num_bytes = 2**size num_bytes = 2**size
assert 0 < num_bytes <= self.byte_width assert 0 < num_bytes <= self.byte_lanes
aligned_addr = (addr // num_bytes) * num_bytes aligned_addr = (addr // num_bytes) * num_bytes
length += 1 length += 1
@@ -241,11 +263,11 @@ class AxiRamRead(Memory, Reset):
cur_addr = aligned_addr cur_addr = aligned_addr
for n in range(length): for n in range(length):
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width cur_word_addr = (cur_addr // self.byte_lanes) * self.byte_lanes
self.mem.seek(cur_word_addr % self.size) self.mem.seek(cur_word_addr % self.size)
data = self.mem.read(self.byte_width) data = self.mem.read(self.byte_lanes)
r = self.r_channel._transaction_obj() r = self.r_channel._transaction_obj()
r.rid = arid r.rid = arid

View File

@@ -26,7 +26,8 @@ from .stream import define_stream
# Write address channel # Write address channel
AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW", AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
signals=["awaddr", "awprot", "awvalid", "awready"], signals=["awaddr", "awvalid", "awready"],
optional_signals=["awprot"],
signal_widths={"awprot": 3} signal_widths={"awprot": 3}
) )
@@ -37,19 +38,22 @@ AxiLiteWBus, AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor
# Write response channel # Write response channel
AxiLiteBBus, AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB", AxiLiteBBus, AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
signals=["bresp", "bvalid", "bready"], signals=["bvalid", "bready"],
optional_signals=["bresp"],
signal_widths={"bresp": 2} signal_widths={"bresp": 2}
) )
# Read address channel # Read address channel
AxiLiteARBus, AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR", AxiLiteARBus, AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
signals=["araddr", "arprot", "arvalid", "arready"], signals=["araddr", "arvalid", "arready"],
optional_signals=["arprot"],
signal_widths={"arprot": 3} signal_widths={"arprot": 3}
) )
# Read data channel # Read data channel
AxiLiteRBus, AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR", AxiLiteRBus, AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
signals=["rdata", "rresp", "rvalid", "rready"], signals=["rdata", "rvalid", "rready"],
optional_signals=["rresp"],
signal_widths={"rresp": 2} signal_widths={"rresp": 2}
) )

View File

@@ -47,6 +47,9 @@ AxiLiteReadResp = namedtuple("AxiLiteReadResp", ["address", "data", "resp"])
class AxiLiteMasterWrite(Reset): class AxiLiteMasterWrite(Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True): def __init__(self, bus, clock, reset=None, reset_active_level=True):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
self.log.info("AXI lite master (write)") self.log.info("AXI lite master (write)")
@@ -73,16 +76,26 @@ class AxiLiteMasterWrite(Reset):
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.strb_mask = 2**self.byte_width-1 self.strb_mask = 2**self.byte_lanes-1
self.awprot_present = hasattr(self.bus.aw, "awprot")
self.log.info("AXI lite master configuration:") self.log.info("AXI lite master configuration:")
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width == len(self.w_channel.bus.wstrb) self.log.info("AXI lite master signals:")
assert self.byte_width * self.byte_size == self.width for bus in (self.bus.aw, self.bus.w, self.bus.b):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
assert self.byte_lanes * self.byte_size == self.width
self._process_write_cr = None self._process_write_cr = None
self._process_write_resp_cr = None self._process_write_resp_cr = None
@@ -96,6 +109,9 @@ class AxiLiteMasterWrite(Reset):
if not isinstance(event, Event): if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
if not self.awprot_present and prot != AxiProt.NONSECURE:
raise ValueError("awprot sideband signal value specified, but signal is not connected")
self.in_flight_operations += 1 self.in_flight_operations += 1
self._idle.clear() self._idle.clear()
@@ -191,15 +207,15 @@ class AxiLiteMasterWrite(Reset):
cmd = await self.write_command_queue.get() cmd = await self.write_command_queue.get()
self.current_write_command = cmd self.current_write_command = cmd
word_addr = (cmd.address // self.byte_width) * self.byte_width word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
start_offset = cmd.address % self.byte_width start_offset = cmd.address % self.byte_lanes
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_width) + 1 end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_lanes) + 1
strb_start = (self.strb_mask << start_offset) & self.strb_mask strb_start = (self.strb_mask << start_offset) & self.strb_mask
strb_end = self.strb_mask >> (self.byte_width - end_offset) strb_end = self.strb_mask >> (self.byte_lanes - end_offset)
cycles = (len(cmd.data) + (cmd.address % self.byte_width) + self.byte_width-1) // self.byte_width cycles = (len(cmd.data) + (cmd.address % self.byte_lanes) + self.byte_lanes-1) // self.byte_lanes
resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event) resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event)
await self.int_write_resp_command_queue.put(resp_cmd) await self.int_write_resp_command_queue.put(resp_cmd)
@@ -211,7 +227,7 @@ class AxiLiteMasterWrite(Reset):
for k in range(cycles): for k in range(cycles):
start = 0 start = 0
stop = self.byte_width stop = self.byte_lanes
strb = self.strb_mask strb = self.strb_mask
if k == 0: if k == 0:
@@ -227,7 +243,7 @@ class AxiLiteMasterWrite(Reset):
offset += 1 offset += 1
aw = self.aw_channel._transaction_obj() aw = self.aw_channel._transaction_obj()
aw.awaddr = word_addr + k*self.byte_width aw.awaddr = word_addr + k*self.byte_lanes
aw.awprot = cmd.prot aw.awprot = cmd.prot
w = self.w_channel._transaction_obj() w = self.w_channel._transaction_obj()
@@ -271,6 +287,9 @@ class AxiLiteMasterWrite(Reset):
class AxiLiteMasterRead(Reset): class AxiLiteMasterRead(Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True): def __init__(self, bus, clock, reset=None, reset_active_level=True):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
self.log.info("AXI lite master (read)") self.log.info("AXI lite master (read)")
@@ -295,14 +314,24 @@ class AxiLiteMasterRead(Reset):
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.arprot_present = hasattr(self.bus.ar, "arprot")
self.log.info("AXI lite master configuration:") self.log.info("AXI lite master configuration:")
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width * self.byte_size == self.width self.log.info("AXI lite master signals:")
for bus in (self.bus.ar, self.bus.r):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes * self.byte_size == self.width
self._process_read_cr = None self._process_read_cr = None
self._process_read_resp_cr = None self._process_read_resp_cr = None
@@ -316,6 +345,9 @@ class AxiLiteMasterRead(Reset):
if not isinstance(event, Event): if not isinstance(event, Event):
raise ValueError("Expected event object") raise ValueError("Expected event object")
if not self.arprot_present and prot != AxiProt.NONSECURE:
raise ValueError("arprot sideband signal value specified, but signal is not connected")
self.in_flight_operations += 1 self.in_flight_operations += 1
self._idle.clear() self._idle.clear()
@@ -410,9 +442,9 @@ class AxiLiteMasterRead(Reset):
cmd = await self.read_command_queue.get() cmd = await self.read_command_queue.get()
self.current_read_command = cmd self.current_read_command = cmd
word_addr = (cmd.address // self.byte_width) * self.byte_width word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
cycles = (cmd.length + self.byte_width-1 + (cmd.address % self.byte_width)) // self.byte_width cycles = (cmd.length + self.byte_lanes-1 + (cmd.address % self.byte_lanes)) // self.byte_lanes
resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event) resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event)
await self.int_read_resp_command_queue.put(resp_cmd) await self.int_read_resp_command_queue.put(resp_cmd)
@@ -422,7 +454,7 @@ class AxiLiteMasterRead(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_width 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)
@@ -434,8 +466,8 @@ class AxiLiteMasterRead(Reset):
cmd = await self.int_read_resp_command_queue.get() cmd = await self.int_read_resp_command_queue.get()
self.current_read_resp_command = cmd self.current_read_resp_command = cmd
start_offset = cmd.address % self.byte_width start_offset = cmd.address % self.byte_lanes
end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1 end_offset = ((cmd.address + cmd.length - 1) % self.byte_lanes) + 1
data = bytearray() data = bytearray()
@@ -451,7 +483,7 @@ class AxiLiteMasterRead(Reset):
resp = cycle_resp resp = cycle_resp
start = 0 start = 0
stop = self.byte_width stop = self.byte_lanes
if k == 0: if k == 0:
start = start_offset start = start_offset

View File

@@ -35,6 +35,9 @@ from .reset import Reset
class AxiLiteRamWrite(Memory, Reset): class AxiLiteRamWrite(Memory, Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs): def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
self.log.info("AXI lite RAM model (write)") self.log.info("AXI lite RAM model (write)")
@@ -53,17 +56,25 @@ class AxiLiteRamWrite(Memory, Reset):
self.width = len(self.w_channel.bus.wdata) self.width = len(self.w_channel.bus.wdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.strb_mask = 2**self.byte_width-1 self.strb_mask = 2**self.byte_lanes-1
self.log.info("AXI lite RAM model configuration:") self.log.info("AXI lite RAM model configuration:")
self.log.info(" Memory size: %d bytes", len(self.mem)) self.log.info(" Memory size: %d bytes", len(self.mem))
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width == len(self.w_channel.bus.wstrb) self.log.info("AXI lite RAM model signals:")
assert self.byte_width * self.byte_size == self.width for bus in (self.bus.aw, self.bus.w, self.bus.b):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
assert self.byte_lanes * self.byte_size == self.width
self._process_write_cr = None self._process_write_cr = None
@@ -88,7 +99,7 @@ class AxiLiteRamWrite(Memory, Reset):
while True: while True:
aw = await self.aw_channel.recv() aw = await self.aw_channel.recv()
addr = (int(aw.awaddr) // self.byte_width) * self.byte_width addr = (int(aw.awaddr) // self.byte_lanes) * self.byte_lanes
prot = AxiProt(aw.awprot) prot = AxiProt(aw.awprot)
w = await self.w_channel.recv() w = await self.w_channel.recv()
@@ -100,12 +111,12 @@ class AxiLiteRamWrite(Memory, Reset):
self.mem.seek(addr % self.size) self.mem.seek(addr % self.size)
data = data.to_bytes(self.byte_width, 'little') data = data.to_bytes(self.byte_lanes, 'little')
self.log.info("Write data awaddr: 0x%08x awprot: %s wstrb: 0x%02x data: %s", self.log.info("Write data awaddr: 0x%08x awprot: %s wstrb: 0x%02x data: %s",
addr, prot, strb, ' '.join((f'{c:02x}' for c in data))) addr, prot, strb, ' '.join((f'{c:02x}' for c in data)))
for i in range(self.byte_width): for i in range(self.byte_lanes):
if strb & (1 << i): if strb & (1 << i):
self.mem.write(data[i:i+1]) self.mem.write(data[i:i+1])
else: else:
@@ -119,6 +130,9 @@ class AxiLiteRamWrite(Memory, Reset):
class AxiLiteRamRead(Memory, Reset): class AxiLiteRamRead(Memory, Reset):
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs): def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
self.bus = bus
self.clock = clock
self.reset = reset
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
self.log.info("AXI lite RAM model (read)") self.log.info("AXI lite RAM model (read)")
@@ -135,15 +149,23 @@ class AxiLiteRamRead(Memory, Reset):
self.width = len(self.r_channel.bus.rdata) self.width = len(self.r_channel.bus.rdata)
self.byte_size = 8 self.byte_size = 8
self.byte_width = self.width // self.byte_size self.byte_lanes = self.width // self.byte_size
self.log.info("AXI lite RAM model configuration:") self.log.info("AXI lite RAM model configuration:")
self.log.info(" Memory size: %d bytes", len(self.mem)) self.log.info(" Memory size: %d bytes", len(self.mem))
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
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_width) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
assert self.byte_width * self.byte_size == self.width self.log.info("AXI lite RAM model signals:")
for bus in (self.bus.ar, self.bus.r):
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
if hasattr(bus, sig):
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
else:
self.log.info(" %s: not present", sig)
assert self.byte_lanes * self.byte_size == self.width
self._process_read_cr = None self._process_read_cr = None
@@ -167,14 +189,14 @@ class AxiLiteRamRead(Memory, Reset):
while True: while True:
ar = await self.ar_channel.recv() ar = await self.ar_channel.recv()
addr = (int(ar.araddr) // self.byte_width) * self.byte_width addr = (int(ar.araddr) // self.byte_lanes) * self.byte_lanes
prot = AxiProt(ar.arprot) prot = AxiProt(ar.arprot)
# todo latency # todo latency
self.mem.seek(addr % self.size) self.mem.seek(addr % self.size)
data = self.mem.read(self.byte_width) data = self.mem.read(self.byte_lanes)
r = self.r_channel._transaction_obj() r = self.r_channel._transaction_obj()
r.rdata = int.from_bytes(data, 'little') r.rdata = int.from_bytes(data, 'little')

View File

@@ -319,25 +319,13 @@ class AxiStreamBase(Reset):
self.log.info("AXI stream %s configuration:", self._type) self.log.info("AXI stream %s configuration:", self._type)
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)
self.log.info(" tvalid: %s", "present" if hasattr(self.bus, "tvalid") else "not present")
self.log.info(" tready: %s", "present" if hasattr(self.bus, "tready") else "not present") self.log.info("AXI stream %s signals:", self._type)
self.log.info(" tlast: %s", "present" if hasattr(self.bus, "tlast") else "not present") for sig in sorted(list(set().union(self.bus._signals, self.bus._optional_signals))):
if hasattr(self.bus, "tkeep"): if hasattr(self.bus, sig):
self.log.info(" tkeep width: %d bits", len(self.bus.tkeep)) self.log.info(" %s width: %d bits", sig, len(getattr(self.bus, sig)))
else: else:
self.log.info(" tkeep: not present") self.log.info(" %s: not present", sig)
if hasattr(self.bus, "tid"):
self.log.info(" tid width: %d bits", len(self.bus.tid))
else:
self.log.info(" tid: not present")
if hasattr(self.bus, "tdest"):
self.log.info(" tdest width: %d bits", len(self.bus.tdest))
else:
self.log.info(" tdest: not present")
if hasattr(self.bus, "tuser"):
self.log.info(" tuser width: %d bits", len(self.bus.tuser))
else:
self.log.info(" tuser: not present")
if self.byte_lanes * self.byte_size != self.width: if self.byte_lanes * self.byte_size != self.width:
raise ValueError(f"Bus does not evenly divide into byte lanes " raise ValueError(f"Bus does not evenly divide into byte lanes "
@@ -492,6 +480,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
async def _run(self): async def _run(self):
frame = None frame = None
frame_offset = 0
self.active = False self.active = False
while True: while True:
@@ -513,6 +502,7 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
self.log.info("TX frame: %s", frame) self.log.info("TX frame: %s", frame)
frame.normalize() frame.normalize()
self.active = True self.active = True
frame_offset = 0
if frame and not self.pause: if frame and not self.pause:
tdata_val = 0 tdata_val = 0
@@ -523,13 +513,14 @@ class AxiStreamSource(AxiStreamBase, AxiStreamPause):
tuser_val = 0 tuser_val = 0
for offset in range(self.byte_lanes): for offset in range(self.byte_lanes):
tdata_val |= (frame.tdata.pop(0) & self.byte_mask) << (offset * self.byte_size) tdata_val |= (frame.tdata[frame_offset] & self.byte_mask) << (offset * self.byte_size)
tkeep_val |= (frame.tkeep.pop(0) & 1) << offset tkeep_val |= (frame.tkeep[frame_offset] & 1) << offset
tid_val = frame.tid.pop(0) tid_val = frame.tid[frame_offset]
tdest_val = frame.tdest.pop(0) tdest_val = frame.tdest[frame_offset]
tuser_val = frame.tuser.pop(0) tuser_val = frame.tuser[frame_offset]
frame_offset += 1
if len(frame.tdata) == 0: if frame_offset >= len(frame.tdata):
tlast_val = 1 tlast_val = 1
frame.sim_time_end = get_sim_time() frame.sim_time_end = get_sim_time()
frame.handle_tx_complete() frame.handle_tx_complete()

View File

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

View File

@@ -85,7 +85,7 @@ async def run_test_write(dut, idle_inserter=None, backpressure_inserter=None, si
tb = TB(dut) tb = TB(dut)
byte_width = tb.axi_master.write_if.byte_width byte_lanes = tb.axi_master.write_if.byte_lanes
max_burst_size = tb.axi_master.write_if.max_burst_size max_burst_size = tb.axi_master.write_if.max_burst_size
if size is None: if size is None:
@@ -96,8 +96,8 @@ async def run_test_write(dut, idle_inserter=None, backpressure_inserter=None, si
tb.set_idle_generator(idle_inserter) tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter) tb.set_backpressure_generator(backpressure_inserter)
for length in list(range(1, byte_width*2))+[1024]: for length in list(range(1, byte_lanes*2))+[1024]:
for offset in list(range(byte_width))+list(range(4096-byte_width, 4096)): for offset in list(range(byte_lanes))+list(range(4096-byte_lanes, 4096)):
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)]) test_data = bytearray([x % 256 for x in range(length)])
@@ -120,7 +120,7 @@ async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None, siz
tb = TB(dut) tb = TB(dut)
byte_width = tb.axi_master.write_if.byte_width byte_lanes = tb.axi_master.write_if.byte_lanes
max_burst_size = tb.axi_master.write_if.max_burst_size max_burst_size = tb.axi_master.write_if.max_burst_size
if size is None: if size is None:
@@ -131,8 +131,8 @@ async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None, siz
tb.set_idle_generator(idle_inserter) tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter) tb.set_backpressure_generator(backpressure_inserter)
for length in list(range(1, byte_width*2))+[1024]: for length in list(range(1, byte_lanes*2))+[1024]:
for offset in list(range(byte_width))+list(range(4096-byte_width, 4096)): for offset in list(range(byte_lanes))+list(range(4096-byte_lanes, 4096)):
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)]) test_data = bytearray([x % 256 for x in range(length)])
@@ -151,12 +151,12 @@ async def run_test_write_words(dut):
tb = TB(dut) tb = TB(dut)
byte_width = tb.axi_master.write_if.byte_width byte_lanes = tb.axi_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
for length in list(range(1, 4)): for length in list(range(1, 4)):
for offset in list(range(byte_width)): for offset in list(range(byte_lanes)):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
@@ -205,12 +205,12 @@ async def run_test_read_words(dut):
tb = TB(dut) tb = TB(dut)
byte_width = tb.axi_master.write_if.byte_width byte_lanes = tb.axi_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
for length in list(range(1, 4)): for length in list(range(1, 4)):
for offset in list(range(byte_width)): for offset in list(range(byte_lanes)):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
@@ -299,8 +299,8 @@ def cycle_pause():
if cocotb.SIM_NAME: if cocotb.SIM_NAME:
data_width = len(cocotb.top.axi_wdata) data_width = len(cocotb.top.axi_wdata)
byte_width = data_width // 8 byte_lanes = data_width // 8
max_burst_size = (byte_width-1).bit_length() max_burst_size = (byte_lanes-1).bit_length()
for test in [run_test_write, run_test_read]: for test in [run_test_write, run_test_read]:

View File

@@ -82,15 +82,15 @@ async def run_test_write(dut, data_in=None, idle_inserter=None, backpressure_ins
tb = TB(dut) tb = TB(dut)
byte_width = tb.axil_master.write_if.byte_width byte_lanes = tb.axil_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
tb.set_idle_generator(idle_inserter) tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter) tb.set_backpressure_generator(backpressure_inserter)
for length in range(1, byte_width*2): for length in range(1, byte_lanes*2):
for offset in range(byte_width): for offset in range(byte_lanes):
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)]) test_data = bytearray([x % 256 for x in range(length)])
@@ -113,15 +113,15 @@ async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inse
tb = TB(dut) tb = TB(dut)
byte_width = tb.axil_master.write_if.byte_width byte_lanes = tb.axil_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
tb.set_idle_generator(idle_inserter) tb.set_idle_generator(idle_inserter)
tb.set_backpressure_generator(backpressure_inserter) tb.set_backpressure_generator(backpressure_inserter)
for length in range(1, byte_width*2): for length in range(1, byte_lanes*2):
for offset in range(byte_width): for offset in range(byte_lanes):
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)]) test_data = bytearray([x % 256 for x in range(length)])
@@ -140,12 +140,12 @@ async def run_test_write_words(dut):
tb = TB(dut) tb = TB(dut)
byte_width = tb.axil_master.write_if.byte_width byte_lanes = tb.axil_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
for length in list(range(1, 4)): for length in list(range(1, 4)):
for offset in list(range(byte_width)): for offset in list(range(byte_lanes)):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000
@@ -194,12 +194,12 @@ async def run_test_read_words(dut):
tb = TB(dut) tb = TB(dut)
byte_width = tb.axil_master.write_if.byte_width byte_lanes = tb.axil_master.write_if.byte_lanes
await tb.cycle_reset() await tb.cycle_reset()
for length in list(range(1, 4)): for length in list(range(1, 4)):
for offset in list(range(byte_width)): for offset in list(range(byte_lanes)):
tb.log.info("length %d, offset %d", length, offset) tb.log.info("length %d, offset %d", length, offset)
addr = offset+0x1000 addr = offset+0x1000