Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

axi_master: Support generator for requests #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 147 additions & 15 deletions cocotbext/axi/axi_master.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_le
self.b_channel.queue_occupancy_limit = 2

self.write_command_queue = Queue()
self.generator = None
self.generator_done_cb = None
self.command_available = Event()
self.current_write_command = None

self.id_count = 2**len(self.aw_channel.bus.awid)
Expand Down Expand Up @@ -270,8 +273,10 @@ def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_le

self._init_reset(reset, reset_active_level)

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):
def _prepare_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):
'''Validate a write request from init_write() or generator, and return AxiWriteCmd object'''

if event is None:
event = Event()
Expand Down Expand Up @@ -328,17 +333,42 @@ def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=Non
else:
wuser = list(wuser)

self.in_flight_operations += 1
cmd = AxiWriteCmd(address, bytes(data), awid, burst, size, lock,
cache, prot, qos, region, user, wuser, event)

return cmd

def generate_writes(self, generator, done_callback=None):
'''Supplied generator will be used for write requests until it is exhausted, at which
time done_callback (if any) will be called.
Any discrete writes from init_write() and co. will be sent before the next generator write.
The generator must yield a tuple (address, data) or (address, data, params)
where params is a dict with keys and values mapping to optional arguments to init_write()
'''
self.generator = generator
self.generator_done_cb = done_callback
self.command_available.set()
self._idle.clear()

cmd = AxiWriteCmd(address, bytes(data), awid, burst, size, lock,
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):

cmd = self._prepare_write(address, data, awid, burst, size, lock,
cache, prot, qos, region, user, wuser, event)

self.write_command_queue.put_nowait(cmd)
self.command_available.set()
self._idle.clear()

return event
return cmd.event

def idle(self):
return not self.in_flight_operations
if (self.write_command_queue.qsize() == 0
and self.generator is None
and self.in_flight_operations == 0):
return True
else:
return False

async def wait(self):
while not self.idle():
Expand Down Expand Up @@ -421,7 +451,10 @@ def flush_cmd(cmd):
self.cur_id = 0
self.active_id = Counter()

self.generator = None
self.generator_done_cb = None # Should this be called?
self.in_flight_operations = 0
self.command_available.clear()
self._idle.set()
else:
self.log.info("Reset de-asserted")
Expand All @@ -430,10 +463,44 @@ def flush_cmd(cmd):
if self._process_write_resp_cr is None:
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())

async def _get_next_write(self):
cmd = None
while cmd is None:
callback = None

# prioritise queued writes over generated
if not self.write_command_queue.empty():
cmd = self.write_command_queue.get_nowait()
elif self.generator is not None:
args = next(self.generator, None)
if args is None:
# generator exhausted
self.generator = None
callback = self.generator_done_cb
self.generator_done_cb = None
else:
address, data = args[:2]
kwargs = args[2] if len(args) > 2 else {}
cmd = self._prepare_write(address, data, **kwargs)

if cmd is None:
# empty queue and no generator or it's exhausted. wait for command source
self.command_available.clear()

# callback may set new generator or queue writes causing immediate wakeup
# is it possible to transition into the idle state here?
if callback is not None:
callback()

await self.command_available.wait()

return cmd

async def _process_write(self):
while True:
cmd = await self.write_command_queue.get()
cmd = await self._get_next_write()
self.current_write_command = cmd
self.in_flight_operations += 1

num_bytes = 2**cmd.size

Expand Down Expand Up @@ -585,7 +652,7 @@ async def _process_write_resp_id(self, context, cmd):

self.in_flight_operations -= 1

if self.in_flight_operations == 0:
if self.idle():
self._idle.set()


Expand All @@ -607,6 +674,9 @@ def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_le
self.r_channel.queue_occupancy_limit = 2

self.read_command_queue = Queue()
self.generator = None
self.generator_done_cb = None
self.command_available = Event()
self.current_read_command = None

self.id_count = 2**len(self.ar_channel.bus.arid)
Expand Down Expand Up @@ -662,7 +732,7 @@ def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_le

self._init_reset(reset, reset_active_level)

def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
def _prepare_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):

if event is None:
Expand Down Expand Up @@ -710,16 +780,35 @@ def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=No
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
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)

return cmd

def generate_reads(self, generator, done_callback=None):
self.generator = generator
self.generator_done_cb = done_callback
self.command_available.set()
self._idle.clear()

cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
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):

cmd = self._prepare_read(address, length, arid, burst, size,
lock, cache, prot, qos, region, user, event)

self.read_command_queue.put_nowait(cmd)
self.command_available.set()
self._idle.clear()

return event
return cmd.event

def idle(self):
return not self.in_flight_operations
if (self.read_command_queue.qsize() == 0
and self.generator is None
and self.in_flight_operations == 0):
return True
else:
return False

async def wait(self):
while not self.idle():
Expand Down Expand Up @@ -801,7 +890,10 @@ def flush_cmd(cmd):
self.cur_id = 0
self.active_id = Counter()

self.generator = None
self.generator_done_cb = None # Should this be called?
self.in_flight_operations = 0
self.command_available.clear()
self._idle.set()
else:
self.log.info("Reset de-asserted")
Expand All @@ -810,10 +902,44 @@ def flush_cmd(cmd):
if self._process_read_resp_cr is None:
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())

async def _get_next_read(self):
cmd = None
while cmd is None:
callback = None

# prioritise queued requests over generated
if not self.read_command_queue.empty():
cmd = self.read_command_queue.get_nowait()
elif self.generator is not None:
args = next(self.generator, None)
if args is None:
# generator exhausted
self.generator = None
callback = self.generator_done_cb
self.generator_done_cb = None
else:
address, length = args[:2]
kwargs = args[2] if len(args) > 2 else {}
cmd = self._prepare_read(address, length, **kwargs)

if cmd is None:
# empty queue and no generator or it's exhausted. wait for command source
self.command_available.clear()

# callback may set new generator or queue writes causing immediate wakeup
# is it possible to transition into the idle state here?
if callback is not None:
callback()

await self.command_available.wait()

return cmd

async def _process_read(self):
while True:
cmd = await self.read_command_queue.get()
cmd = await self._get_next_read()
self.current_read_command = cmd
self.in_flight_operations += 1

num_bytes = 2**cmd.size

Expand Down Expand Up @@ -965,7 +1091,7 @@ async def _process_read_resp_id(self, context, cmd):

self.in_flight_operations -= 1

if self.in_flight_operations == 0:
if self.idle():
self._idle.set()


Expand All @@ -985,6 +1111,12 @@ def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=Non
cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None):
return self.write_if.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)

def generate_reads(self, generator, done_callback=None):
return self.read_if.generate_reads(generator, done_callback)

def generate_writes(self, generator, done_callback=None):
return self.write_if.generate_writes(generator, done_callback)

def idle(self):
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())

Expand Down