Share the same software routiines to configure system in both cocotb simulation and actual hardware #3615
-
|
I was wondering about the best way to reuse hardware configuration software routines for both cocotb simulation and actual hardware configuration and testing. In my case, the system is configured exclusively through a memory-mapped interface (AXI-4). Therefore, the cocotbext-axi AXIMaster object would be passed as an argument to these software routines for cocotb simulations. Similarly, an AXIMaster object carrying read and write methods pointing to My hesitation starts from the fact that the cocotb simulation is based on async coroutines invoked using the I assume other users already did something similar. Would anyone like to share some tips or an example of how this can be done? |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 10 replies
-
|
I am sharing a bit more details to see if the question becomes more clear. Imagine one has a low-level software featuring thousands of lines using sequential coding, which is represented in a simplistic way in function def shared_software(interface_obj):
# assert reset
interface_obj.write(0x0,0x1)
interface_obj.write(0x0,0x0)The question is how one can import the same For configuring the hardware, the Please see how the cocotb testbench would look like: @cocotb.coroutine
async def chip2chip_test(dut):
# Starting clock and AXI-4 master
cocotb.start_soon(Clock(dut.ref_clk, 3.571, units="ns").start(start_high=True))
axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.s_aclk, dut.s_aresetn,
reset_active_level=False, max_burst_len=1)
# Creating a sequential wrapper for AXI master
sequential_axi_master = some_wrapper_class(axi_master)
# Calling the same low-level software that run in the embedded system
shared_software(sequential_axi_master)In essence, this low-level software is sequential, so no parallelism is needed other than making sure the other threads initiated before, e.g. clock, keep running during the simulation. I am aware one can do it using something like |
Beta Was this translation helpful? Give feedback.
-
|
I was looking for a solution for an almost identical problem. After a lot of experimentation I came up with a solution. The test code should not directly execute your sequential application code. Instead run this in a new thread. Furthermore the sequential thread should not directly change the dut. Instead convey requests/responses through queue's. See below for a simplified example: import cocotb
import threading
import queue
req_q = queue.Queue()
rsp_q = queue.Queue()
async def sim_driver(dut):
while True:
await cocotb.triggers.FallingEdge(dut.s_axi_aclk)
if not req_q.empty():
req = req_q.get()
# do something depending on req
if req["op"] == "axi":
dut.driver_axi_request.value = 1
await cocotb.triggers.RisingEdge(dut.driver_axi_ready)
dut.driver_axi_request.value = 0
rsp_q.put(None)
elif req["op"] == "stop":
break
def app_task():
for n in range(4):
req_q.put({"op": "axi"})
x = rsp_q.get()
req_q.put({"op": "stop"})
@cocotb.test()
def system_test(dut):
app_thread = threading.Thread(target=app_task)
app_thread.start()
drv_task = cocotb.start_soon(sim_driver(dut))
yield drv_task.join()
app_thread.join() |
Beta Was this translation helpful? Give feedback.
-
|
That answers the question. I tested it and works perfectly. Many thanks @marlonjames and @ktbarrett ! |
Beta Was this translation helpful? Give feedback.
You should be able to accomplish this with
cocotb.externalandcocotb.functiondecorators.where any functions called in
shared_softwarethat need to "yield" time to the simulator are decorated withcocotb.function:A caveat with this approach:
cocotb.externalruns the function in a separate thread each time it is called. If you need to run lots of external functions there will be overhead from the thread creation and management.