class Stream: def __init__(self, dut, name): self.valid = dut.__getattr__(name + "_valid") self.ready = dut.__getattr__(name + "_ready") self.payload = Bundle(dut, name + "_payload") # Event self.event_ready = Event() self.event_valid = Event() def startMonitoringReady(self, clk): self.clk = clk self.fork_ready = cocotb.fork(self.monitor_ready()) def startMonitoringValid(self, clk): self.clk = clk self.fork_valid = cocotb.fork(self.monitor_valid()) def stopMonitoring(self): self.fork_ready.kill() self.fork_valid.kill() @cocotb.coroutine def monitor_ready(self): while True: yield RisingEdge(self.clk) if int(self.ready) == 1: self.event_ready.set(self.payload) @cocotb.coroutine def monitor_valid(self): while True: yield RisingEdge(self.clk) if int(self.valid) == 1: self.event_valid.set(self.payload)
async def test_event_set_schedule(dut): """ Test that Event.set() doesn't cause an immediate reschedule. The coroutine waiting with Event.wait() will be woken after the current coroutine awaits a trigger. """ e = Event() waiter_scheduled = False async def waiter(event): await event.wait() nonlocal waiter_scheduled waiter_scheduled = True cocotb.fork(waiter(e)) e.set() # waiter() shouldn't run until after test awaits a trigger assert waiter_scheduled is False await NullTrigger() assert waiter_scheduled is True
class ResponseQueue(UVMQueue): """ Returns either the next response or the item with the id. """ def __init__(self, maxsize: int = 0): super().__init__(maxsize=maxsize) self.put_event = CocotbEvent("put event") def put_nowait(self, item): super().put_nowait(item) self.put_event.set() self.put_event.clear() async def get_response(self, txn_id=None): if txn_id is None: return await self.get() else: while True: item_list = list(self._queue) txn_list = [ xx for xx in item_list if xx.transaction_id == txn_id ] if len(txn_list) == 0: await self.put_event.wait() else: assert len(txn_list) == 1, \ f"Multiple transactionsn have the same ID: {txn_id}" _ = self._queue.index(txn_list[0]) self._queue.remove(txn_list[0]) return txn_list[0] def __str__(self): return str([str(xx) for xx in self.queue])
class SfifoMonitor(WrRdDriverMonitor): ''' Monitors the reading interface of a powlib_sfifo. ''' def __init__(self, *args, **kwargs): ''' This constructor simply creates the state and event objects needed to implement the read method defined for the monitor. ''' self.__state = "stopped" self.__evt = Event() WrRdDriverMonitor.__init__(self, *args, **kwargs) def start(self): ''' Indicate to the read method it should start its operation. ''' self.__evt.set() def stop(self): ''' After one more read, stop further read transactions. ''' self.__state = "stopped" @coroutine def read(self): ''' Implements the behavior of the monitor's read method. Initially, the monitor is in a stopped state, until the user decides to start it. Once started, the read method will call the drivers read method that sets the rdrdy signal and set the state of the monitor to started. While in its started state, the monitor will call the driver's read method, without any arguments, indicating it should simply read whenever the rdvld is high. ''' if self.__state == "stopped": ''' Stopped state indicates the monitor is waiting. ''' yield self.driver.read(rdrdy=0) yield self.__evt.wait() self.__evt.clear() self.__state = "started" yield self.cycle() value = yield self.driver.read(rdrdy=1) raise ReturnValue(value) elif self.__state == "started": ''' Once started, the monitor will continue to read valid data from the read interface. ''' value = yield self.driver.read() raise ReturnValue(value) else: raise ValueError("Unknown state occurred.")
async def test_event_is_set(dut): e = Event() assert not e.is_set() e.set() assert e.is_set() e.clear() assert not e.is_set()
class TestBench: ## dut = design under test def __init__(self, dut, num_executions): self.dut = dut self.num_executions = num_executions self.in_vals = Event( ) # Event to communicate Driver and ReferenceModel self.out_vals = Event( ) # Event to communicate ReferenceModel and Checker self.ref_equal = 0 @cocotb.coroutine def Driver(self): self.dut.rst <= 1 yield RisingEdge(self.dut.clk) yield RisingEdge(self.dut.clk) self.dut.rst <= 0 vals = rand_vals(2**SIGNAL_LENGTH) for _ in range(self.num_executions): vals.randomize() # Randomize values of the vectors self.dut.signal_to_delay <= vals.signal_to_delay self.dut.signal_delayed <= vals.signal_delayed self.in_vals.set((vals.signal_to_delay, vals.signal_delayed)) yield RisingEdge(self.dut.clk) sample_coverage(vals) @cocotb.coroutine def ReferenceModel(self): ref_signal_to_delay_delayed_1 = 0 ref_signal_to_delay_delayed_2 = 0 ref_signal_to_delay_delayed_3 = 0 for _ in range(self.num_executions): yield self.in_vals.wait() self.ref_equal = ( ref_signal_to_delay_delayed_3 == self.in_vals.data[1]) ref_signal_to_delay_delayed_3 = ref_signal_to_delay_delayed_2 ref_signal_to_delay_delayed_2 = ref_signal_to_delay_delayed_1 ref_signal_to_delay_delayed_1 = self.in_vals.data[0] self.out_vals.set(self.ref_equal) self.in_vals.clear() @cocotb.coroutine def Checker(self, ref_th): last_ref_equal = False for _ in range(self.num_executions): yield self.out_vals.wait() cocotb.log.info(" ref_equal={0}, dut_equal={1}".format( last_ref_equal, int(self.dut.equal.value))) assert self.dut.equal == last_ref_equal last_ref_equal = self.out_vals.data self.out_vals.clear() @cocotb.coroutine def run(self): cocotb.fork(self.Driver()) ref_th = cocotb.fork(self.ReferenceModel()) yield cocotb.fork(self.Checker(ref_th)).join()
class test_locker(object): def __init__(self): self.in_event = None self.out_event = Event() self.result = None def set_in(self): self.in_event.set() def set_out(self): self.out_event.set()
class ObjectionHandler(metaclass=Singleton): """ This singleton accepts objections and then allows them to be removed. It returns True to run_phase_complete() when there are no objections left. """ def __init__(self): self.__objections = {} self._objection_event = Event("objection changed") self.objection_raised = False self.run_phase_done_flag = None # used in test suites self.printed_warning = False def __str__(self): ss = f"run_phase complete: {self.run_phase_complete()}\n" ss += "Current Objections:\n" for cc in self.__objections: ss += f"{self.__objections[cc]}\n" return ss def clear(self): if len(self.__objections) != 0: logging.warning("Clearing objections raised by %s", ', '.join(self.__objections.values())) self.__objections = {} self.objection_raised = False def raise_objection(self, raiser): name = raiser.get_full_name() self.__objections[name] = self.__objections.setdefault(name, 0) + 1 self.objection_raised = True self._objection_event.clear() def drop_objection(self, dropper): name = dropper.get_full_name() try: self.__objections[name] -= 1 except KeyError: self.objection_raised = True pass if self.__objections[name] == 0: del self.__objections[name] self._objection_event.set() async def run_phase_complete(self): # Allow the run_phase coros to get scheduled and raise objections: await NullTrigger() if self.objection_raised: await self._objection_event.wait() else: logging.warning( "You did not call self.raise_objection() in any run_phase")
async def _send( self, transaction: Any, callback: Callable[[Any], Any], event: Event, sync: bool = True, **kwargs ) -> None: """Send coroutine. Args: transaction: The transaction to be sent. callback: Optional function to be called when the transaction has been sent. event: event to be set when the transaction has been sent. sync: Synchronize the transfer by waiting for a rising edge. **kwargs: Any additional arguments used in child class' :any:`_driver_send` method. """ await self._driver_send(transaction, sync=sync, **kwargs) # Notify the world that this transaction is complete if event: event.set() if callback: callback(transaction)
class Flow: #========================================================================== # Constructor #========================================================================== def __init__(self, dut, name): # interface self.valid = dut.__getattr__(name + "_valid") self.payload = Bundle(dut, name + "_payload") # Event self.event_valid = Event() #========================================================================== # Start to monitor the valid signal #========================================================================== def startMonitoringValid(self, clk): self.clk = clk self.fork_valid = cocotb.fork(self.monitor_valid()) #========================================================================== # Stop monitoring #========================================================================== def stopMonitoring(self): self.fork_valid.kill() #========================================================================== # Monitor the valid signal #========================================================================== @cocotb.coroutine def monitor_valid(self): while True: yield RisingEdge(self.clk) if int(self.valid) == 1: self.event_valid.set(self.payload)
class test_drv(BusDriver): _signals = ["A", "B", "X"] _optional_signals= [] def __init__(self, entity, name, clock): BusDriver.__init__(self, entity, name, clock) self.bus.A.setimmediatevalue(5) self.bus.B.setimmediatevalue(5) self.log.debug("Test DrvM created") self.busy_event = Event("%s_busy" % name) self.busy = False @cocotb.coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @cocotb.coroutine def write(self, value_a, value_b, sync=True): """ """ yield self._acquire_lock() if sync: yield RisingEdge(self.clock) self.bus.A <= value_a self.bus.B <= value_b self._release_lock()
class UsPcieSource(UsPcieBase): _signal_widths = {"tvalid": 1, "tready": 1} _valid_signal = "tvalid" _ready_signal = "tready" _transaction_obj = UsPcieTransaction _frame_obj = UsPcieFrame def __init__(self, bus, clock, reset=None, *args, **kwargs): super().__init__(bus, clock, reset, *args, **kwargs) self.drive_obj = None self.drive_sync = Event() self.queue_occupancy_limit_bytes = -1 self.queue_occupancy_limit_frames = -1 self.bus.tdata.setimmediatevalue(0) self.bus.tvalid.setimmediatevalue(0) self.bus.tlast.setimmediatevalue(0) self.bus.tkeep.setimmediatevalue(0) self.bus.tuser.setimmediatevalue(0) self._init() cocotb.fork(self._run_source()) cocotb.fork(self._run()) async def _drive(self, obj): if self.drive_obj is not None: self.drive_sync.clear() await self.drive_sync.wait() self.drive_obj = obj async def send(self, frame): while self.full(): self.dequeue_event.clear() await self.dequeue_event.wait() frame = UsPcieFrame(frame) await self.queue.put(frame) self.idle_event.clear() self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 def send_nowait(self, frame): if self.full(): raise QueueFull() frame = UsPcieFrame(frame) self.queue.put_nowait(frame) self.idle_event.clear() self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 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): return self.empty() and not self.active async def wait(self): await self.idle_event.wait() async def _run_source(self): self.active = False while True: await RisingEdge(self.clock) # read handshake signals tready_sample = self.bus.tready.value tvalid_sample = self.bus.tvalid.value if self.reset is not None and self.reset.value: self.active = False self.bus.tdata <= 0 self.bus.tvalid <= 0 self.bus.tlast <= 0 self.bus.tkeep <= 0 self.bus.tuser <= 0 continue if (tready_sample and tvalid_sample) or not tvalid_sample: if self.drive_obj and not self.pause: self.bus.drive(self.drive_obj) self.drive_obj = None self.drive_sync.set() self.bus.tvalid <= 1 self.active = True else: self.bus.tvalid <= 0 self.active = bool(self.drive_obj) if not self.drive_obj: self.idle_event.set() async def _run(self): raise NotImplementedError async def _get_frame(self): frame = await self.queue.get() self.dequeue_event.set() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame def _get_frame_nowait(self): frame = self.queue.get_nowait() self.dequeue_event.set() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame
class OPBMaster(BusDriver): """ On-chip peripheral bus master """ _signals = ["xferAck", "errAck", "toutSup", "retry", "DBus_out", "select", "RNW", "BE", "ABus", "DBus_in"] _optional_signals = ["seqAddr"] _max_cycles = 16 def __init__(self, entity, name, clock): BusDriver.__init__(self, entity, name, clock) self.bus.select.setimmediatevalue(0) self.log.debug("OPBMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False @cocotb.coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @cocotb.coroutine def read(self, address, sync=True): """ Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. """ yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.ABus <= address self.bus.select <= 1 self.bus.RNW <= 1 self.bus.BE <= 0xF count = 0 while not int(self.bus.xferAck.value): yield RisingEdge(self.clock) yield ReadOnly() if int(self.bus.toutSup.value): count = 0 else: count += 1 if count >= self._max_cycles: raise OPBException("Read took longer than 16 cycles") data = int(self.bus.DBus_out.value) # Deassert read self.bus.select <= 0 self._release_lock() self.log.info("Read of address 0x%x returned 0x%08x" % (address, data)) raise ReturnValue(data) @cocotb.coroutine def write(self, address, value, sync=True): """ """ yield self._acquire_lock() if sync: yield RisingEdge(self.clock) self.bus.ABus <= address self.bus.select <= 1 self.bus.RNW <= 0 self.bus.BE <= 0xF self.bus.DBus_out <= value count = 0 while not int(self.bus.xferAck.value): yield RisingEdge(self.clock) yield ReadOnly() if int(self.bus.toutSup.value): count = 0 else: count += 1 if count >= self._max_cycles: raise OPBException("Write took longer than 16 cycles") self.bus.select <= 0 self._release_lock()
class AvalonMaster(AvalonMM): """Avalon Memory Mapped Interface (Avalon-MM) Master.""" def __init__(self, entity, name, clock, **kwargs): AvalonMM.__init__(self, entity, name, clock, **kwargs) self.log.debug("AvalonMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False def __len__(self): return 2**len(self.bus.address) @coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @coroutine def read(self, address, sync=True): """Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. Args: address (int): The address to read from. sync (bool, optional): Wait for rising edge on clock initially. Defaults to True. Returns: BinaryValue: The read data value. Raises: :any:`TestError`: If master is write-only. """ if not self._can_read: self.log.error("Cannot read - have no read signal") raise TestError("Attempt to read on a write-only AvalonMaster") yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.address <= address self.bus.read <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): yield self._wait_for_nsignal(self.bus.waitrequest) yield RisingEdge(self.clock) # Deassert read self.bus.read <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v if hasattr(self.bus, "readdatavalid"): while True: yield ReadOnly() if int(self.bus.readdatavalid): break yield RisingEdge(self.clock) else: # Assume readLatency = 1 if no readdatavalid # FIXME need to configure this, # should take a dictionary of Avalon properties. yield ReadOnly() # Get the data data = self.bus.readdata.value self._release_lock() raise ReturnValue(data) @coroutine def write(self, address, value): """Issue a write to the given address with the specified value. Args: address (int): The address to write to. value (int): The data value to write. Raises: :any:`TestError`: If master is read-only. """ if not self._can_write: self.log.error("Cannot write - have no write signal") raise TestError("Attempt to write on a read-only AvalonMaster") yield self._acquire_lock() # Apply values to bus yield RisingEdge(self.clock) self.bus.address <= address self.bus.writedata <= value self.bus.write <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): count = yield self._wait_for_nsignal(self.bus.waitrequest) # Deassert write yield RisingEdge(self.clock) self.bus.write <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v v = self.bus.writedata.value v.binstr = "x" * len(self.bus.writedata) self.bus.writedata <= v self._release_lock()
class UVMEventBase(UVMObject): #const static string type_name = "uvm_event_base" type_name = "uvm_event_base" # Function: new # Creates a new event object. def __init__(self, name=""): UVMObject.__init__(self, name) self.m_event = Event() self.num_waiters = 0 self.on = False self.on_event = Event("on_event_" + name) self.trigger_time = 0 self.callbacks = UVMQueue() # uvm_event_callback self.m_waiters = 0 self.m_value_changed_event = Event("value_changed_event_" + name) # endfunction def set_value(self, key, value): setattr(self, key, value) self.m_value_changed_event.set() def set(self): if self.m_waiters > 0: self.on_event.set() @cocotb.coroutine def wait(self): if self.m_waiters == 0: self.on_event.clear() self.m_waiters += 1 yield self.on_event.wait() self.m_waiters -= 1 if self.m_waiters == 0: self.on_event.clear() # else-branch needed with Timer(0) # //---------// # // waiting // # //---------// # # // Task: wait_on # // # // Waits for the event to be activated for the first time. # // # // If the event has already been triggered, this task returns immediately. # // If ~delta~ is set, the caller will be forced to wait a single delta #0 # // before returning. This prevents the caller from returning before # // previously waiting processes have had a chance to resume. # // # // Once an event has been triggered, it will be remain "on" until the event # // is <reset>. # virtual task wait_on (bit delta = 0) @cocotb.coroutine def wait_on(self, delta=False): if self.on is True: if delta is True: #0 yield Timer(0) yield Timer(0) return self.num_waiters += 1 yield wait(lambda: self.on is True, self.m_value_changed_event) #while True: # yield self.m_value_changed_event.wait() # self.m_value_changed_event.clear() # if self.on is True: # break # endtask # // Task: wait_off # // # // If the event has already triggered and is "on", this task waits for the # // event to be turned "off" via a call to <reset>. # // # // If the event has not already been triggered, this task returns immediately. # // If ~delta~ is set, the caller will be forced to wait a single delta #0 # // before returning. This prevents the caller from returning before # // previously waiting processes have had a chance to resume. # # virtual task wait_off (bit delta = 0) @cocotb.coroutine def wait_off(self, delta=False): if self.on is False: if delta is True: yield Timer(0) #0 return self.num_waiters += 1 yield wait(lambda: self.on is False, self.m_value_changed_event) # endtask # // Task: wait_trigger # // # // Waits for the event to be triggered. # // # // If one process calls wait_trigger in the same delta as another process # // calls <uvm_event#(T)::trigger>, a race condition occurs. If the call to wait occurs # // before the trigger, this method will return in this delta. If the wait # // occurs after the trigger, this method will not return until the next # // trigger, which may never occur and thus cause deadlock. # @cocotb.coroutine def wait_trigger(self): self.num_waiters += 1 yield self.m_event.wait() self.m_event.clear() # endtask # // Task: wait_ptrigger # // # // Waits for a persistent trigger of the event. Unlike <wait_trigger>, this # // views the trigger as persistent within a given time-slice and thus avoids # // certain race conditions. If this method is called after the trigger but # // within the same time-slice, the caller returns immediately. @cocotb.coroutine def wait_ptrigger(self): if self.m_event.fired: return self.num_waiters += 1 yield self.m_event.wait() self.m_event.clear() # // Function: get_trigger_time # // # // Gets the time that this event was last triggered. If the event has not bee # // triggered, or the event has been reset, then the trigger time will be 0. # def get_trigger_time(self): return self.trigger_time # //-------// # // state // # //-------// # // Function: is_on # // # // Indicates whether the event has been triggered since it was last reset. # // # // A return of 1 indicates that the event has triggered. # def is_on(self): return self.on # // Function: is_off # // # // Indicates whether the event has been triggered or been reset. # // # // A return of 1 indicates that the event has not been triggered. # def is_off(self): return self.on is False # // Function: reset # // # // Resets the event to its off state. If ~wakeup~ is set, then all processes # // currently waiting for the event are activated before the reset. # // # // No self.callbacks are called during a reset. # def reset(self, wakeup=False): if wakeup is True: self.m_event.set() self.m_event = Event() self.num_waiters = 0 self.set_value("on", False) self.trigger_time = 0
class AvalonMaster(AvalonMM): """Avalon Memory Mapped Interface (Avalon-MM) Master""" def __init__(self, entity, name, clock, **kwargs): AvalonMM.__init__(self, entity, name, clock, **kwargs) self.log.debug("AvalonMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False def __len__(self): return 2**len(self.bus.address) @coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @coroutine def read(self, address, sync=True): """Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. Args: address (int): The address to read from. sync (bool, optional): Wait for rising edge on clock initially. Defaults to True. Returns: BinaryValue: The read data value. Raises: :any:`TestError`: If master is write-only. """ if not self._can_read: self.log.error("Cannot read - have no read signal") raise TestError("Attempt to read on a write-only AvalonMaster") yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.address <= address self.bus.read <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): yield self._wait_for_nsignal(self.bus.waitrequest) yield RisingEdge(self.clock) # Deassert read self.bus.read <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v if hasattr(self.bus, "readdatavalid"): while True: yield ReadOnly() if int(self.bus.readdatavalid): break yield RisingEdge(self.clock) else: # Assume readLatency = 1 if no readdatavalid # FIXME need to configure this, # should take a dictionary of Avalon properties. yield ReadOnly() # Get the data data = self.bus.readdata.value self._release_lock() raise ReturnValue(data) @coroutine def write(self, address, value): """Issue a write to the given address with the specified value. Args: address (int): The address to write to. value (int): The data value to write. Raises: :any:`TestError`: If master is read-only. """ if not self._can_write: self.log.error("Cannot write - have no write signal") raise TestError("Attempt to write on a read-only AvalonMaster") yield self._acquire_lock() # Apply values to bus yield RisingEdge(self.clock) self.bus.address <= address self.bus.writedata <= value self.bus.write <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): count = yield self._wait_for_nsignal(self.bus.waitrequest) # Deassert write yield RisingEdge(self.clock) self.bus.write <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v v = self.bus.writedata.value v.binstr = "x" * len(self.bus.writedata) self.bus.writedata <= v self._release_lock()
class AxiStreamBase(Reset): _signals = ["tdata"] _optional_signals = [ "tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser" ] _type = "base" _init_x = False _valid_init = None _ready_init = None def __init__(self, bus, clock, reset=None, reset_active_level=True, byte_size=None, byte_lanes=None, *args, **kwargs): self.bus = bus self.clock = clock self.reset = reset self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") self.log.info("AXI stream %s", self._type) self.log.info("cocotbext-axi version %s", __version__) self.log.info("Copyright (c) 2020 Alex Forencich") self.log.info("https://github.com/alexforencich/cocotbext-axi") super().__init__(*args, **kwargs) self.active = False self.queue = Queue() self.dequeue_event = Event() self.current_frame = None self.idle_event = Event() self.idle_event.set() self.active_event = Event() self.queue_occupancy_bytes = 0 self.queue_occupancy_frames = 0 self.width = len(self.bus.tdata) self.byte_lanes = 1 if self._valid_init is not None and hasattr(self.bus, "tvalid"): self.bus.tvalid.setimmediatevalue(self._valid_init) if self._ready_init is not None and hasattr(self.bus, "tready"): self.bus.tready.setimmediatevalue(self._ready_init) for sig in self._signals + self._optional_signals: if hasattr(self.bus, sig): if self._init_x and sig not in ("tvalid", "tready"): v = getattr(self.bus, sig).value v.binstr = 'x' * len(v) getattr(self.bus, sig).setimmediatevalue(v) if hasattr(self.bus, "tkeep"): self.byte_lanes = len(self.bus.tkeep) if byte_size is not None or byte_lanes is not None: raise ValueError( "Cannot specify byte_size or byte_lanes if tkeep is connected" ) else: if byte_lanes is not None: self.byte_lanes = byte_lanes if byte_size is not None: raise ValueError( "Cannot specify both byte_size and byte_lanes") elif byte_size is not None: self.byte_lanes = self.width // byte_size self.byte_size = self.width // self.byte_lanes self.byte_mask = 2**self.byte_size - 1 self.log.info("AXI stream %s configuration:", self._type) 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( " 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( " tlast: %s", "present" if hasattr(self.bus, "tlast") else "not present") if hasattr(self.bus, "tkeep"): self.log.info(" tkeep width: %d bits", len(self.bus.tkeep)) else: self.log.info(" tkeep: not present") 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: raise ValueError( f"Bus does not evenly divide into byte lanes " f"({self.byte_lanes} * {self.byte_size} != {self.width})") self._run_cr = None self._init_reset(reset, reset_active_level) def count(self): return self.queue.qsize() def empty(self): return self.queue.empty() def clear(self): while not self.queue.empty(): frame = self.queue.get_nowait() frame.sim_time_end = None frame.handle_tx_complete() self.dequeue_event.set() self.idle_event.set() self.active_event.clear() self.queue_occupancy_bytes = 0 self.queue_occupancy_frames = 0 def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._run_cr is not None: self._run_cr.kill() self._run_cr = None self.active = False if self.queue.empty(): self.idle_event.set() else: self.log.info("Reset de-asserted") if self._run_cr is None: self._run_cr = cocotb.fork(self._run()) async def _run(self): raise NotImplementedError()
class WishboneMaster(Wishbone): """Wishbone master """ _acked_ops = 0 # ack cntr. comp with opbuf len. wait for equality before releasing lock _res_buf = [] # save readdata/ack/err _aux_buf = [] # save read/write order _op_cnt = 0 # number of ops we've been issued _clk_cycle_count = 0 _timeout = None def __init__(self, entity, name, clock, timeout=5000): Wishbone.__init__(self, entity, name, clock) sTo = ", no cycle timeout" if not (timeout is None): sTo = ", cycle timeout is %u clockcycles" % timeout self.log.info("Wishbone Master created%s" % sTo) self.busy_event = Event("%s_busy" % name) self.busy = False self._timeout = timeout @coroutine def _clk_cycle_counter(self): """ Cycle counter to time bus operations """ clkedge = RisingEdge(self.clock) self._clk_cycle_count = 0 while self.busy: yield clkedge self._clk_cycle_count += 1 @coroutine def _open_cycle(self): #Open new wishbone cycle if self.busy: self.log.error("Opening Cycle, but WB Driver is already busy. Someting's wrong") yield self.busy_event.wait() self.busy_event.clear() self.busy = True cocotb.fork(self._read()) cocotb.fork(self._clk_cycle_counter()) self.bus.cyc <= 1 self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self.log.debug("Opening cycle, %u Ops" % self._op_cnt) @coroutine def _close_cycle(self): #Close current wishbone cycle clkedge = RisingEdge(self.clock) count = 0 last_acked_ops = 0 #Wait for all Operations being acknowledged by the slave before lowering the cycle line #This is not mandatory by the bus standard, but a crossbar might send acks to the wrong master #if we don't wait. We don't want to risk that, it could hang the bus while self._acked_ops < self._op_cnt: if last_acked_ops != self._acked_ops: self.log.debug("Waiting for missing acks: %u/%u" % (self._acked_ops, self._op_cnt) ) last_acked_ops = self._acked_ops #check for timeout when finishing the cycle count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure("Timeout of %u clock cycles reached when waiting for reply from slave" % self._timeout) yield clkedge self.busy = False self.busy_event.set() self.bus.cyc <= 0 self.log.debug("Closing cycle") yield clkedge @coroutine def _wait_stall(self): """Wait for stall to be low before continuing """ clkedge = RisingEdge(self.clock) count = 0 if hasattr(self.bus, "stall"): count = 0 while self.bus.stall.getvalue(): yield clkedge count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure("Timeout of %u clock cycles reached when on stall from slave" % self._timeout) self.log.debug("Stalled for %u cycles" % count) raise ReturnValue(count) @coroutine def _wait_ack(self): """Wait for ACK on the bus before continuing """ #wait for acknownledgement before continuing - Classic Wishbone without pipelining clkedge = RisingEdge(self.clock) count = 0 if not hasattr(self.bus, "stall"): while not self._get_reply(): yield clkedge count += 1 self.log.debug("Waited %u cycles for ackknowledge" % count) raise ReturnValue(count) def _get_reply(self): #helper function for slave acks tmpAck = int(self.bus.ack.getvalue()) tmpErr = 0 tmpRty = 0 if hasattr(self.bus, "err"): tmpErr = int(self.bus.err.getvalue()) if hasattr(self.bus, "rty"): tmpRty = int(self.bus.rty.getvalue()) #check if more than one line was raised if ((tmpAck + tmpErr + tmpRty) > 1): raise TestFailure("Slave raised more than one reply line at once! ACK: %u ERR: %u RTY: %u" % (tmpAck, tmpErr, tmpRty)) #return 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY. use 'replyTypes' Dict for lookup return (tmpAck + 2 * tmpErr + 3 * tmpRty) @coroutine def _read(self): """ Reader for slave replies """ count = 0 clkedge = RisingEdge(self.clock) while self.busy: reply = self._get_reply() # valid reply? if(bool(reply)): datrd = int(self.bus.datrd.getvalue()) #append reply and meta info to result buffer tmpRes = wbr(ack=reply, sel=None, adr=None, datrd=datrd, datwr=None, waitIdle=None, waitStall=None, waitAck=self._clk_cycle_count) self._res_buf.append(tmpRes) self._acked_ops += 1 yield clkedge count += 1 @coroutine def _drive(self, we, adr, datwr, sel, idle): """ Drive the Wishbone Master Out Lines """ clkedge = RisingEdge(self.clock) if self.busy: # insert requested idle cycles if idle != None: idlecnt = idle while idlecnt > 0: idlecnt -= 1 yield clkedge # drive outputs self.bus.stb <= 1 self.bus.adr <= adr self.bus.sel <= sel self.bus.datwr <= datwr self.bus.we <= we yield clkedge #deal with flow control (pipelined wishbone) stalled = yield self._wait_stall() #append operation and meta info to auxiliary buffer self._aux_buf.append(wba(sel, adr, datwr, stalled, idle, self._clk_cycle_count)) #reset strobe and write enable without advancing time self.bus.stb <= 0 self.bus.we <= 0 # non pipelined wishbone yield self._wait_ack() else: self.log.error("Cannot drive the Wishbone bus outside a cycle!") @coroutine def send_cycle(self, arg): """ The main sending routine Args: list(WishboneOperations) """ cnt = 0 clkedge = RisingEdge(self.clock) yield clkedge if is_sequence(arg): if len(arg) < 1: self.log.error("List contains no operations to carry out") else: self._op_cnt = len(arg) firstword = True for op in arg: if firstword: firstword = False result = [] yield self._open_cycle() if op.dat != None: we = 1 dat = op.dat else: we = 0 dat = 0 yield self._drive(we, op.adr, dat, op.sel, op.idle) self.log.debug("#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE: %3u" % (cnt, we, op.adr, dat, op.sel, op.idle)) cnt += 1 yield self._close_cycle() #do pick and mix from result- and auxiliary buffer so we get all operation and meta info for res, aux in zip(self._res_buf, self._aux_buf): res.datwr = aux.datwr res.sel = aux.sel res.adr = aux.adr res.waitIdle = aux.waitIdle res.waitStall = aux.waitStall res.waitack -= aux.ts result.append(res) raise ReturnValue(result) else: self.log.error("Expecting a list") raise ReturnValue(None)
class AD9361(BusDriver): ''' classdocs ''' def __init__(self, dut, rx_channels=1, tx_channels=1, tx_clock_half_period=16276, rx_clock_half_period=16276, loopback_queue_maxlen=16): ''' Constructor ''' self.dut = dut self.tx_clock_half_period = tx_clock_half_period self.rx_clock_half_period = rx_clock_half_period self.rx_frame_asserted = False self.tx_frame_asserted = False self.lbqi = deque() self.lbqq = deque() cocotb.fork(self._rx_clock()) self.got_tx = Event("Got tx event") @cocotb.coroutine def _rx_clock(self): t = Timer(self.rx_clock_half_period) while True: self.dut.rx_clk_in_p <= 1 self.dut.rx_clk_in_n <= 0 yield t self.dut.rx_clk_in_p <= 0 self.dut.rx_clk_in_n <= 1 yield t def send_data(self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): print binaryRepresentation cocotb.fork(self.rx_data_to_ad9361(i_data, q_data, i_data2, q_data2, binaryRepresentation)) @cocotb.coroutine def rx_data_to_ad9361(self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): i_bin_val = BinaryValue(bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) q_bin_val = BinaryValue(bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) index = 0 if i_data2 is None and q_data2 is None: while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if index < len(i_data): i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) index += 1 else: return self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] else: I_SEND_HIGH = True Q_SEND_HIGH = True channel = 1 while True: yield RisingEdge(self.dut.rx_clk_in_p) if I_SEND_HIGH: self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] I_SEND_HIGH = False if channel == 1: self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 elif channel == 2: self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] I_SEND_HIGH = True yield RisingEdge(self.dut.rx_clk_in_n) if Q_SEND_HIGH: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] Q_SEND_HIGH = False else: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] Q_SEND_HIGH = True if index < len(i_data): if channel == 1: i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) channel = 2 elif channel == 2: i_bin_val.set_value(i_data2[index]) q_bin_val.set_value(q_data2[index]) channel = 1 index += 1 else: return @cocotb.coroutine def _tx_data_from_ad9361(self): i_bin_val = BinaryValue(bits=12, bigEndian=False) q_bin_val = BinaryValue(bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.tx_clk_out_p) if self.dut.tx_frame_out_p.value.integer == 1: q_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: q_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() yield RisingEdge(self.dut.tx_clk_out_n) if self.dut.tx_frame_out_p.value.integer == 1: i_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: i_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() # print "i_data",i_bin_val.get_value() # print "q_data",q_bin_val.get_value() self.lbqi.append(i_bin_val) self.lbqq.append(q_bin_val) self.got_tx.set([i_bin_val, q_bin_val]) @cocotb.coroutine def _ad9361_tx_to_rx_loopback(self): cocotb.fork(self._tx_data_from_ad9361()) i_bin_val = BinaryValue(bits=12, bigEndian=False) q_bin_val = BinaryValue(bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if len(self.lbqi) > 0: i_bin_val = self.lbqi.popleft() else: i_bin_val.set_value(0) if len(self.lbqq) > 0: q_bin_val = self.lbqq.popleft() else: q_bin_val.set_value(0) self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] def ad9361_tx_to_rx_loopback(self): cocotb.fork(self._ad9361_tx_to_rx_loopback()) def tx_data_from_ad9361(self): cocotb.fork(self._tx_data_from_ad9361())
class AD9361(BusDriver): """Driver for the AD9361 RF Transceiver.""" def __init__(self, dut, rx_channels=1, tx_channels=1, tx_clock_half_period=16276, rx_clock_half_period=16276, loopback_queue_maxlen=16): self.dut = dut self.tx_clock_half_period = tx_clock_half_period self.rx_clock_half_period = rx_clock_half_period self.rx_frame_asserted = False self.tx_frame_asserted = False self.lbqi = deque() self.lbqq = deque() cocotb.fork(self._rx_clock()) self.got_tx = Event("Got tx event") @cocotb.coroutine def _rx_clock(self): t = Timer(self.rx_clock_half_period) while True: self.dut.rx_clk_in_p <= 1 self.dut.rx_clk_in_n <= 0 yield t self.dut.rx_clk_in_p <= 0 self.dut.rx_clk_in_n <= 1 yield t def send_data(self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): """Forks the ``rx_data_to_ad9361`` coroutine to send data. Args: i_data (int): Data of the I0 channel. q_data (int): Data of the Q0 channel. i_data2 (int, optional): Data of the I1 channel. q_data2 (int, optional): Data of the Q1 channel. binaryRepresentation (BinaryRepresentation): The representation of the binary value. Default is :any:`TWOS_COMPLEMENT`. """ print(binaryRepresentation) cocotb.fork(self.rx_data_to_ad9361(i_data, q_data, i_data2, q_data2, binaryRepresentation)) @cocotb.coroutine def rx_data_to_ad9361(self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): """Receive data to AD9361. This is a coroutine. Args: i_data (int): Data of the I0 channel. q_data (int): Data of the Q0 channel. i_data2 (int, optional): Data of the I1 channel. q_data2 (int, optional): Data of the Q1 channel. binaryRepresentation (BinaryRepresentation): The representation of the binary value. Default is :any:`TWOS_COMPLEMENT`. """ i_bin_val = BinaryValue(n_bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) q_bin_val = BinaryValue(n_bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) index = 0 if i_data2 is None and q_data2 is None: while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if index < len(i_data): i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) index += 1 else: return self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] else: I_SEND_HIGH = True Q_SEND_HIGH = True channel = 1 while True: yield RisingEdge(self.dut.rx_clk_in_p) if I_SEND_HIGH: self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] I_SEND_HIGH = False if channel == 1: self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 elif channel == 2: self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] I_SEND_HIGH = True yield RisingEdge(self.dut.rx_clk_in_n) if Q_SEND_HIGH: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] Q_SEND_HIGH = False else: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] Q_SEND_HIGH = True if index < len(i_data): if channel == 1: i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) channel = 2 elif channel == 2: i_bin_val.set_value(i_data2[index]) q_bin_val.set_value(q_data2[index]) channel = 1 index += 1 else: return @cocotb.coroutine def _tx_data_from_ad9361(self): i_bin_val = BinaryValue(n_bits=12, bigEndian=False) q_bin_val = BinaryValue(n_bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.tx_clk_out_p) if self.dut.tx_frame_out_p.value.integer == 1: q_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: q_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() yield RisingEdge(self.dut.tx_clk_out_n) if self.dut.tx_frame_out_p.value.integer == 1: i_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: i_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() # print("i_data",i_bin_val.get_value()) # print("q_data",q_bin_val.get_value()) self.lbqi.append(i_bin_val) self.lbqq.append(q_bin_val) self.got_tx.set([i_bin_val, q_bin_val]) @cocotb.coroutine def _ad9361_tx_to_rx_loopback(self): cocotb.fork(self._tx_data_from_ad9361()) i_bin_val = BinaryValue(n_bits=12, bigEndian=False) q_bin_val = BinaryValue(n_bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if len(self.lbqi) > 0: i_bin_val = self.lbqi.popleft() else: i_bin_val.set_value(0) if len(self.lbqq) > 0: q_bin_val = self.lbqq.popleft() else: q_bin_val.set_value(0) self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] def ad9361_tx_to_rx_loopback(self): """Create loopback from tx to rx. Forks a coroutine doing the actual task. """ cocotb.fork(self._ad9361_tx_to_rx_loopback()) def tx_data_from_ad9361(self): """Transmit data from AD9361. Forks a coroutine doing the actual task. """ cocotb.fork(self._tx_data_from_ad9361())
class Scheduler(object): """The main scheduler. Here we accept callbacks from the simulator and schedule the appropriate coroutines. A callback fires, causing the :any:`react` method to be called, with the trigger that caused the callback as the first argument. We look up a list of coroutines to schedule (indexed by the trigger) and schedule them in turn. NB implementors should not depend on the scheduling order! Some additional management is required since coroutines can return a list of triggers, to be scheduled when any one of the triggers fires. To ensure we don't receive spurious callbacks, we have to un-prime all the other triggers when any one fires. Due to the simulator nuances and fun with delta delays we have the following modes: Normal mode - Callbacks cause coroutines to be scheduled - Any pending writes are cached and do not happen immediately ReadOnly mode - Corresponds to cbReadOnlySynch (VPI) or vhpiCbLastKnownDeltaCycle (VHPI). In this state we are not allowed to perform writes. Write mode - Corresponds to cbReadWriteSynch (VPI) or vhpiCbEndOfProcesses (VHPI) In this mode we play back all the cached write updates. We can legally transition from normal->write by registering a ReadWrite callback, however usually once a simulator has entered the ReadOnly phase of a given timestep then we must move to a new timestep before performing any writes. The mechanism for moving to a new timestep may not be consistent across simulators and therefore we provide an abstraction to assist with compatibility. Unless a coroutine has explicitly requested to be scheduled in ReadOnly mode (for example wanting to sample the finally settled value after all delta delays) then it can reasonably be expected to be scheduled during "normal mode" i.e. where writes are permitted. """ _MODE_NORMAL = 1 # noqa _MODE_READONLY = 2 # noqa _MODE_WRITE = 3 # noqa _MODE_TERM = 4 # noqa # Singleton events, recycled to avoid spurious object creation _readonly = ReadOnly() _timer1 = Timer(1) def __init__(self): self.log = SimLog("cocotb.scheduler") if _debug: self.log.setLevel(logging.DEBUG) # Use OrderedDict here for deterministic behavior (gh-934) # A dictionary of pending coroutines for each trigger, # indexed by trigger self._trigger2coros = collections.OrderedDict() # A dictionary mapping coroutines to the trigger they are waiting for self._coro2trigger = collections.OrderedDict() # Our main state self._mode = Scheduler._MODE_NORMAL # A dictionary of pending writes self._writes = collections.OrderedDict() self._pending_coros = [] self._pending_triggers = [] self._pending_threads = [] self._pending_events = [] # Events we need to call set on once we've unwound self._terminate = False self._test_result = None self._entrypoint = None self._main_thread = threading.current_thread() self._is_reacting = False self._write_coro_inst = None self._writes_pending = Event() @cocotb.decorators.coroutine def _do_writes(self): """ An internal coroutine that performs pending writes """ while True: yield self._writes_pending.wait() if self._mode != Scheduler._MODE_NORMAL: yield NextTimeStep() yield ReadWrite() while self._writes: handle, value = self._writes.popitem() handle.setimmediatevalue(value) self._writes_pending.clear() def _check_termination(self): """ Handle a termination that causes us to move onto the next test. """ if self._terminate: if _debug: self.log.debug("Test terminating, scheduling Timer") if self._write_coro_inst is not None: self._write_coro_inst.kill() self._write_coro_inst = None for t in self._trigger2coros: t.unprime() if self._timer1.primed: self._timer1.unprime() self._timer1.prime(self.begin_test) self._trigger2coros = collections.OrderedDict() self._coro2trigger = collections.OrderedDict() self._terminate = False self._writes = collections.OrderedDict() self._writes_pending.clear() self._mode = Scheduler._MODE_TERM def begin_test(self, trigger=None): """Called to initiate a test. Could be called on start-up or from a callback. """ if _debug: self.log.debug("begin_test called with trigger: %s" % (str(trigger))) if _profiling: ps = pstats.Stats(_profile).sort_stats('cumulative') ps.dump_stats("test_profile.pstat") ctx = profiling_context() else: ctx = nullcontext() with ctx: self._mode = Scheduler._MODE_NORMAL if trigger is not None: trigger.unprime() # Issue previous test result, if there is one if self._test_result is not None: if _debug: self.log.debug("Issue test result to regression object") cocotb.regression_manager.handle_result(self._test_result) self._test_result = None if self._entrypoint is not None: test = self._entrypoint self._entrypoint = None self.schedule(test) self._check_termination() def react(self, trigger): """ Called when a trigger fires. We ensure that we only start the event loop once, rather than letting it recurse. """ if self._is_reacting: # queue up the trigger, the event loop will get to it self._pending_triggers.append(trigger) return assert not self._pending_triggers # start the event loop self._is_reacting = True try: self._event_loop(trigger) finally: self._is_reacting = False def _event_loop(self, trigger): """ Run an event loop triggered by the given trigger. The loop will keep running until no further triggers fire. This should be triggered by only: * The beginning of a test, when there is no trigger to react to * A GPI trigger """ if _profiling: ctx = profiling_context() else: ctx = nullcontext() with ctx: # When a trigger fires it is unprimed internally if _debug: self.log.debug("Trigger fired: %s" % str(trigger)) # trigger.unprime() if self._mode == Scheduler._MODE_TERM: if _debug: self.log.debug("Ignoring trigger %s since we're terminating" % str(trigger)) return if trigger is self._readonly: self._mode = Scheduler._MODE_READONLY # Only GPI triggers affect the simulator scheduling mode elif isinstance(trigger, GPITrigger): self._mode = Scheduler._MODE_NORMAL # work through triggers one by one is_first = True self._pending_triggers.append(trigger) while self._pending_triggers: trigger = self._pending_triggers.pop(0) if not is_first and isinstance(trigger, GPITrigger): self.log.warning( "A GPI trigger occurred after entering react - this " "should not happen." ) assert False # this only exists to enable the warning above is_first = False if trigger not in self._trigger2coros: # GPI triggers should only be ever pending if there is an # associated coroutine waiting on that trigger, otherwise it would # have been unprimed already if isinstance(trigger, GPITrigger): self.log.critical( "No coroutines waiting on trigger that fired: %s" % str(trigger)) trigger.log.info("I'm the culprit") # For Python triggers this isn't actually an error - we might do # event.set() without knowing whether any coroutines are actually # waiting on this event, for example elif _debug: self.log.debug( "No coroutines waiting on trigger that fired: %s" % str(trigger)) continue # Scheduled coroutines may append to our waiting list so the first # thing to do is pop all entries waiting on this trigger. scheduling = self._trigger2coros.pop(trigger) if _debug: debugstr = "\n\t".join([coro.__name__ for coro in scheduling]) if len(scheduling): debugstr = "\n\t" + debugstr self.log.debug("%d pending coroutines for event %s%s" % (len(scheduling), str(trigger), debugstr)) # This trigger isn't needed any more trigger.unprime() for coro in scheduling: if _debug: self.log.debug("Scheduling coroutine %s" % (coro.__name__)) self.schedule(coro, trigger=trigger) if _debug: self.log.debug("Scheduled coroutine %s" % (coro.__name__)) # Schedule may have queued up some events so we'll burn through those while self._pending_events: if _debug: self.log.debug("Scheduling pending event %s" % (str(self._pending_events[0]))) self._pending_events.pop(0).set() # no more pending triggers self._check_termination() if _debug: self.log.debug("All coroutines scheduled, handing control back" " to simulator") def unschedule(self, coro): """Unschedule a coroutine. Unprime any pending triggers""" # Unprime the trigger this coroutine is waiting on try: trigger = self._coro2trigger.pop(coro) except KeyError: # coroutine probably finished pass else: if coro in self._trigger2coros.setdefault(trigger, []): self._trigger2coros[trigger].remove(coro) if not self._trigger2coros[trigger]: trigger.unprime() del self._trigger2coros[trigger] if Join(coro) in self._trigger2coros: self.react(Join(coro)) else: try: # throws an error if the background coroutine errored # and no one was monitoring it coro.retval except TestComplete as test_result: self.log.debug("TestComplete received: {}".format(test_result.__class__.__name__)) self.finish_test(test_result) except Exception as e: self.finish_test(create_error(self, "Forked coroutine {} raised exception: {}".format(coro, e))) def save_write(self, handle, value): if self._mode == Scheduler._MODE_READONLY: raise Exception("Write to object {0} was scheduled during a read-only sync phase.".format(handle._name)) # TODO: we should be able to better keep track of when this needs to # be scheduled if self._write_coro_inst is None: self._write_coro_inst = self._do_writes() self.schedule(self._write_coro_inst) self._writes[handle] = value self._writes_pending.set() def _coroutine_yielded(self, coro, trigger): """Prime the trigger and update our internal mappings.""" self._coro2trigger[coro] = trigger trigger_coros = self._trigger2coros.setdefault(trigger, []) if coro is self._write_coro_inst: # Our internal write coroutine always runs before any user coroutines. # This preserves the behavior prior to the refactoring of writes to # this coroutine. trigger_coros.insert(0, coro) else: # Everything else joins the back of the queue trigger_coros.append(coro) if not trigger.primed: try: trigger.prime(self.react) except Exception as e: # Convert any exceptions into a test result self.finish_test( create_error(self, "Unable to prime trigger %s: %s" % (str(trigger), str(e)))) def queue(self, coroutine): """Queue a coroutine for execution""" self._pending_coros.append(coroutine) def queue_function(self, coroutine): """Queue a coroutine for execution and move the containing thread so that it does not block execution of the main thread any longer. """ # We should be able to find ourselves inside the _pending_threads list matching_threads = [ t for t in self._pending_threads if t.thread == threading.current_thread() ] if len(matching_threads) == 0: raise RuntimeError("queue_function called from unrecognized thread") # Raises if there is more than one match. This can never happen, since # each entry always has a unique thread. t, = matching_threads t.thread_suspend() self._pending_coros.append(coroutine) return t def run_in_executor(self, func, *args, **kwargs): """Run the coroutine in a separate execution thread and return a yieldable object for the caller. """ # Create a thread # Create a trigger that is called as a result of the thread finishing # Create an Event object that the caller can yield on # Event object set when the thread finishes execution, this blocks the # calling coroutine (but not the thread) until the external completes def execute_external(func, _waiter): _waiter._outcome = outcomes.capture(func, *args, **kwargs) if _debug: self.log.debug("Execution of external routine done %s" % threading.current_thread()) _waiter.thread_done() waiter = external_waiter() thread = threading.Thread(group=None, target=execute_external, name=func.__name__ + "_thread", args=([func, waiter]), kwargs={}) waiter.thread = thread self._pending_threads.append(waiter) return waiter def add(self, coroutine): """Add a new coroutine. Just a wrapper around self.schedule which provides some debug and useful error messages in the event of common gotchas. """ if isinstance(coroutine, cocotb.decorators.coroutine): self.log.critical( "Attempt to schedule a coroutine that hasn't started") coroutine.log.error("This is the failing coroutine") self.log.warning( "Did you forget to add parentheses to the @test decorator?") self._test_result = TestError( "Attempt to schedule a coroutine that hasn't started") self._terminate = True return elif not isinstance(coroutine, cocotb.decorators.RunningCoroutine): self.log.critical( "Attempt to add something to the scheduler which isn't a " "coroutine") self.log.warning( "Got: %s (%s)" % (str(type(coroutine)), repr(coroutine))) self.log.warning("Did you use the @coroutine decorator?") self._test_result = TestError( "Attempt to schedule a coroutine that hasn't started") self._terminate = True return if _debug: self.log.debug("Adding new coroutine %s" % coroutine.__name__) self.schedule(coroutine) self._check_termination() return coroutine def new_test(self, coroutine): self._entrypoint = coroutine def schedule(self, coroutine, trigger=None): """Schedule a coroutine by calling the send method. Args: coroutine (cocotb.decorators.coroutine): The coroutine to schedule. trigger (cocotb.triggers.Trigger): The trigger that caused this coroutine to be scheduled. """ if trigger is None: send_outcome = outcomes.Value(None) else: send_outcome = trigger._outcome if _debug: self.log.debug("Scheduling with {}".format(send_outcome)) try: result = coroutine._advance(send_outcome) if _debug: self.log.debug("Coroutine %s yielded %s (mode %d)" % (coroutine.__name__, str(result), self._mode)) # TestComplete indication is game over, tidy up except TestComplete as test_result: # Tag that close down is needed, save the test_result # for later use in cleanup handler self.log.debug("TestComplete received: %s" % test_result.__class__.__name__) self.finish_test(test_result) return # Normal coroutine completion except cocotb.decorators.CoroutineComplete as exc: if _debug: self.log.debug("Coroutine completed: %s" % str(coroutine)) self.unschedule(coroutine) return # Don't handle the result if we're shutting down if self._terminate: return # convert lists into `First` Waitables. if isinstance(result, list): result = cocotb.triggers.First(*result) # convert waitables into coroutines if isinstance(result, cocotb.triggers.Waitable): result = result._wait() # convert coroutinues into triggers if isinstance(result, cocotb.decorators.RunningCoroutine): if not result.has_started(): self.queue(result) if _debug: self.log.debug("Scheduling nested coroutine: %s" % result.__name__) else: if _debug: self.log.debug("Joining to already running coroutine: %s" % result.__name__) result = result.join() if isinstance(result, Trigger): if _debug: self.log.debug("%s: is instance of Trigger" % result) self._coroutine_yielded(coroutine, result) else: msg = ("Coroutine %s yielded something the scheduler can't handle" % str(coroutine)) msg += ("\nGot type: %s repr: %s str: %s" % (type(result), repr(result), str(result))) msg += "\nDid you forget to decorate with @cocotb.coroutine?" try: raise_error(self, msg) except Exception as e: self.finish_test(e) # We do not return from here until pending threads have completed, but only # from the main thread, this seems like it could be problematic in cases # where a sim might change what this thread is. def unblock_event(ext): @cocotb.coroutine def wrapper(): ext.event.set() yield PythonTrigger() if self._main_thread is threading.current_thread(): for ext in self._pending_threads: ext.thread_start() if _debug: self.log.debug("Blocking from %s on %s" % (threading.current_thread(), ext.thread)) state = ext.thread_wait() if _debug: self.log.debug("Back from wait on self %s with newstate %d" % (threading.current_thread(), state)) if state == external_state.EXITED: self._pending_threads.remove(ext) self._pending_events.append(ext.event) # Handle any newly queued coroutines that need to be scheduled while self._pending_coros: self.add(self._pending_coros.pop(0)) def finish_test(self, test_result): """Cache the test result and set the terminate flag.""" self.log.debug("finish_test called with %s" % (repr(test_result))) if not self._terminate: self._terminate = True self._test_result = test_result self.cleanup() def finish_scheduler(self, test_result): """Directly call into the regression manager and end test once we return the sim will close us so no cleanup is needed. """ self.log.debug("Issue sim closedown result to regression object") cocotb.regression_manager.handle_result(test_result) def cleanup(self): """Clear up all our state. Unprime all pending triggers and kill off any coroutines stop all externals. """ # copy since we modify this in kill items = list(self._trigger2coros.items()) # reversing seems to fix gh-928, although the order is still somewhat # arbitrary. for trigger, waiting in items[::-1]: for coro in waiting: if _debug: self.log.debug("Killing %s" % str(coro)) coro.kill() if self._main_thread is not threading.current_thread(): raise Exception("Cleanup() called outside of the main thread") for ext in self._pending_threads: self.log.warn("Waiting for %s to exit", ext.thread)
class Queue(Generic[T]): """A queue, useful for coordinating producer and consumer coroutines. If *maxsize* is less than or equal to 0, the queue size is infinite. If it is an integer greater than 0, then :meth:`put` will block when the queue reaches *maxsize*, until an item is removed by :meth:`get`. """ def __init__(self, maxsize: int = 0): self._maxsize = maxsize self._finished = Event() self._finished.set() self._getters = collections.deque() self._putters = collections.deque() self._init(maxsize) def _init(self, maxsize): self._queue = collections.deque() def _put(self, item): self._queue.append(item) def _get(self): return self._queue.popleft() def _wakeup_next(self, waiters): while waiters: event, task = waiters.popleft() if not task._finished: event.set() break def __repr__(self): return '<{} {} at {}>'.format( type(self).__name__, self._format(), _pointer_str(self)) def __str__(self): return '<{} {}>'.format(type(self).__name__, self._format()) def __class_getitem__(cls, type): return cls def _format(self): result = 'maxsize={}'.format(repr(self._maxsize)) if getattr(self, '_queue', None): result += ' _queue={}'.format(repr(list(self._queue))) if self._getters: result += ' _getters[{}]'.format(len(self._getters)) if self._putters: result += ' _putters[{}]'.format(len(self._putters)) return result def qsize(self) -> int: """Number of items in the queue.""" return len(self._queue) @property def maxsize(self) -> int: """Number of items allowed in the queue.""" return self._maxsize def empty(self) -> bool: """Return ``True`` if the queue is empty, ``False`` otherwise.""" return not self._queue def full(self) -> bool: """Return ``True`` if there are :meth:`maxsize` items in the queue. .. note:: If the Queue was initialized with ``maxsize=0`` (the default), then :meth:`full` is never ``True``. """ if self._maxsize <= 0: return False else: return self.qsize() >= self._maxsize async def put(self, item: T) -> None: """Put an *item* into the queue. If the queue is full, wait until a free slot is available before adding the item. """ while self.full(): event = Event('{} put'.format(type(self).__name__)) self._putters.append((event, cocotb.scheduler._current_task)) await event.wait() self.put_nowait(item) def put_nowait(self, item: T) -> None: """Put an *item* into the queue without blocking. If no free slot is immediately available, raise :exc:`asyncio.QueueFull`. """ if self.full(): raise QueueFull() self._put(item) self._finished.clear() self._wakeup_next(self._getters) async def get(self) -> T: """Remove and return an item from the queue. If the queue is empty, wait until an item is available. """ while self.empty(): event = Event('{} get'.format(type(self).__name__)) self._getters.append((event, cocotb.scheduler._current_task)) await event.wait() return self.get_nowait() def get_nowait(self) -> T: """Remove and return an item from the queue. Return an item if one is immediately available, else raise :exc:`asyncio.QueueEmpty`. """ if self.empty(): raise QueueEmpty() item = self._get() self._wakeup_next(self._putters) return item
class Monitor(object): def __init__(self, callback=None, event=None): """ Constructor for a monitor instance callback will be called with each recovered transaction as the argument If the callback isn't used, received transactions will be placed on a queue and the event used to notify any consumers. """ self._event = event self._wait_event = None self._recvQ = [] self._callbacks = [] self.stats = MonitorStatistics() self._wait_event = Event() # Subclasses may already set up logging if not hasattr(self, "log"): self.log = SimLog("cocotb.monitor.%s" % (self.__class__.__name__)) if callback is not None: self.add_callback(callback) # Create an independent coroutine which can receive stuff self._thread = cocotb.scheduler.add(self._monitor_recv()) def kill(self): if self._thread: self._thread.kill() self._thread = None def __len__(self): return len(self._recvQ) def __getitem__(self, idx): return self._recvQ[idx] def add_callback(self, callback): self.log.debug("Adding callback of function %s to monitor" % (callback.__name__)) self._callbacks.append(callback) @coroutine def wait_for_recv(self, timeout=None): if timeout: t = Timer(timeout) fired = yield [self._wait_event.wait(), t] if fired is t: raise ReturnValue(None) else: yield self._wait_event.wait() pkt = self._wait_event.data raise ReturnValue(pkt) @coroutine def _monitor_recv(self): """ actual impementation of the receiver subclasses should override this method to implement the actual receive routine and call self._recv() with the recovered transaction """ raise NotImplementedError("Attempt to use base monitor class without " "providing a _monitor_recv method") def _recv(self, transaction): """Common handling of a received transaction.""" self.stats.received_transactions += 1 # either callback based consumer for callback in self._callbacks: callback(transaction) # Or queued with a notification if not self._callbacks: self._recvQ.append(transaction) if self._event is not None: self._event.set() # If anyone was waiting then let them know if self._wait_event is not None: self._wait_event.set(data=transaction) self._wait_event.clear()
class OPBMaster(BusDriver): """On-chip peripheral bus master.""" _signals = [ "xferAck", "errAck", "toutSup", "retry", "DBus_out", "select", "RNW", "BE", "ABus", "DBus_in" ] _optional_signals = ["seqAddr"] _max_cycles = 16 def __init__(self, entity, name, clock, **kwargs): BusDriver.__init__(self, entity, name, clock, **kwargs) self.bus.select.setimmediatevalue(0) self.log.debug("OPBMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False @cocotb.coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @cocotb.coroutine def read(self, address: int, sync: bool = True) -> BinaryValue: """Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. Args: address: The address to read from. sync: Wait for rising edge on clock initially. Defaults to True. Returns: The read data value. Raises: OPBException: If read took longer than 16 cycles. """ yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.ABus <= address self.bus.select <= 1 self.bus.RNW <= 1 self.bus.BE <= 0xF count = 0 while not int(self.bus.xferAck.value): yield RisingEdge(self.clock) yield ReadOnly() if int(self.bus.toutSup.value): count = 0 else: count += 1 if count >= self._max_cycles: raise OPBException("Read took longer than 16 cycles") data = int(self.bus.DBus_out.value) # Deassert read self.bus.select <= 0 self._release_lock() self.log.info("Read of address 0x%x returned 0x%08x" % (address, data)) return data @cocotb.coroutine def write(self, address: int, value: int, sync: bool = True) -> None: """Issue a write to the given address with the specified value. Args: address: The address to read from. value: The data value to write. sync: Wait for rising edge on clock initially. Defaults to True. Raises: OPBException: If write took longer than 16 cycles. """ yield self._acquire_lock() if sync: yield RisingEdge(self.clock) self.bus.ABus <= address self.bus.select <= 1 self.bus.RNW <= 0 self.bus.BE <= 0xF self.bus.DBus_out <= value count = 0 while not int(self.bus.xferAck.value): yield RisingEdge(self.clock) yield ReadOnly() if int(self.bus.toutSup.value): count = 0 else: count += 1 if count >= self._max_cycles: raise OPBException("Write took longer than 16 cycles") self.bus.select <= 0 self._release_lock()
class I2CSlaveModelHAL: def __init__(self, helperMaster ): # initMemory = dict()): self.startEvent = Event() self.stopEvent = Event() self.dataRxEvent = Event() self.dataTXEvent = Event() self.sda_rd = helperMaster.io.sda_wr self.sda_wr = helperMaster.io.sda_rd self.scl_rd = helperMaster.io.scl_wr self.scl_wr = helperMaster.io.scl_rd self.resetn = helperMaster.io.resetn self.clk = helperMaster.io.clk self.sda = 1 self.scl = 1 ########################################################################## # Launch all slave process @cocotb.coroutine def startSlave(self,listOperations): yield RisingEdge(self.clk) self.fork_drain = cocotb.fork(self._manageSDA()) self.fork_start = cocotb.fork(self._startDetection()) self.fork_stop = cocotb.fork(self._stopDetection()) cocotb.fork(self._runSlave(listOperations)) ########################################################################## # Stop all processes def stop(self): self.fork_drain.kill() self.fork_start.kill() self.fork_stop.kill() ########################################################################## # Slave simulation @cocotb.coroutine def _runSlave(self, listOperations): for index in range(0,len(listOperations)): operation = listOperations[index] if isinstance(operation, START): if index != 0: yield FallingEdge(self.scl_wr) yield FallingEdge(self.scl_wr) else: yield FallingEdge(self.scl_wr) elif isinstance(operation, WRITE): yield self._writeData(operation.enCollision) elif isinstance(operation, READ): yield self._readData(operation.data) elif isinstance(operation, ACK): prevOperation = listOperations[index-1] self.sda = 0 if isinstance(prevOperation, WRITE) else 1 yield FallingEdge(self.scl_rd) self.sda = 1 elif isinstance(operation, ACK): self.sda = 1 yield FallingEdge(self.scl_wr) self.sda = 1 elif isinstance(operation, STOP): pass ########################################################################## # Simulate an open drain pin @cocotb.coroutine def _manageSDA(self): while True: yield RisingEdge(self.clk) if int(self.sda_rd) == 0: self.sda_wr <= 0 else: self.sda_wr <= self.sda if int(self.scl_rd) == 0 : self.scl_wr <= 0 else: self.scl_wr <= self.scl ########################################################################## # Detect the start/restart sequence @cocotb.coroutine def _startDetection(self): while True: yield RisingEdge(self.clk) prev = int(self.sda_rd) yield RisingEdge(self.clk) if prev == 1 and int(self.sda_rd) == 0: if int(self.scl_rd) == 1: self.startEvent.set() ########################################################################## # Detect the stop sequence @cocotb.coroutine def _stopDetection(self): while True: yield RisingEdge(self.clk) prev = int(self.sda_rd) yield RisingEdge(self.clk) if prev == 0 and int(self.sda_rd) == 1: if int(self.scl_rd) == 1: self.stopEvent.set() ########################################################################## # Read a data comming from the master @cocotb.coroutine def _writeData(self, enCollision): cnt = 0 dataRead = list() while True: if (cnt == 8): yield FallingEdge(self.scl_wr) dataInt = int("".join([str(x) for x in dataRead]), 2) self.dataTXEvent.set(data= dataInt ) break else: if enCollision == True: yield FallingEdge(self.scl_wr) if int(self.sda_wr) == 1: self.sda = 0 yield RisingEdge(self.scl_wr) dataRead.append( int(self.sda_wr) ) cnt += 1 ########################################################################## # Write a data to the master @cocotb.coroutine def _readData(self, data): write = True cnt = 0 data2Send = bin(data)[2:].zfill(8) self.sda = int(data2Send[0]) cnt += 1 while write: yield FallingEdge(self.scl_rd) self.sda = int(data2Send[cnt]) if (cnt == 7): result = dict() result["data"] = int("".join([str(x) for x in data2Send]), 2) result["ack"] = int(self.sda_rd) self.dataRxEvent.set(result) yield FallingEdge(self.scl_rd) self.sda = 1 break cnt += 1
class WishboneMaster(Wishbone): """ Wishbone master """ def __init__(self, entity, name, clock, timeout=None, width=32): sTo = ", no cycle timeout" if timeout is not None: sTo = ", cycle timeout is %u clock cycles" % timeout self.busy_event = Event("%s_busy" % name) self._timeout = timeout self.busy = False self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self._op_cnt = 0 self._clk_cycle_count = 0 Wishbone.__init__(self, entity, name, clock, width) self.log.info("Wishbone Master created%s" % sTo) @coroutine def _clk_cycle_counter(self): """ Cycle counter to time bus operations """ clkedge = RisingEdge(self.clock) self._clk_cycle_count = 0 while self.busy: yield clkedge self._clk_cycle_count += 1 @coroutine def _open_cycle(self): # Open new wishbone cycle if self.busy: self.log.error( "Opening Cycle, but WB Driver is already busy." ) yield self.busy_event.wait() self.busy_event.clear() self.busy = True cocotb.fork(self._read()) cocotb.fork(self._clk_cycle_counter()) self.bus.cyc <= 1 self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self.log.debug("Opening cycle, %u Ops" % self._op_cnt) @coroutine def _close_cycle(self): # Close current wishbone cycle clkedge = RisingEdge(self.clock) count = 0 last_acked_ops = 0 # Wait for all Operations being acknowledged by the slave # before lowering the cycle line. This is not mandatory by the bus # standard, but a crossbar might send acks to the wrong master if we # don't wait. We don't want to risk that, it could hang the bus while self._acked_ops < self._op_cnt: if last_acked_ops != self._acked_ops: self.log.debug("Waiting for missing acks: %u/%u" % (self._acked_ops, self._op_cnt)) last_acked_ops = self._acked_ops # check for timeout when finishing the cycle count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure( "Timeout of %u clock cycles reached when waiting for" "reply from slave" % self._timeout) yield clkedge self.busy = False self.busy_event.set() self.bus.cyc <= 0 yield clkedge @coroutine def _wait_stall(self): """Wait for stall to be low before continuing (Pipelined Wishbone) """ clkedge = RisingEdge(self.clock) count = 0 if hasattr(self.bus, "stall"): count = 0 while self.bus.stall: yield clkedge count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure( "Timeout of %u clock cycles reached when on stall" "from slave" % self._timeout) self.log.debug("Stalled for %u cycles" % count) raise ReturnValue(count) @coroutine def _wait_ack(self): """Wait for ACK on the bus before continuing (Non pipelined Wishbone) """ # wait for acknownledgement before continuing # Classic Wishbone without pipelining clkedge = RisingEdge(self.clock) count = 0 if not hasattr(self.bus, "stall"): while not self._get_reply(): yield clkedge count += 1 self.log.debug("Waited %u cycles for acknowledge" % count) raise ReturnValue(count) def _get_reply(self): # helper function for slave acks tmpAck = int(self.bus.ack) tmpErr = 0 tmpRty = 0 if hasattr(self.bus, "err"): tmpErr = int(self.bus.err) if hasattr(self.bus, "rty"): tmpRty = int(self.bus.rty) # check if more than one line was raised if ((tmpAck + tmpErr + tmpRty) > 1): raise TestFailure( "Slave raised more than one reply line at once! ACK: %u ERR:" "%u RTY: %u" % (tmpAck, tmpErr, tmpRty)) # return 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY. # use 'replyTypes' Dict for lookup return (tmpAck + 2 * tmpErr + 3 * tmpRty) @coroutine def _read(self): """ Reader for slave replies """ count = 0 clkedge = RisingEdge(self.clock) while self.busy: reply = self._get_reply() # valid reply? if (bool(reply)): datrd = int(self.bus.datrd) # append reply and meta info to result buffer tmpRes = WBRes(ack=reply, sel=None, adr=None, datrd=datrd, datwr=None, waitIdle=None, waitStall=None, waitAck=self._clk_cycle_count) self._res_buf.append(tmpRes) self._acked_ops += 1 yield clkedge count += 1 @coroutine def _drive(self, we, adr, datwr, sel, idle): """ Drive the Wishbone Master Out Lines """ clkedge = RisingEdge(self.clock) if self.busy: # insert requested idle cycles if idle is not None: idlecnt = idle while idlecnt > 0: idlecnt -= 1 yield clkedge # drive outputs self.bus.stb <= 1 self.bus.adr <= adr self.bus.sel <= sel self.bus.datwr <= datwr self.bus.we <= we yield clkedge # deal with flow control (pipelined wishbone) stalled = yield self._wait_stall() # append operation and meta info to auxiliary buffer self._aux_buf.append( WBAux(sel, adr, datwr, stalled, idle, self._clk_cycle_count)) # non pipelined wishbone yield self._wait_ack() # reset strobe and write enable after the acknowledgement was # received. Note that this was different from the original code, # which cleared these values immediately. # It is not clear from the spec whether these values must only be # set on the first cycle, or they must be asserted until the other # side acknowledges. self.bus.stb <= 0 self.bus.we <= 0 else: self.log.error("Cannot drive the Wishbone bus outside a cycle!") @coroutine def send_cycle(self, arg): """ The main sending routine Args: list(WishboneOperations) """ cnt = 0 clkedge = RisingEdge(self.clock) yield clkedge if is_sequence(arg): if len(arg) < 1: self.log.error("List contains no operations to carry out") else: self._op_cnt = len(arg) firstword = True for op in arg: if not isinstance(op, WBOp): raise TestFailure( "Sorry, argument must be a list of WBOp (Wishbone" "Operation) objects!" ) if firstword: firstword = False result = [] yield self._open_cycle() if op.dat is not None: we = 1 dat = op.dat else: we = 0 dat = 0 yield self._drive(we, op.adr, dat, op.sel, op.idle) self.log.debug( "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE:" "%3u" % (cnt, we, op.adr << 2, dat, op.sel, op.idle)) cnt += 1 yield self._close_cycle() # do pick and mix from result- and auxiliary buffer so we get # all operation and meta info for res, aux in zip(self._res_buf, self._aux_buf): res.datwr = aux.datwr res.sel = aux.sel res.adr = aux.adr res.waitIdle = aux.waitIdle res.waitStall = aux.waitStall res.waitAck -= aux.ts result.append(res) raise ReturnValue(result) else: raise TestFailure( "Sorry, argument must be a list of WBOp (Wishbone Operation)" " objects!" ) raise ReturnValue(None) @coroutine def read(self, adr): result = yield self.send_cycle([WBOp(adr >> 2)]) for rec in result: self.log.debug("Result: {}".format(rec)) raise ReturnValue(result[-1].datrd) @coroutine def write(self, adr, data): result = yield self.send_cycle([WBOp(adr >> 2, data)]) for rec in result: self.log.debug("Result: {}".format(rec)) raise ReturnValue(0)
class AvalonMaster(AvalonMM): """Avalon-MM master """ def __init__(self, entity, name, clock): AvalonMM.__init__(self, entity, name, clock) self.log.debug("AvalonMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False @coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @coroutine def read(self, address): """ Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ yield self._acquire_lock() # Apply values for next clock edge yield RisingEdge(self.clock) self.bus.address <= address self.bus.read <= 1 # Wait for waitrequest to be low yield self._wait_for_nsignal(self.bus.waitrequest) # Assume readLatency = 1 # FIXME need to configure this, should take a dictionary of Avalon properties. yield RisingEdge(self.clock) # Deassert read self.bus.read <= 0 # Get the data yield ReadOnly() data = self.bus.readdata.value yield NextTimeStep() self._release_lock() raise ReturnValue(data) @coroutine def write(self, address, value): """ Issue a write to the given address with the specified value. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ yield self._acquire_lock() # Apply valuse to bus yield RisingEdge(self.clock) self.bus.address <= address self.bus.writedata <= value self.bus.write <= 1 # Wait for waitrequest to be low count = yield self._wait_for_nsignal(self.bus.waitrequest) # Deassert write yield RisingEdge(self.clock) self.bus.write <= 0 self._release_lock()
class AXIS_Monitor(BusMonitor): _signals = ["tvalid"] _optional_signals = [ "tdata", "tkeep", "tready", "tlast", "tstrb", "tid", "tdest", "tuser" ] def __init__(self, entity, name, clock, lsb_first=True, tuser_bytewise=False, **kwargs): self._init_done = Event( "Init Done" ) # workaround for scheduler immediately running newly added coroutines BusMonitor.__init__(self, entity, name, clock, **kwargs) if hasattr(self.bus, 'tdata'): self._n_bytes, rem = divmod(len(self.bus.tdata), 8) if rem: raise AttributeError("tdata width has to be multiple of 8") else: self._n_bytes = 1 if hasattr(self.bus, 'tuser'): self._tuser_bytewise = tuser_bytewise if tuser_bytewise and self._n_bytes: self._user_bits, rem = divmod(len(self.bus.tuser), self._n_bytes) if rem: raise AttributeError( "in byte-wise mode tuser width has to be multiple of tdata width" ) else: self._user_bits = len(self.bus.tuser) self._lsb_first = lsb_first self._init_done.set() @coroutine def _monitor_recv(self): """Watch the pins and reconstruct transactions.""" yield self._init_done.wait() # Avoid spurious object creation by recycling clkedge = RisingEdge(self.clock) rdonly = ReadOnly() class _dummy(): def __init__(self, value): self.value = value tlast = getattr(self.bus, 'tlast', None) tready = getattr(self.bus, 'tready', _dummy(BinaryValue('1'))) tkeep = getattr(self.bus, 'tkeep', _dummy(BinaryValue("1" * self._n_bytes))) tstrb = getattr(self.bus, 'tstrb', tkeep) tdata = getattr(self.bus, 'tdata', None) tid = getattr(self.bus, 'tid', None) tdest = getattr(self.bus, 'tdest', None) tuser = getattr(self.bus, 'tuser', None) tvalid = self.bus.tvalid packet_buf = {} while True: yield clkedge yield rdonly if self.in_reset: if packet_buf: self.log.warning( "Discarding unfinished packet(s) as the bus is in reset" ) packet_buf = {} continue if int(tvalid) and int(tready): if self._lsb_first: byte_range = range(self._n_bytes - 1, -1, -1) else: byte_range = range(self._n_bytes) filtered_data = [] filtered_user = [] for b in byte_range: byte_type = resolve(tkeep.value.binstr[b] + tstrb.value.binstr[b]) if byte_type == "11": # data byte if tdata: filtered_data.append( int( resolve(tdata.value.binstr[b * 8:(b + 1) * 8]), 2)) if tuser and self._tuser_bytewise: filtered_user.append( int( resolve( tuser.value.binstr[b * self._user_bits: (b + 1) * self._user_bits]), 2)) elif byte_type == "10": # position byte if tdata: filtered_data.append(0) if tuser and self._tuser_bytewise: filtered_user.append(0) elif byte_type == "01": raise AXIS_ProtocolError( "Invald combination of TKEEP and TSTRB byte qualifiers" ) else: # null byte pass stream_id = (int(tid) if tid else None, int(tdest) if tdest else None) if not tlast or int(tlast): recv_pkt = {} try: if tdata: recv_pkt["data"] = b"".join( packet_buf[stream_id]["data"]) + bytes( filtered_data) if tuser: if self._tuser_bytewise: recv_pkt["user"] = packet_buf[stream_id][ "user"] + filtered_user else: recv_pkt["user"] = resolve(tuser.value.binstr) if tid: recv_pkt["tid"] = int(tid) if tdest: recv_pkt["tdest"] = int(tdest) self._recv(recv_pkt) del packet_buf[stream_id] except KeyError: # No buffered data if tdata: recv_pkt["data"] = bytes(filtered_data) if tuser: if self._tuser_bytewise: recv_pkt["user"] = filtered_user else: recv_pkt["user"] = resolve(tuser.value.binstr) if tid: recv_pkt["tid"] = int(tid) if tdest: recv_pkt["tdest"] = int(tdest) else: try: if tdata: packet_buf[stream_id]["data"].append( bytes(filtered_data)) if tuser and self._tuser_bytewise: packet_buf[stream_id]["user"].extend(filtered_user) except KeyError: packet_buf[stream_id] = {} if tdata: packet_buf[stream_id]["data"] = [ bytes(filtered_data) ] if tuser and self._tuser_bytewise: packet_buf[stream_id]["user"] = filtered_user
class Driver(object): """Class defining the standard interface for a driver within a testbench. The driver is responsible for serialising transactions onto the physical pins of the interface. This may consume simulation time. """ def __init__(self): """Constructor for a driver instance.""" self._pending = Event(name="Driver._pending") self._sendQ = deque() # Subclasses may already set up logging if not hasattr(self, "log"): self.log = SimLog("cocotb.driver.%s" % (self.__class__.__name__)) # Create an independent coroutine which can send stuff self._thread = cocotb.scheduler.add(self._send_thread()) def kill(self): """Kill the coroutine sending stuff.""" if self._thread: self._thread.kill() self._thread = None def append(self, transaction, callback=None, event=None, **kwargs): """Queue up a transaction to be sent over the bus. Mechanisms are provided to permit the caller to know when the transaction is processed. Args: transaction (any): The transaction to be sent. callback (callable, optional): Optional function to be called when the transaction has been sent. event (optional): :class:`~cocotb.triggers.Event` to be set when the transaction has been sent. **kwargs: Any additional arguments used in child class' :any:`_driver_send` method. """ self._sendQ.append((transaction, callback, event, kwargs)) self._pending.set() def clear(self): """Clear any queued transactions without sending them onto the bus.""" self._sendQ = deque() @coroutine def send(self, transaction, sync=True, **kwargs): """Blocking send call (hence must be "yielded" rather than called). Sends the transaction over the bus. Args: transaction (any): The transaction to be sent. sync (bool, optional): Synchronise the transfer by waiting for a rising edge. **kwargs (dict): Additional arguments used in child class' :any:`_driver_send` method. """ yield self._send(transaction, None, None, sync=sync, **kwargs) def _driver_send(self, transaction, sync=True, **kwargs): """Actual implementation of the send. Subclasses should override this method to implement the actual :meth:`~cocotb.drivers.Driver.send` routine. Args: transaction (any): The transaction to be sent. sync (boolean, optional): Synchronise the transfer by waiting for a rising edge. **kwargs: Additional arguments if required for protocol implemented in subclass. """ raise NotImplementedError("Subclasses of Driver should define a " "_driver_send coroutine") @coroutine def _send(self, transaction, callback, event, sync=True, **kwargs): """Send coroutine. Args: transaction (any): The transaction to be sent. callback (callable, optional): Optional function to be called when the transaction has been sent. event (optional): event to be set when the transaction has been sent. sync (boolean, optional): Synchronise the transfer by waiting for a rising edge. **kwargs: Any additional arguments used in child class' :any:`_driver_send` method. """ yield self._driver_send(transaction, sync=sync, **kwargs) # Notify the world that this transaction is complete if event: event.set() if callback: callback(transaction) @coroutine def _send_thread(self): while True: # Sleep until we have something to send while not self._sendQ: self._pending.clear() yield self._pending.wait() synchronised = False # Send in all the queued packets, # only synchronise on the first send while self._sendQ: transaction, callback, event, kwargs = self._sendQ.popleft() self.log.debug("Sending queued packet...") yield self._send(transaction, callback, event, sync=not synchronised, **kwargs) synchronised = True
class WishboneMaster(Wishbone): """ Wishbone master """ def __init__(self, entity, name, clock, timeout=None, width=32, **kwargs): sTo = ", no cycle timeout" if timeout is not None: sTo = ", cycle timeout is %u clockcycles" % timeout self.busy_event = Event("%s_busy" % name) self._timeout = timeout self.busy = False self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self._op_cnt = 0 self._clk_cycle_count = 0 Wishbone.__init__(self, entity, name, clock, width, **kwargs) self.log.info("Wishbone Master created%s" % sTo) async def _clk_cycle_counter(self): """ Cycle counter to time bus operations """ clkedge = RisingEdge(self.clock) self._clk_cycle_count = 0 while self.busy: await clkedge self._clk_cycle_count += 1 async def _open_cycle(self): #Open new wishbone cycle if self.busy: self.log.error( "Opening Cycle, but WB Driver is already busy. Someting's wrong" ) await self.busy_event.wait() print('\n--------\nsarasa') self.busy_event.clear() self.busy = True cocotb.fork(self._read()) cocotb.fork(self._clk_cycle_counter()) self.bus.cyc.value = 1 self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self.log.debug("Opening cycle, %u Ops" % self._op_cnt) async def _close_cycle(self): #Close current wishbone cycle clkedge = RisingEdge(self.clock) count = 0 last_acked_ops = 0 #Wait for all Operations being acknowledged by the slave before lowering the cycle line #This is not mandatory by the bus standard, but a crossbar might send acks to the wrong master #if we don't wait. We don't want to risk that, it could hang the bus while self._acked_ops < self._op_cnt: if last_acked_ops != self._acked_ops: self.log.debug("Waiting for missing acks: %u/%u" % (self._acked_ops, self._op_cnt)) last_acked_ops = self._acked_ops #check for timeout when finishing the cycle count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure( "Timeout of %u clock cycles reached when waiting for reply from slave" % self._timeout) await clkedge self.busy = False self.busy_event.set() self.bus.cyc.value = 0 self.log.debug("Closing cycle") await clkedge async def _wait_stall(self): """Wait for stall to be low before continuing (Pipelined Wishbone) """ clkedge = RisingEdge(self.clock) count = 0 if hasattr(self.bus, "stall"): count = 0 while self.bus.stall.value == 1: await clkedge count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure( "Timeout of %u clock cycles reached when on stall from slave" % self._timeout) self.log.debug("Stalled for %u cycles" % count) return count async def _wait_ack(self): """Wait for ACK on the bus before continuing (Non pipelined Wishbone) """ #wait for acknownledgement before continuing - Classic Wishbone without pipelining clkedge = RisingEdge(self.clock) count = 0 if hasattr(self.bus, "stall"): self.bus.stb.value = 0 if self._acktimeout == 0: while not self._get_reply()[0]: await clkedge count += 1 else: while (not self._get_reply()[0]) and (count < self._acktimeout): await clkedge count += 1 if (self._acktimeout != 0) and (count >= self._acktimeout): raise TestFailure( "Timeout of %u clock cycles reached when waiting for acknowledge" % count) if not hasattr(self.bus, "stall"): self.bus.stb.value = 0 self._acked_ops += 1 self.log.debug("Waited %u cycles for ackknowledge" % count) return count def _get_reply(self): code = 0 # 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY ack = self.bus.ack.value == 1 if ack: code = 1 if hasattr(self.bus, "err") and self.bus.err.value == 1: if ack: raise TestFailure("Slave raised ACK and ERR line") ack = True code = 2 if hasattr(self.bus, "rty") and self.bus.rty.value == 1: if ack: raise TestFailure("Slave raised {} and RTY line".format( "ACK" if code == 1 else "ERR")) ack = True code = 3 return ack, code async def _read(self): """ Reader for slave replies """ count = 0 clkedge = RisingEdge(self.clock) while self.busy: ack, reply = self._get_reply() # valid reply? if ack: datrd = self.bus.datrd.value #append reply and meta info to result buffer tmpRes = WBRes(ack=reply, sel=None, adr=None, datrd=datrd, datwr=None, waitIdle=None, waitStall=None, waitAck=self._clk_cycle_count) self._res_buf.append(tmpRes) self._acked_ops += 1 await clkedge count += 1 async def _drive(self, we, adr, datwr, sel, idle): """ Drive the Wishbone Master Out Lines """ clkedge = RisingEdge(self.clock) if self.busy: # insert requested idle cycles if idle is not None: idlecnt = idle while idlecnt > 0: idlecnt -= 1 await clkedge # drive outputs self.bus.stb.value = 1 self.bus.adr.value = adr if hasattr(self.bus, "sel"): self.bus.sel.value = sel if sel is not None else BinaryValue( "1" * len(self.bus.sel)) self.bus.datwr.value = datwr self.bus.we.value = we await clkedge #deal with flow control (pipelined wishbone) stalled = await self._wait_stall() #append operation and meta info to auxiliary buffer self._aux_buf.append( WBAux(sel, adr, datwr, stalled, idle, self._clk_cycle_count)) await self._wait_ack() self.bus.we.value = 0 else: self.log.error("Cannot drive the Wishbone bus outside a cycle!") async def send_cycle(self, arg): """ The main sending routine Args: list(WishboneOperations) """ cnt = 0 clkedge = RisingEdge(self.clock) await clkedge if is_sequence(arg): self._op_cnt = len(arg) if self._op_cnt < 1: self.log.error("List contains no operations to carry out") else: result = [] await self._open_cycle() for op in arg: if not isinstance(op, WBOp): raise TestFailure( "Sorry, argument must be a list of WBOp (Wishbone Operation) objects!" ) self._acktimeout = op.acktimeout if op.dat is not None: we = 1 dat = op.dat else: we = 0 dat = 0 await self._drive(we, op.adr, dat, op.sel, op.idle) if op.sel is not None: self.log.debug( "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE: %3u" % (cnt, we, op.adr, dat, op.sel, op.idle)) else: self.log.debug( "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: None IDLE: %3u" % (cnt, we, op.adr, dat, op.idle)) cnt += 1 await self._close_cycle() #do pick and mix from result- and auxiliary buffer so we get all operation and meta info for res, aux in zip(self._res_buf, self._aux_buf): res.datwr = aux.datwr res.sel = aux.sel res.adr = aux.adr res.waitIdle = aux.waitIdle res.waitStall = aux.waitStall res.waitAck -= aux.ts result.append(res) return result else: raise TestFailure( "Sorry, argument must be a list of WBOp (Wishbone Operation) objects!" ) return None
class AvalonMaster(AvalonMM): """Avalon-MM master """ def __init__(self, entity, name, clock): AvalonMM.__init__(self, entity, name, clock) self.log.debug("AvalonMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False def __len__(self): return 2**len(self.bus.address) @coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @coroutine def read(self, address, sync=True): """ Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ if not self._can_read: self.log.error("Cannot read - have no read signal") raise TestError("Attempt to read on a write-only AvalonMaster") yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.address <= address self.bus.read <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): yield self._wait_for_nsignal(self.bus.waitrequest) yield RisingEdge(self.clock) # Get the data data = self.bus.readdata.value # Deassert read self.bus.read <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v if hasattr(self.bus, "readdatavalid"): while True: yield ReadOnly() # Get the data if int(self.bus.readdatavalid): data = self.bus.readdata.value break yield RisingEdge(self.clock) else: # Assume readLatency = 1 if no readdatavalid # FIXME need to configure this, # should take a dictionary of Avalon properties. yield ReadOnly() self._release_lock() raise ReturnValue(data) @coroutine def write(self, address, value): """ Issue a write to the given address with the specified value. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ if not self._can_write: self.log.error("Cannot write - have no write signal") raise TestError("Attempt to write on a read-only AvalonMaster") yield self._acquire_lock() # Apply valuse to bus yield RisingEdge(self.clock) self.bus.address <= address self.bus.writedata <= value self.bus.write <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): count = yield self._wait_for_nsignal(self.bus.waitrequest) # Deassert write yield RisingEdge(self.clock) self.bus.write <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v v = self.bus.writedata.value v.binstr = "x" * len(self.bus.writedata) self.bus.writedata <= v self._release_lock()
class StreamBase(Reset): _signals = ["data", "valid", "ready"] _optional_signals = [] _signal_widths = {"valid": 1, "ready": 1} _init_x = False _valid_signal = "valid" _valid_init = None _ready_signal = "ready" _ready_init = None _transaction_obj = StreamTransaction _bus_obj = StreamBus def __init__(self, bus, clock, reset=None, reset_active_level=True, *args, **kwargs): self.bus = bus self.clock = clock self.reset = reset self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") super().__init__(*args, **kwargs) self.active = False self.queue = Queue() self.dequeue_event = Event() self.idle_event = Event() self.idle_event.set() self.active_event = Event() self.ready = None self.valid = None if self._ready_signal is not None and hasattr(self.bus, self._ready_signal): self.ready = getattr(self.bus, self._ready_signal) if self._ready_init is not None: self.ready.setimmediatevalue(self._ready_init) if self._valid_signal is not None and hasattr(self.bus, self._valid_signal): self.valid = getattr(self.bus, self._valid_signal) if self._valid_init is not None: self.valid.setimmediatevalue(self._valid_init) for sig in self._signals + self._optional_signals: if hasattr(self.bus, sig): if sig in self._signal_widths: assert len(getattr(self.bus, sig)) == self._signal_widths[sig] if self._init_x and sig not in (self._valid_signal, self._ready_signal): v = getattr(self.bus, sig).value v.binstr = 'x' * len(v) getattr(self.bus, sig).setimmediatevalue(v) self._run_cr = None self._init_reset(reset, reset_active_level) def count(self): return self.queue.qsize() def empty(self): return self.queue.empty() def clear(self): while not self.queue.empty(): self.queue.get_nowait() self.dequeue_event.set() self.idle_event.set() self.active_event.clear() def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._run_cr is not None: self._run_cr.kill() self._run_cr = None self.active = False if self.queue.empty(): self.idle_event.set() else: self.log.info("Reset de-asserted") if self._run_cr is None: self._run_cr = cocotb.start_soon(self._run()) async def _run(self): raise NotImplementedError()
class Driver(object): """ Class defining the standard interface for a driver within a testbench The driver is responsible for serialising transactions onto the physical pins of the interface. This may consume simulation time. """ def __init__(self): """ Constructor for a driver instance """ # self._busy = Lock() self._pending = Event(name="Driver._pending") self._sendQ = deque() # Subclasses may already set up logging if not hasattr(self, "log"): self.log = SimLog("cocotb.driver.%s" % (self.__class__.__name__)) # Create an independent coroutine which can send stuff self._thread = cocotb.scheduler.add(self._send_thread()) def kill(self): if self._thread: self._thread.kill() self._thread = None def append(self, transaction, callback=None, event=None): """ Queue up a transaction to be sent over the bus. Mechanisms are provided to permit the caller to know when the transaction is processed callback: optional function to be called when the transaction has been sent event: event to be set when the tansaction has been sent """ self._sendQ.append((transaction, callback, event)) self._pending.set() def clear(self): """ Clear any queued transactions without sending them onto the bus """ self._sendQ = deque() @coroutine def send(self, transaction, sync=True): """ Blocking send call (hence must be "yielded" rather than called) Sends the transaction over the bus Args: transaction (any): the transaction to send Kwargs: sync (boolean): synchronise the transfer by waiting for risingedge """ yield self._send(transaction, None, None, sync=sync) def _driver_send(self, transaction, sync=True): """ actual impementation of the send. subclasses should override this method to implement the actual send routine """ raise NotImplementedError("Subclasses of Driver should define a " "_driver_send coroutine") @coroutine def _send(self, transaction, callback, event, sync=True): """ assumes the caller has already acquired the busy lock releases busy lock once sending is complete """ yield self._driver_send(transaction, sync=sync) # Notify the world that this transaction is complete if event: event.set() if callback: callback(transaction) # No longer hogging the bus # self.busy.release() @coroutine def _send_thread(self): while True: # Sleep until we have something to send while not self._sendQ: self._pending.clear() yield self._pending.wait() synchronised = False # Send in all the queued packets, # only synchronise on the first send while self._sendQ: transaction, callback, event = self._sendQ.popleft() self.log.debug("Sending queued packet...") yield self._send(transaction, callback, event, sync=not synchronised) synchronised = True
class AD9361(BusDriver): ''' classdocs ''' def __init__(self, dut, rx_channels=1, tx_channels=1, tx_clock_half_period=16276, rx_clock_half_period=16276, loopback_queue_maxlen=16): ''' Constructor ''' self.dut = dut self.tx_clock_half_period = tx_clock_half_period self.rx_clock_half_period = rx_clock_half_period self.rx_frame_asserted = False self.tx_frame_asserted = False self.lbqi = deque() self.lbqq = deque() cocotb.fork(self._rx_clock()) self.got_tx = Event("Got tx event") @cocotb.coroutine def _rx_clock(self): t = Timer(self.rx_clock_half_period) while True: self.dut.rx_clk_in_p <= 1 self.dut.rx_clk_in_n <= 0 yield t self.dut.rx_clk_in_p <= 0 self.dut.rx_clk_in_n <= 1 yield t def send_data(self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): print(binaryRepresentation) cocotb.fork( self.rx_data_to_ad9361(i_data, q_data, i_data2, q_data2, binaryRepresentation)) @cocotb.coroutine def rx_data_to_ad9361( self, i_data, q_data, i_data2=None, q_data2=None, binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT): i_bin_val = BinaryValue(bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) q_bin_val = BinaryValue(bits=12, bigEndian=False, binaryRepresentation=binaryRepresentation) index = 0 if i_data2 is None and q_data2 is None: while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if index < len(i_data): i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) index += 1 else: return self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] else: I_SEND_HIGH = True Q_SEND_HIGH = True channel = 1 while True: yield RisingEdge(self.dut.rx_clk_in_p) if I_SEND_HIGH: self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] I_SEND_HIGH = False if channel == 1: self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 elif channel == 2: self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] I_SEND_HIGH = True yield RisingEdge(self.dut.rx_clk_in_n) if Q_SEND_HIGH: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] Q_SEND_HIGH = False else: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] Q_SEND_HIGH = True if index < len(i_data): if channel == 1: i_bin_val.set_value(i_data[index]) q_bin_val.set_value(q_data[index]) channel = 2 elif channel == 2: i_bin_val.set_value(i_data2[index]) q_bin_val.set_value(q_data2[index]) channel = 1 index += 1 else: return @cocotb.coroutine def _tx_data_from_ad9361(self): i_bin_val = BinaryValue(bits=12, bigEndian=False) q_bin_val = BinaryValue(bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.tx_clk_out_p) if self.dut.tx_frame_out_p.value.integer == 1: q_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: q_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() yield RisingEdge(self.dut.tx_clk_out_n) if self.dut.tx_frame_out_p.value.integer == 1: i_bin_val[11:6] = self.dut.tx_data_out_p.value.get_binstr() else: i_bin_val[5:0] = self.dut.tx_data_out_p.value.get_binstr() # print("i_data",i_bin_val.get_value()) # print("q_data",q_bin_val.get_value()) self.lbqi.append(i_bin_val) self.lbqq.append(q_bin_val) self.got_tx.set([i_bin_val, q_bin_val]) @cocotb.coroutine def _ad9361_tx_to_rx_loopback(self): cocotb.fork(self._tx_data_from_ad9361()) i_bin_val = BinaryValue(bits=12, bigEndian=False) q_bin_val = BinaryValue(bits=12, bigEndian=False) while True: yield RisingEdge(self.dut.rx_clk_in_p) if self.rx_frame_asserted: self.dut.rx_data_in_p <= i_bin_val[5:0] self.dut.rx_data_in_n <= ~i_bin_val[5:0] self.rx_frame_asserted = False self.dut.rx_frame_in_p <= 0 self.dut.rx_frame_in_n <= 1 else: if len(self.lbqi) > 0: i_bin_val = self.lbqi.popleft() else: i_bin_val.set_value(0) if len(self.lbqq) > 0: q_bin_val = self.lbqq.popleft() else: q_bin_val.set_value(0) self.dut.rx_data_in_p <= i_bin_val[11:6] self.dut.rx_data_in_n <= ~i_bin_val[11:6] self.rx_frame_asserted = True self.dut.rx_frame_in_p <= 1 self.dut.rx_frame_in_n <= 0 yield RisingEdge(self.dut.rx_clk_in_n) if self.rx_frame_asserted: self.dut.rx_data_in_p <= q_bin_val[11:6] self.dut.rx_data_in_n <= ~q_bin_val[11:6] else: self.dut.rx_data_in_p <= q_bin_val[5:0] self.dut.rx_data_in_n <= ~q_bin_val[5:0] def ad9361_tx_to_rx_loopback(self): cocotb.fork(self._ad9361_tx_to_rx_loopback()) def tx_data_from_ad9361(self): cocotb.fork(self._tx_data_from_ad9361())
class Monitor(object): """Base class for Monitor objects. Monitors are passive 'listening' objects that monitor pins going in or out of a DUT. This class should not be used directly, but should be subclassed and the internal :any:`_monitor_recv` method should be overridden and decorated as a :any:`coroutine`. This :any:`_monitor_recv` method should capture some behavior of the pins, form a transaction, and pass this transaction to the internal :any:`_recv` method. The :any:`_monitor_recv` method is added to the cocotb scheduler during the ``__init__`` phase, so it should not be yielded anywhere. The primary use of a Monitor is as an interface for a :class:`~cocotb.scoreboard.Scoreboard`. Args: callback (callable): Callback to be called with each recovered transaction as the argument. If the callback isn't used, received transactions will be placed on a queue and the event used to notify any consumers. event (event): Object that supports a ``set`` method that will be called when a transaction is received through the internal :any:`_recv` method. """ def __init__(self, callback=None, event=None): self._event = event self._wait_event = None self._recvQ = deque() self._callbacks = [] self.stats = MonitorStatistics() self._wait_event = Event() # Subclasses may already set up logging if not hasattr(self, "log"): self.log = SimLog("cocotb.monitor.%s" % (self.__class__.__name__)) if callback is not None: self.add_callback(callback) # Create an independent coroutine which can receive stuff self._thread = cocotb.scheduler.add(self._monitor_recv()) def kill(self): """Kill the monitor coroutine.""" if self._thread: self._thread.kill() self._thread = None def __len__(self): return len(self._recvQ) def __getitem__(self, idx): return self._recvQ[idx] def add_callback(self, callback): """Add function as a callback. Args: callback (callable): The function to call back. """ self.log.debug("Adding callback of function %s to monitor" % (callback.__name__)) self._callbacks.append(callback) @coroutine def wait_for_recv(self, timeout=None): """With *timeout*, :meth:`.wait` for transaction to arrive on monitor and return its data. Args: timeout (optional): The timeout value for :class:`~.triggers.Timer`. Defaults to ``None``. Returns: Data of received transaction. """ if timeout: t = Timer(timeout) fired = yield [self._wait_event.wait(), t] if fired is t: raise ReturnValue(None) else: yield self._wait_event.wait() pkt = self._wait_event.data raise ReturnValue(pkt) @coroutine def _monitor_recv(self): """Actual implementation of the receiver. Subclasses should override this method to implement the actual receive routine and call :any:`_recv` with the recovered transaction. """ raise NotImplementedError("Attempt to use base monitor class without " "providing a ``_monitor_recv`` method") def _recv(self, transaction): """Common handling of a received transaction.""" self.stats.received_transactions += 1 # either callback based consumer for callback in self._callbacks: callback(transaction) # Or queued with a notification if not self._callbacks: self._recvQ.append(transaction) if self._event is not None: self._event.set() # If anyone was waiting then let them know if self._wait_event is not None: self._wait_event.set(data=transaction) self._wait_event.clear()
class UsPcieBase: _signal_widths = {"tvalid": 1, "tready": 1} _valid_signal = "tvalid" _ready_signal = "tready" _transaction_obj = UsPcieTransaction _frame_obj = UsPcieFrame def __init__(self, bus, clock, reset=None, *args, **kwargs): self.bus = bus self.clock = clock self.reset = reset self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") super().__init__(*args, **kwargs) self.active = False self.queue = Queue() self.dequeue_event = Event() self.idle_event = Event() self.idle_event.set() self.active_event = Event() self.pause = False self._pause_generator = None self._pause_cr = None self.queue_occupancy_bytes = 0 self.queue_occupancy_frames = 0 self.width = len(self.bus.tdata) self.byte_lanes = len(self.bus.tkeep) self.byte_size = self.width // self.byte_lanes self.byte_mask = 2**self.byte_size - 1 assert self.width in [64, 128, 256, 512] assert self.byte_size == 32 def _init(self): pass def count(self): return self.queue.qsize() def empty(self): return self.queue.empty() def clear(self): while not self.queue.empty(): self.queue.get_nowait() self.idle_event.set() self.active_event.clear() def idle(self): raise NotImplementedError() async def wait(self): raise NotImplementedError() def set_pause_generator(self, generator=None): if self._pause_cr is not None: self._pause_cr.kill() self._pause_cr = None self._pause_generator = generator if self._pause_generator is not None: self._pause_cr = cocotb.fork(self._run_pause()) def clear_pause_generator(self): self.set_pause_generator(None) async def _run_pause(self): for val in self._pause_generator: self.pause = val await RisingEdge(self.clock)
class I2CMasterModelHAL: def __init__(self, helperSlave, clockDivider): self.wr_scl = helperSlave.io.scl_rd self.wr_sda = helperSlave.io.sda_rd self.rd_scl = helperSlave.io.scl_wr self.rd_sda = helperSlave.io.sda_wr self.clk = helperSlave.io.clk self.sda = 1 self.scl = 1 self.clockDivider = clockDivider self.scl_en = 0 self.trigger = Event() self.sclRising = Event() self.sclFalling = Event() self.dataRead = Event() self.freezeBus = False ########################################################################## # Start the master @cocotb.coroutine def startMaster(self, listOperations): yield RisingEdge(self.clk) self.fork_scl = cocotb.fork(self._genSCL()) self.fork_drain = cocotb.fork(self._manageOpenDrain()) cocotb.fork(self._runMaster(listOperations)) ########################################################################## # Stop all processes def stop(self): self.fork_scl.kill() self.fork_drain.kill() ########################################################################## # Execute all operation @cocotb.coroutine def _runMaster(self, listOperations): for index in range(0,len(listOperations)): operation = listOperations[index] # START ----------------------------------------------------------- if isinstance(operation, START): if index != 0 : yield self.sclRising.wait() yield self._genStart() # WRITE ----------------------------------------------------------- elif isinstance(operation, WRITE): yield self._writeData(operation.data) # READ ------------------------------------------------------------ elif isinstance(operation, READ): yield self._readData() # ACK ------------------------------------------------------------- elif isinstance(operation, ACK): prevOperation = listOperations[index-1] yield self.sclFalling.wait() self.sda = 1 if isinstance(prevOperation, WRITE) else 0 yield self.sclRising.wait() yield self.sclFalling.wait() self.sda = 1 # NACK ------------------------------------------------------------ elif isinstance(operation, NACK): yield self.sclFalling.wait() self.sda = 1 yield self.sclRising.wait() yield self.sclFalling.wait() # STOP ------------------------------------------------------------ elif isinstance(operation, STOP): yield self._genStop() ########################################################################## # Simulation of the openDrain @cocotb.coroutine def _manageOpenDrain(self): while True: yield RisingEdge(self.clk) if int(self.rd_sda) == 0 : self.wr_sda <= 0 else: self.wr_sda <= self.sda if int(self.rd_scl) == 0 : self.wr_scl <= 0 else: self.wr_scl <= self.scl ########################################################################## # SCL generation @cocotb.coroutine def _genSCL(self): cnt = 0 self.scl = 1 while True: yield RisingEdge(self.clk) if self.scl_en == 1 and self.freezeBus == False: cnt += 1 if (cnt >= self.clockDivider): if self.scl == 0: self.scl = 1 self.sclRising.set() else: self.scl = 0 self.sclFalling.set() cnt = 0 elif(cnt == self.clockDivider/2): self.trigger.set() else: self.scl <= 1 ########################################################################## # Generate the start condition @cocotb.coroutine def _genStart(self): self.scl_en = 1 yield self.trigger.wait() self.sda = 0 yield self.sclFalling.wait() self.sda = 1 ########################################################################## # Generate the stop condition @cocotb.coroutine def _genStop(self): self.sda = 0 yield self.sclRising.wait() yield self.trigger.wait() self.sda = 1 self.scl_en = 0 ########################################################################## # Write a data @cocotb.coroutine def _writeData(self, data): data2Send = bin(data)[2:].zfill(I2CConfig.dataWdith) write = True index = 0 while write: yield FallingEdge(self.wr_scl) self.sda = int(data2Send[index]) if index == I2CConfig.dataWdith-1: break index += 1 ########################################################################## # Read a data @cocotb.coroutine def _readData(self): cnt = 0 dataRead = list() while True: if (cnt == I2CConfig.dataWdith): dataInt = int("".join([str(x) for x in dataRead]), 2) self.dataRead.set(data= dataInt ) break else: yield self.sclRising.wait() dataRead.append( int(self.rd_sda) ) cnt += 1
class UsPcieSink(UsPcieBase): _signal_widths = {"tvalid": 1, "tready": 1} _valid_signal = "tvalid" _ready_signal = "tready" _transaction_obj = UsPcieTransaction _frame_obj = UsPcieFrame def __init__(self, bus, clock, reset=None, *args, **kwargs): super().__init__(bus, clock, reset, *args, **kwargs) self.sample_obj = None self.sample_sync = Event() self.queue_occupancy_limit_bytes = -1 self.queue_occupancy_limit_frames = -1 self.bus.tready.setimmediatevalue(0) self._init() cocotb.fork(self._run_sink()) cocotb.fork(self._run()) def _recv(self, frame): if self.queue.empty(): self.active_event.clear() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame async def recv(self): frame = await self.queue.get() return self._recv(frame) def recv_nowait(self): frame = self.queue.get_nowait() return self._recv(frame) 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): return not self.active async def wait(self, timeout=0, timeout_unit='ns'): if not self.empty(): return if timeout: await First(self.active_event.wait(), Timer(timeout, timeout_unit)) else: await self.active_event.wait() async def _run_sink(self): while True: await RisingEdge(self.clock) # read handshake signals tready_sample = self.bus.tready.value tvalid_sample = self.bus.tvalid.value if self.reset is not None and self.reset.value: self.bus.tready <= 0 continue if tready_sample and tvalid_sample: self.sample_obj = self._transaction_obj() self.bus.sample(self.sample_obj) self.sample_sync.set() self.bus.tready <= (not self.full() and not self.pause) async def _run(self): raise NotImplementedError() def _sink_frame(self, frame): self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 self.queue.put_nowait(frame) self.active_event.set()
async def test_task_repr(dut): """Test RunningTask.__repr__.""" log = logging.getLogger("cocotb.test") gen_e = Event('generator_coro_inner') def generator_coro_inner(): gen_e.set() yield Timer(1, units='ns') raise ValueError("inner") @cocotb.coroutine # testing debug with legacy coroutine syntax def generator_coro_outer(): yield from generator_coro_inner() gen_task = generator_coro_outer() log.info(repr(gen_task)) assert re.match(r"<Task \d+ created coro=generator_coro_outer\(\)>", repr(gen_task)) cocotb.fork(gen_task) await gen_e.wait() log.info(repr(gen_task)) assert re.match( r"<Task \d+ pending coro=generator_coro_inner\(\) trigger=<Timer of 1000.00ps at \w+>>", repr(gen_task)) try: await Join(gen_task) except ValueError: pass log.info(repr(gen_task)) assert re.match( r"<Task \d+ finished coro=generator_coro_outer\(\) outcome=Error\(ValueError\('inner',?\)\)>", repr(gen_task)) coro_e = Event('coroutine_inner') async def coroutine_forked(task): log.info(repr(task)) assert re.match(r"<Task \d+ adding coro=coroutine_outer\(\)>", repr(task)) @cocotb.coroutine # Combine requires use of cocotb.coroutine async def coroutine_wait(): await Timer(1, units='ns') async def coroutine_inner(): await coro_e.wait() this_task = coro_e.data # cr_await is None while the coroutine is running, so we can't get the stack... log.info(repr(this_task)) assert re.match(r"<Task \d+ running coro=coroutine_outer\(\)>", repr(this_task)) cocotb.fork(coroutine_forked(this_task)) await Combine(*(coroutine_wait() for _ in range(2))) return "Combine done" async def coroutine_middle(): return await coroutine_inner() async def coroutine_outer(): return await coroutine_middle() coro_task = cocotb.fork(coroutine_outer()) coro_e.set(coro_task) await NullTrigger() log.info(repr(coro_task)) assert re.match( r"<Task \d+ pending coro=coroutine_inner\(\) trigger=Combine\(Join\(<Task \d+>\), Join\(<Task \d+>\)\)>", repr(coro_task)) await Timer(2, units='ns') log.info(repr(coro_task)) assert re.match( r"<Task \d+ finished coro=coroutine_outer\(\) outcome=Value\('Combine done'\)", repr(coro_task)) async def coroutine_first(): await First(coroutine_wait(), Timer(2, units='ns')) coro_task = cocotb.fork(coroutine_first()) log.info(repr(coro_task)) assert re.match( r"<Task \d+ pending coro=coroutine_first\(\) trigger=First\(Join\(<Task \d+>\), <Timer of 2000.00ps at \w+>\)>", repr(coro_task)) async def coroutine_timer(): await Timer(1, units='ns') coro_task = cocotb.fork(coroutine_timer()) # Trigger.__await__ should be popped from the coroutine stack log.info(repr(coro_task)) assert re.match( r"<Task \d+ pending coro=coroutine_timer\(\) trigger=<Timer of 1000.00ps at \w+>>", repr(coro_task))
class AvalonMaster(AvalonMM): """Avalon-MM master """ def __init__(self, entity, name, clock): AvalonMM.__init__(self, entity, name, clock) self.log.debug("AvalonMaster created") self.busy_event = Event("%s_busy" % name) self.busy = False def __len__(self): return 2**len(self.bus.address) @coroutine def _acquire_lock(self): if self.busy: yield self.busy_event.wait() self.busy_event.clear() self.busy = True def _release_lock(self): self.busy = False self.busy_event.set() @coroutine def read(self, address, sync=True): """ Issue a request to the bus and block until this comes back. Simulation time still progresses but syntactically it blocks. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ if not self._can_read: self.log.error("Cannot read - have no read signal") raise TestError("Attempt to read on a write-only AvalonMaster") yield self._acquire_lock() # Apply values for next clock edge if sync: yield RisingEdge(self.clock) self.bus.address <= address self.bus.read <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): yield self._wait_for_nsignal(self.bus.waitrequest) yield RisingEdge(self.clock) # Deassert read self.bus.read <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v if hasattr(self.bus, "readdatavalid"): while True: yield ReadOnly() if int(self.bus.readdatavalid): break yield RisingEdge(self.clock) else: # Assume readLatency = 1 if no readdatavalid # FIXME need to configure this, # should take a dictionary of Avalon properties. yield ReadOnly() # Get the data data = self.bus.readdata.value self._release_lock() raise ReturnValue(data) @coroutine def write(self, address, value): """ Issue a write to the given address with the specified value. See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf """ if not self._can_write: self.log.error("Cannot write - have no write signal") raise TestError("Attempt to write on a read-only AvalonMaster") yield self._acquire_lock() # Apply valuse to bus yield RisingEdge(self.clock) self.bus.address <= address self.bus.writedata <= value self.bus.write <= 1 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2) if hasattr(self.bus, "cs"): self.bus.cs <= 1 # Wait for waitrequest to be low if hasattr(self.bus, "waitrequest"): count = yield self._wait_for_nsignal(self.bus.waitrequest) # Deassert write yield RisingEdge(self.clock) self.bus.write <= 0 if hasattr(self.bus, "byteenable"): self.bus.byteenable <= 0 if hasattr(self.bus, "cs"): self.bus.cs <= 0 v = self.bus.address.value v.binstr = "x" * len(self.bus.address) self.bus.address <= v v = self.bus.writedata.value v.binstr = "x" * len(self.bus.writedata) self.bus.writedata <= v self._release_lock()
class PCIe_host(): def __init__(self): self.read_outstanding = 0 self.rxdata = [] self.requested = 0 self.txqueue = Queue.Queue() self.completion_data = np.zeros(4*1048576, dtype = 'uint64') self.write_data = np.zeros(4*1048576, dtype = 'uint64') self.write_data_expected = np.zeros(4*1048576, dtype = 'uint64') self.errors = 0 self.read_tag = 0 self.read_data = 0 self.command_start = 0 # address for commands def write(self, data, address): """32 bit address / 64 bit data write""" tlp = np.zeros(6, dtype='uint32') tlp[0] = 0x40000002 tlp[1] = 0xbeef00ff tlp[2] = address tlp[3] = endianswap(data) tlp[4] = endianswap(data >> 32) self.txqueue.put(tlp) @cocotb.coroutine def read(self, address, tag=0): """ 32 bit read """ tlp = np.zeros(4, dtype='uint32') tlp[0] = 0x00000001 tlp[1] = 0xbaaa00ff | tag << 8 tlp[2] = address self.read_wait = Event("rw") self.txqueue.put(tlp) yield self.read_wait.wait() raise ReturnValue(self.read_data) def complete(self, address, reqid_tag): address &= 0xFFFF8 address = address >> 3 complete_size = random.choice([16,32]) # DW cdata_64 = self.completion_data[address:address + 512/8] cdata_32 = np.fromstring(cdata_64.tostring(), dtype = 'uint32') cdata_32.byteswap(True) for i in range(128/complete_size): tlp = np.zeros(4 + complete_size, dtype='uint32') tlp[0] = 0x4A000000 | complete_size tlp[1] = 0xbeef0000 | (512-complete_size*4*i) tlp[2] = (reqid_tag << 8) | ((i & 1) << 6) tlp[3:3+complete_size] = cdata_32[i*complete_size: (i+1)*complete_size] address += complete_size/2 self.txqueue.put(tlp) @cocotb.coroutine def do_tx(self, p): while True: try: tlp = self.txqueue.get(False) i_last = len(tlp)/2 - 1 for i in range(len(tlp)/2): while random.choice([0,0,0,1]): p.m_axis_rx_tvalid = 0 yield RisingEdge(p.clock) p.m_axis_rx_tvalid = 1 p.m_axis_rx_tdata = tlp[2*i] | (int(tlp[2*i+1]) << 32) if i == i_last: p.m_axis_rx_tlast = 1 else: p.m_axis_rx_tlast = 0 yield RisingEdge(p.clock) except Queue.Empty: p.m_axis_rx_tvalid = 0 yield RisingEdge(p.clock) @cocotb.coroutine def docycle(self, dut): p = dut p.m_axis_tx_tvalid = 0 p.s_axis_tx_tready = 0 p.pci_reset = 1 for i in range(10): yield RisingEdge(p.clock) p.pci_reset = 0 for i in range(10): yield RisingEdge(p.clock) cocotb.fork(self.do_tx(p)) while True: # RX if (int(p.s_axis_tx_tvalid) == 1) and (int(p.s_axis_tx_tready == 1)): tdata = int(p.s_axis_tx_tdata) tlast = int(p.s_axis_tx_tlast) t_1dw = int(p.s_axis_tx_1dw) self.rxdata.append(tdata & 0xFFFFFFFF) self.rxdata.append(tdata >> 32) if (t_1dw == 1) and (tlast == 0): raise TestFailure("RX: Error: 1dw and not tlast") if tlast == 1: dw0 = self.rxdata[0] dw1 = self.rxdata[1] dw2 = self.rxdata[2] dw3 = self.rxdata[3] tlptype = (dw0 >> 24) & 0x5F length = dw0 & 0x3FF if (dw0 & 0x20000000) != 0: bits = 64 address = dw3 | (int(dw2) << 32) else: bits = 32 address = dw2 if tlptype == 0b0000000: # read #print "{} bit read request at 0x{:016X}, tag = 0x{:02X}, length = {} dw".format(bits, address, int(dw1 >> 8 & 0xFF), length) reqid_tag = 0xFFFFFF & (dw1 >> 8) self.complete(address, reqid_tag) elif tlptype == 0b1000000: # write self.handle_write_tlp(bits, address, length, self.rxdata) elif tlptype == 0b1001010: # read completion self.handle_read_completion_tlp(bits, length, self.rxdata) else: raise TestFailure("RX: unknown TLP {} {} {}".format(hex(dw0), hex(dw1), hex(dw2))) self.rxdata = [] p.s_axis_tx_tready = random.choice([0,1,1,1]) yield RisingEdge(p.clock) def handle_read_completion_tlp(self, bits, length, rxdata): if length != 1: raise TestFailure("ERROR: read completion TLP has incorrect length in header, {}".format(length)) if len(rxdata) != 4: raise TestFailure("ERROR: read completion TLP length does not match header, {}".format(len(self.rxdata))) data = endianswap(rxdata[3]) tag = 0xFF & (rxdata[2]>>8) print "read completion, data = {}, tag = {}".format(data, tag) self.read_data = data self.read_wait.set(data) def handle_write_tlp(self, bits, address, length, rxdata): #print "{} bit write request at 0x{:016X}, {} header dw, {} payload dw".format(bits, address, length, len(rxdata)) #if random.choice([0,0,0,0,1]): #self.read(0x1, self.read_tag) #self.read_tag += 1 if length & 0x01 != 0: raise TestFailure("write request is an odd length") if length + 4 != len(self.rxdata): raise TestFailure("write request length does not match".format(len(rxdata))) ndata = np.asarray(rxdata, dtype = 'uint32') if bits == 32: data = ndata[3:3+length] else: data = ndata[4:4+length] data.byteswap(True) addr_start = (address/8) & 0xFFFFFF d = np.fromstring(data.tostring(), dtype = 'uint64') self.write_data[addr_start:addr_start + length/2] = d #print "d[{}] = 0x{:016X}".format(0, d[0]) def write_fifo(self, fifo, data): self.write(data, (8+fifo)*8)
class S10PcieSink(S10PcieBase): _signal_widths = {"valid": 1, "ready": 1} _valid_signal = "valid" _ready_signal = "ready" _transaction_obj = S10PcieTransaction _frame_obj = S10PcieFrame def __init__(self, bus, clock, reset=None, ready_latency=0, *args, **kwargs): super().__init__(bus, clock, reset, ready_latency, *args, **kwargs) self.sample_obj = None self.sample_sync = Event() self.queue_occupancy_limit_bytes = -1 self.queue_occupancy_limit_frames = -1 self.bus.ready.setimmediatevalue(0) cocotb.start_soon(self._run_sink()) cocotb.start_soon(self._run()) def _recv(self, frame): if self.queue.empty(): self.active_event.clear() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame async def recv(self): frame = await self.queue.get() return self._recv(frame) def recv_nowait(self): frame = self.queue.get_nowait() return self._recv(frame) 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): return not self.active async def wait(self, timeout=0, timeout_unit='ns'): if not self.empty(): return if timeout: await First(self.active_event.wait(), Timer(timeout, timeout_unit)) else: await self.active_event.wait() async def _run_sink(self): ready_delay = [] clock_edge_event = RisingEdge(self.clock) while True: await clock_edge_event # read handshake signals ready_sample = self.bus.ready.value valid_sample = self.bus.valid.value if self.reset is not None and self.reset.value: self.bus.ready.value = 0 continue # ready delay if self.ready_latency > 0: if len(ready_delay) != self.ready_latency: ready_delay = [0] * self.ready_latency ready_delay.append(ready_sample) ready_sample = ready_delay.pop(0) if valid_sample and ready_sample: self.sample_obj = self._transaction_obj() self.bus.sample(self.sample_obj) self.sample_sync.set() elif self.ready_latency > 0: assert not valid_sample, "handshake error: valid asserted outside of ready cycle" self.bus.ready.value = (not self.full() and not self.pause) async def _run(self): self.active = False frame = None dword_count = 0 while True: while not self.sample_obj: self.sample_sync.clear() await self.sample_sync.wait() self.active = True sample = self.sample_obj self.sample_obj = None for seg in range(self.seg_count): if not sample.valid & (1 << seg): continue if sample.sop & (1 << seg): assert frame is None, "framing error: sop asserted in frame" frame = S10PcieFrame() hdr = (sample.data >> (seg * self.seg_width)) & self.seg_mask fmt = (hdr >> 29) & 0b111 if fmt & 0b001: dword_count = 4 else: dword_count = 3 if fmt & 0b010: count = hdr & 0x3ff if count == 0: count = 1024 dword_count += count frame.bar_range = (sample.bar_range >> seg * 3) & 0x7 frame.func_num = (sample.func_num >> seg * 3) & 0x7 if sample.vf_active & (1 << seg): frame.vf_num = (sample.vf_num >> seg * 11) & 0x7ff frame.err = (sample.err >> seg) & 0x1 assert frame is not None, "framing error: data transferred outside of frame" if dword_count > 0: data = (sample.data >> (seg * self.seg_width)) & self.seg_mask parity = (sample.parity >> (seg * self.seg_par_width)) & self.seg_par_mask for k in range(min(self.seg_byte_lanes, dword_count)): frame.data.append((data >> 32 * k) & 0xffffffff) frame.parity.append((parity >> 4 * k) & 0xf) dword_count -= 1 if sample.eop & (1 << seg): assert dword_count == 0, "framing error: incorrect length or early eop" self.log.info(f"RX frame: {frame}") self._sink_frame(frame) self.active = False frame = None def _sink_frame(self, frame): self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 self.queue.put_nowait(frame) self.active_event.set()
class I2CHALAnalyser: def __init__(self, helperSlave, listOperation): self.sda = helperSlave.io.sda_rd self.scl = helperSlave.io.scl_rd self.clk = helperSlave.io.clk self.event_RisingEdge = Event() self.event_FallingEdge = Event() self.event_Start = Event() self.event_Stop = Event() self.listOp = list() self.refListOp = listOperation self.dataBinRead = list() self.startSeq = 0 #========================================================================== # Start to analyse the bus #========================================================================== @cocotb.coroutine def start(self): self.fork_falling = cocotb.fork(self._FallingEdgeDetection()) self.fork_rising = cocotb.fork(self._RisingEdgeDetection()) self.fork_start = cocotb.fork(self._startDetection()) self.fork_stop = cocotb.fork(self._stopDetection()) yield self._analyser() #========================================================================== # Stop all processes #========================================================================== def stop(self): self.fork_falling.kill() self.fork_rising.kill() self.fork_start.kill() self.fork_stop.kill() #========================================================================== # Store all event appening on the bus #========================================================================== @cocotb.coroutine def _analyser(self): self.listOp = list() # Start --------------------------------------------------------------- yield self.event_Start.wait() yield RisingEdge(self.clk) self.startSeq = 0 while True: dataBinRead = list() index = 0 # Read data ----------------------------------------------------------- while index < I2CConfig.dataWdith: yield self.event_RisingEdge.wait() dataBinRead.append(int(self.sda)) #print("data ", index, " value " , int(self.sda), "index ", index, "start " , self.startSeq ) if self.startSeq == 1: index = 0 self.startSeq = 0 dataBinRead = list() dataBinRead.append(int(self.sda)) index += 1 dataInRead = int("".join([str(x) for x in dataBinRead]), 2) self.listOp.append(DATA(dataInRead)) # Read ACK ------------------------------------------------------------ yield self.event_RisingEdge.wait() if int(self.sda) == 0: self.listOp.append(ACK()) else: self.listOp.append(NACK()) #print() #========================================================================== # Detect the start condition #========================================================================== @cocotb.coroutine def _startDetection(self): yield RisingEdge(self.clk) while True: prev = int(self.sda) yield RisingEdge(self.clk) if prev == 1 and int(self.sda) == 0: if int(self.scl) == 1: self.event_Start.set() self.listOp.append(START()) self.startSeq = 1 #========================================================================== # Detect the stop condition #========================================================================== @cocotb.coroutine def _stopDetection(self): yield RisingEdge(self.clk) while True: prev = int(self.sda) yield RisingEdge(self.clk) if prev == 0 and int(self.sda) == 1: if int(self.scl) == 1: self.event_Stop.set() self.listOp.append(STOP()) # check sequence... for (op, ref) in zip(self.listOp, self.refListOp): if isinstance(ref, START) and isinstance(op, START): pass elif isinstance(ref, STOP) and isinstance(op, STOP): pass elif isinstance(ref, ACK) and isinstance(op, ACK): pass elif isinstance(ref, NACK) and isinstance(op, NACK): pass elif (isinstance(ref, WRITE) or isinstance(ref, READ)) and isinstance(op, DATA): if ref.data != op.data: print("ref ", hex(ref.data), " op ", hex(op.data)) assertEquals(ref.data , op.data , "Analyser data ERROR") else: assertEquals(True , False , "%s is not equal to %s" % (ref, op)) #========================================================================== # Detect a Rising edge of scl #========================================================================== @cocotb.coroutine def _RisingEdgeDetection(self): while True: yield RisingEdge(self.scl) self.event_RisingEdge.set() #========================================================================== # Detect a Falling edge of scl #========================================================================== @cocotb.coroutine def _FallingEdgeDetection(self): while True: yield FallingEdge(self.scl) self.event_FallingEdge.set()
class S10PcieBase: _signal_widths = {"ready": 1} _valid_signal = "valid" _ready_signal = "ready" _transaction_obj = S10PcieTransaction _frame_obj = S10PcieFrame def __init__(self, bus, clock, reset=None, ready_latency=0, *args, **kwargs): self.bus = bus self.clock = clock self.reset = reset self.ready_latency = ready_latency self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}") super().__init__(*args, **kwargs) self.active = False self.queue = Queue() self.dequeue_event = Event() self.idle_event = Event() self.idle_event.set() self.active_event = Event() self.pause = False self._pause_generator = None self._pause_cr = None self.queue_occupancy_bytes = 0 self.queue_occupancy_frames = 0 self.width = len(self.bus.data) self.byte_size = 32 self.byte_lanes = self.width // self.byte_size self.byte_mask = 2**self.byte_size - 1 self.seg_count = len(self.bus.valid) self.seg_width = self.width // self.seg_count self.seg_mask = 2**self.seg_width - 1 self.seg_par_width = self.seg_width // 8 self.seg_par_mask = 2**self.seg_par_width - 1 self.seg_byte_lanes = self.byte_lanes // self.seg_count self.seg_empty_width = (self.seg_byte_lanes - 1).bit_length() self.seg_empty_mask = 2**self.seg_empty_width - 1 assert self.width in {256, 512} assert len(self.bus.data) == self.seg_count * self.seg_width assert len(self.bus.sop) == self.seg_count assert len(self.bus.eop) == self.seg_count assert len(self.bus.valid) == self.seg_count if hasattr(self.bus, "empty"): assert len(self.bus.empty) == self.seg_count * self.seg_empty_width if hasattr(self.bus, "err"): assert len(self.bus.err) == self.seg_count if hasattr(self.bus, "bar_range"): assert len(self.bus.bar_range) == self.seg_count * 3 if hasattr(self.bus, "vf_active"): assert len(self.bus.vf_active) == self.seg_count if hasattr(self.bus, "func_num"): assert len(self.bus.func_num) == self.seg_count * 2 if hasattr(self.bus, "vf_num"): assert len(self.bus.vf_num) == self.seg_count * 11 if hasattr(self.bus, "parity"): assert len(self.bus.parity) == self.seg_count * self.seg_width // 8 def count(self): return self.queue.qsize() def empty(self): return self.queue.empty() def clear(self): while not self.queue.empty(): self.queue.get_nowait() self.idle_event.set() self.active_event.clear() def idle(self): raise NotImplementedError() async def wait(self): raise NotImplementedError() def set_pause_generator(self, generator=None): if self._pause_cr is not None: self._pause_cr.kill() self._pause_cr = None self._pause_generator = generator if self._pause_generator is not None: self._pause_cr = cocotb.start_soon(self._run_pause()) def clear_pause_generator(self): self.set_pause_generator(None) async def _run_pause(self): clock_edge_event = RisingEdge(self.clock) for val in self._pause_generator: self.pause = val await clock_edge_event
class EIMMaster(EIM): """ EIM master """ def __init__(self, entity, name, clock, timeout=None, width=16, **kwargs): sTo = ", no cycle timeout" if timeout is not None: sTo = ", cycle timeout is %u clockcycles" % timeout self.busy_event = Event("%s_busy" % name) self._timeout = timeout self.busy = False self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self._op_cnt = 0 self._clk_cycle_count = 0 EIM.__init__(self, entity, name, clock, width, **kwargs) self.log.info("EIM Master created%s" % sTo) @coroutine def _clk_cycle_counter(self): """ Cycle counter to time bus operations """ clkedge = RisingEdge(self.clock) self._clk_cycle_count = 0 while self.busy: yield clkedge self._clk_cycle_count += 1 @coroutine def _open_cycle(self): #Open new eim cycle if self.busy: self.log.error("Opening Cycle, but EIM Driver is already busy." + "Someting's wrong") yield self.busy_event.wait() self.busy_event.clear() self.busy = True cocotb.fork(self._read()) cocotb.fork(self._clk_cycle_counter()) #self.bus.cyc <= 1 self._acked_ops = 0 self._res_buf = [] self._aux_buf = [] self.log.debug("Opening cycle, %u Ops" % self._op_cnt) @coroutine def _close_cycle(self): #Close current eim cycle clkedge = RisingEdge(self.clock) count = 0 last_acked_ops = 0 #Wait for all Operations being acknowledged by the slave before #lowering the cycle line. #This is not mandatory by the bus standard, but a crossbar might #send acks to the wrong master if we don't wait. #We don't want to risk that, it could hang the bus while self._acked_ops < self._op_cnt: if last_acked_ops != self._acked_ops: self.log.debug("Waiting for missing acks: %u/%u" % (self._acked_ops, self._op_cnt) ) last_acked_ops = self._acked_ops #check for timeout when finishing the cycle count += 1 if (not (self._timeout is None)): if (count > self._timeout): raise TestFailure( "Timeout of %u clock cycles "%self._timeout + "reached when waiting for reply from " + "slave") yield clkedge self.busy = False self.busy_event.set() self.bus.lba <= 1 self.bus.cs <= 1 self.log.debug("Closing cycle") yield clkedge @coroutine def _read(self): """ Reader for slave replies """ clkedge = RisingEdge(self.clock) while self.busy: datrd = self.bus.daout.value #append reply and meta info to result buffer tmpRes = EIMRes(adr=None, datrd=datrd, datwr=None, waitIdle=None, waitStall=None, waitAck=self._clk_cycle_count) self._res_buf.append(tmpRes) self._acked_ops += 1 yield clkedge @coroutine def _drive(self, we, adr, datwr, wsc, idle): """ Drive the EIM Master Out Lines """ clkedge = RisingEdge(self.clock) if self.busy: # insert requested idle cycles if idle is not None: idlecnt = idle while idlecnt > 0: idlecnt -= 1 yield clkedge # set address self.bus.lba <= 0 self.bus.dain <= adr yield clkedge self.bus.lba <= 1 # drive outputs self.bus.cs <= 0 self.bus.dain <= datwr self.bus.rw <= we yield clkedge for i in range(wsc): yield clkedge #append operation and meta info to auxiliary buffer self._aux_buf.append( EIMAux(adr, datwr, idle, self._clk_cycle_count)) #XXX yield self._wait_ack() self.bus.rw <= 0 self.bus.cs <= 1 else: self.log.error("Cannot drive the EIM bus outside a cycle!") @coroutine def send_cycle(self, arg): """ The main sending routine Args: list(EIMOperations) """ cnt = 0 clkedge = RisingEdge(self.clock) yield clkedge if is_sequence(arg): self._op_cnt = len(arg) if self._op_cnt < 1: self.log.error("List contains no operations to carry out") else: result = [] yield self._open_cycle() for op in arg: if not isinstance(op, EIMOp): raise TestFailure("Sorry, argument must be a list " + "of EIMOp (EIM Operation) objects.") if op.dat is not None: we = 1 dat = op.dat else: we = 0 dat = 0 yield self._drive(we, op.adr, dat, op.wsc, op.idle) self.log.debug( "#%3u WE: %s ADR: 0x%08x " % (cnt, we, op.adr) + "DAT: 0x%08x " % dat + "IDLE: %3u" % op.idle) cnt += 1 yield self._close_cycle() #do pick and mix from result- #and auxiliary buffer so we get all operation and meta info for res, aux in zip(self._res_buf, self._aux_buf): res.datwr = aux.datwr res.adr = aux.adr res.waitIdle = aux.waitIdle res.waitStall = aux.waitStall res.waitAck -= aux.ts result.append(res) raise ReturnValue(result) else: raise TestFailure("Sorry, argument must be a list of EIMOp " + "(EIM Operation) objects!") raise ReturnValue(None)
class S10PcieSource(S10PcieBase): _signal_widths = {"valid": 1, "ready": 1} _valid_signal = "valid" _ready_signal = "ready" _transaction_obj = S10PcieTransaction _frame_obj = S10PcieFrame def __init__(self, bus, clock, reset=None, ready_latency=0, *args, **kwargs): super().__init__(bus, clock, reset, ready_latency, *args, **kwargs) self.drive_obj = None self.drive_sync = Event() self.queue_occupancy_limit_bytes = -1 self.queue_occupancy_limit_frames = -1 self.bus.data.setimmediatevalue(0) self.bus.sop.setimmediatevalue(0) self.bus.eop.setimmediatevalue(0) self.bus.valid.setimmediatevalue(0) if hasattr(self.bus, "empty"): self.bus.empty.setimmediatevalue(0) if hasattr(self.bus, "err"): self.bus.err.setimmediatevalue(0) if hasattr(self.bus, "bar_range"): self.bus.bar_range.setimmediatevalue(0) if hasattr(self.bus, "vf_active"): self.bus.vf_active.setimmediatevalue(0) if hasattr(self.bus, "func_num"): self.bus.func_num.setimmediatevalue(0) if hasattr(self.bus, "vf_num"): self.bus.vf_num.setimmediatevalue(0) if hasattr(self.bus, "parity"): self.bus.parity.setimmediatevalue(0) cocotb.start_soon(self._run_source()) cocotb.start_soon(self._run()) async def _drive(self, obj): if self.drive_obj is not None: self.drive_sync.clear() await self.drive_sync.wait() self.drive_obj = obj async def send(self, frame): while self.full(): self.dequeue_event.clear() await self.dequeue_event.wait() frame = S10PcieFrame(frame) await self.queue.put(frame) self.idle_event.clear() self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 def send_nowait(self, frame): if self.full(): raise QueueFull() frame = S10PcieFrame(frame) self.queue.put_nowait(frame) self.idle_event.clear() self.queue_occupancy_bytes += len(frame) self.queue_occupancy_frames += 1 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): return self.empty() and not self.active async def wait(self): await self.idle_event.wait() async def _run_source(self): self.active = False ready_delay = [] clock_edge_event = RisingEdge(self.clock) while True: await clock_edge_event # read handshake signals ready_sample = self.bus.ready.value valid_sample = self.bus.valid.value if self.reset is not None and self.reset.value: self.active = False self.bus.valid.value = 0 continue # ready delay if self.ready_latency > 1: if len(ready_delay) != (self.ready_latency - 1): ready_delay = [0] * (self.ready_latency - 1) ready_delay.append(ready_sample) ready_sample = ready_delay.pop(0) if (ready_sample and valid_sample ) or not valid_sample or self.ready_latency > 0: if self.drive_obj and not self.pause and ( ready_sample or self.ready_latency == 0): self.bus.drive(self.drive_obj) self.drive_obj = None self.drive_sync.set() self.active = True else: self.bus.valid.value = 0 self.active = bool(self.drive_obj) if not self.drive_obj: self.idle_event.set() async def _run(self): while True: frame = await self._get_frame() frame_offset = 0 self.log.info(f"TX frame: {frame}") first = True while frame is not None: transaction = self._transaction_obj() for seg in range(self.seg_count): if frame is None: if not self.empty(): frame = self._get_frame_nowait() frame_offset = 0 self.log.info(f"TX frame: {frame}") first = True else: break if first: first = False transaction.valid |= 1 << seg transaction.sop |= 1 << seg transaction.bar_range |= frame.bar_range << seg * 3 transaction.func_num |= frame.func_num << seg * 3 if frame.vf_num is not None: transaction.vf_active |= 1 << seg transaction.vf_num |= frame.vf_num << seg * 11 transaction.err |= frame.err << seg empty = 0 if frame.data: transaction.valid |= 1 << seg for k in range( min(self.seg_byte_lanes, len(frame.data) - frame_offset)): transaction.data |= frame.data[ frame_offset] << 32 * ( k + seg * self.seg_byte_lanes) transaction.parity |= frame.parity[ frame_offset] << 4 * ( k + seg * self.seg_byte_lanes) empty = self.seg_byte_lanes - 1 - k frame_offset += 1 if frame_offset >= len(frame.data): transaction.eop |= 1 << seg transaction.empty |= empty << seg * self.seg_empty_width frame = None await self._drive(transaction) async def _get_frame(self): frame = await self.queue.get() self.dequeue_event.set() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame def _get_frame_nowait(self): frame = self.queue.get_nowait() self.dequeue_event.set() self.queue_occupancy_bytes -= len(frame) self.queue_occupancy_frames -= 1 return frame