Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2c36276f3 | ||
|
|
eab0c7fee0 | ||
|
|
35ed1472d6 | ||
|
|
a7fe5d9674 | ||
|
|
08122c1a65 | ||
|
|
8e76f2d24c | ||
|
|
69717c1698 | ||
|
|
c18fdd6e22 | ||
|
|
222af15437 | ||
|
|
ffaf8932fc | ||
|
|
8f719daf75 | ||
|
|
154d3b11f4 | ||
|
|
97abb032d8 | ||
|
|
5593bfe296 | ||
|
|
3caa343845 | ||
|
|
6654c707b2 | ||
|
|
359e015b35 | ||
|
|
7e0464f6f3 | ||
|
|
4ec0c36892 | ||
|
|
7c18f9b73f | ||
|
|
2e79ef233f | ||
|
|
7748f871f4 | ||
|
|
f6823fc8fd | ||
|
|
c63d65bb87 | ||
|
|
1618220a30 | ||
|
|
ecb2f5ae81 | ||
|
|
fcf6374c3c | ||
|
|
7582067929 | ||
|
|
7b7e8bf3e2 | ||
|
|
426c5828f1 | ||
|
|
44e58275ad | ||
|
|
f4f7c0b59d | ||
|
|
ad83d58b01 | ||
|
|
4336c32b40 |
83
README.md
83
README.md
@@ -3,6 +3,7 @@
|
||||
[](https://github.com/alexforencich/cocotbext-axi/actions/)
|
||||
[](https://codecov.io/gh/alexforencich/cocotbext-axi)
|
||||
[](https://pypi.org/project/cocotbext-axi)
|
||||
[](https://pepy.tech/project/cocotbext-axi)
|
||||
|
||||
GitHub repository: https://github.com/alexforencich/cocotbext-axi
|
||||
|
||||
@@ -37,11 +38,11 @@ The `AxiMaster` is a wrapper around `AxiMasterWrite` and `AxiMasterRead`. Simil
|
||||
|
||||
To use these modules, import the one you need and connect it to the DUT:
|
||||
|
||||
from cocotbext.axi import AxiMaster
|
||||
from cocotbext.axi import AxiBus, AxiMaster
|
||||
|
||||
axi_master = AxiMaster(dut, "s_axi", dut.clk, dut.rst)
|
||||
axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)
|
||||
|
||||
The modules use `cocotb.bus.Bus` internally to automatically connect to the corresponding signals in the bus, presuming they are named according to the AXI spec and have a common prefix.
|
||||
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||
|
||||
Once the module is instantiated, read and write operations can be initiated in a few different ways.
|
||||
|
||||
@@ -74,10 +75,10 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
||||
|
||||
#### `AxiMaster` and `AxiLiteMaster` constructor parameters
|
||||
|
||||
* _entity_: object that contains the AXI slave interface signals
|
||||
* _name_: signal name prefix (e.g. for `s_axi_awaddr`, the prefix is `s_axi`)
|
||||
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
||||
* _clock_: clock signal
|
||||
* _reset_: reset signal (optional)
|
||||
* _reset_active_level_: reset active level (optional, default `True`)
|
||||
|
||||
#### Additional parameters for `AxiMaster`
|
||||
|
||||
@@ -131,6 +132,10 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
||||
* _prot_: AXI protection flags, default `AxiProt.NONSECURE`
|
||||
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None` (`init_read()` and `init_write()` only). If provided, the event will be triggered when the operation completes and the result returned via `Event.data` instead of `get_read_data()` or `get_write_resp()`.
|
||||
|
||||
#### `AxiBus` and `AxiLiteBus` objects
|
||||
|
||||
The `AxiBus`, `AxiLiteBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are extensions of `cocotb.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate.
|
||||
|
||||
### AXI and AXI lite RAM
|
||||
|
||||
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts.
|
||||
@@ -139,11 +144,11 @@ The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `Ax
|
||||
|
||||
To use these modules, import the one you need and connect it to the DUT:
|
||||
|
||||
from cocotbext.axi import AxiRam
|
||||
from cocotbext.axi import AxiBus, AxiRam
|
||||
|
||||
axi_ram = AxiRam(dut, "m_axi", dut.clk, dut.rst, size=2**16)
|
||||
axi_ram = AxiRam(AxiBus.from_prefix(dut, "m_axi"), dut.clk, dut.rst, size=2**16)
|
||||
|
||||
The modules use `cocotb.bus.Bus` internally to automatically connect to the corresponding signals in the bus, presuming they are named according to the AXI spec and have a common prefix.
|
||||
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||
|
||||
Once the module is instantiated, the memory contents can be accessed in a couple of different ways. First, the `mmap` object can be accessed directly via the `mem` attribute. Second, `read()`, `write()`, and various word-access wrappers are available. Hex dump helper methods are also provided for debugging. For example:
|
||||
|
||||
@@ -152,17 +157,17 @@ Once the module is instantiated, the memory contents can be accessed in a couple
|
||||
|
||||
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
||||
|
||||
axi_ram_p1 = AxiRam(dut, "m00_axi", dut.clk, dut.rst, size=2**16)
|
||||
axi_ram_p2 = AxiRam(dut, "m01_axi", dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
axi_ram_p3 = AxiRam(dut, "m02_axi", dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
axi_ram_p4 = AxiRam(dut, "m03_axi", dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
axi_ram_p1 = AxiRam(AxiBus.from_prefix(dut, "m00_axi"), dut.clk, dut.rst, size=2**16)
|
||||
axi_ram_p2 = AxiRam(AxiBus.from_prefix(dut, "m01_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
axi_ram_p3 = AxiRam(AxiBus.from_prefix(dut, "m02_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
axi_ram_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||
|
||||
#### `AxiRam` and `AxiLiteRam` constructor parameters
|
||||
|
||||
* _entity_: object that contains the AXI master interface signals
|
||||
* _name_: signal name prefix (e.g. for `m_axi_awaddr`, the prefix is `m_axi`)
|
||||
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
||||
* _clock_: clock signal
|
||||
* _reset_: reset signal (optional)
|
||||
* _reset_active_level_: reset active level (optional, default `True`)
|
||||
* _size_: memory size in bytes (optional, default 1024)
|
||||
* _mem_: mmap object to use (optional, overrides _size_)
|
||||
|
||||
@@ -188,33 +193,40 @@ Multi-port memories can be constructed by passing the `mem` object of the first
|
||||
* `write_word(address, data, byteorder='little', ws=2)`: write single _ws_-byte word at _address_
|
||||
* `write_dword(address, data, byteorder='little')`: write single 4-byte dword at _address_
|
||||
* `write_qword(address, data, byteorder='little')`: write single 8-byte qword at _address_
|
||||
* `hexdump(address, length, prefix='')`: print hex dump of _length_ bytes starting from `address`, prefix lines with optional `prefix`
|
||||
* `hexdump_line(address, length, prefix='')`: return hex dump (list of str) of _length_ bytes starting from `address`, prefix lines with optional `prefix`
|
||||
* `hexdump_str(address, length, prefix='')`: return hex dump (str) of _length_ bytes starting from `address`, prefix lines with optional `prefix`
|
||||
* `hexdump(address, length, prefix='')`: print hex dump of _length_ bytes starting from _address_, prefix lines with optional _prefix_
|
||||
* `hexdump_line(address, length, prefix='')`: return hex dump (list of str) of _length_ bytes starting from _address_, prefix lines with optional _prefix_
|
||||
* `hexdump_str(address, length, prefix='')`: return hex dump (str) of _length_ bytes starting from _address_, prefix lines with optional _prefix_
|
||||
|
||||
### AXI stream
|
||||
|
||||
The `AxiStreamSource`, `AxiStreamSink`, and `AxiStreamMonitor` classes can be used to drive, receive, and monitor traffic on AXI stream interfaces. The `AxiStreamSource` drives all signals except for `tready` and can be used to drive AXI stream traffic into a design. The `AxiStreamSink` drives the `tready` line only and as such can receive AXI stream traffic and exert backpressure. The `AxiStreamMonitor` drives no signals and as such can be connected to internal AXI stream interfaces to monitor traffic.
|
||||
The `AxiStreamSource`, `AxiStreamSink`, and `AxiStreamMonitor` classes can be used to drive, receive, and monitor traffic on AXI stream interfaces. The `AxiStreamSource` drives all signals except for `tready` and can be used to drive AXI stream traffic into a design. The `AxiStreamSink` drives the `tready` line only and as such can receive AXI stream traffic and exert backpressure. The `AxiStreamMonitor` drives no signals and as such can be connected to AXI stream interfaces anywhere within a design to passively monitor traffic.
|
||||
|
||||
To use these modules, import the one you need and connect it to the DUT:
|
||||
|
||||
from cocotbext.axi import AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
from cocotbext.axi import (AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor)
|
||||
|
||||
axis_source = AxiStreamSource(dut, "s_axis", dut.clk, dut.rst)
|
||||
axis_sink = AxiStreamSink(dut, "m_axis", dut.clk, dut.rst)
|
||||
axis_monitor = AxiStreamMonitor(dut.inst, "int_axis", dut.clk, dut.rst)
|
||||
axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
|
||||
axis_sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "m_axis"), dut.clk, dut.rst)
|
||||
axis_mon= AxiStreamMonitor(AxiStreamBus.from_prefix(dut.inst, "int_axis"), dut.clk, dut.rst)
|
||||
|
||||
The modules use `cocotb.bus.Bus` internally to automatically connect to the corresponding signals in the bus, presuming they are named according to the AXI spec and have a common prefix.
|
||||
The first argument to the constructor accepts an `AxiStreamBus` object. This object is a container for the interface signals and includes class methods to automate connections.
|
||||
|
||||
To send data into a design with an `AxiStreamSource`, call `send()` or `write()`. Accepted data types are iterables or `AxiStreamFrame` objects. Call `wait()` to wait for the transmit operation to complete. Example:
|
||||
To send data into a design with an `AxiStreamSource`, call `send()`/`send_nowait()` or `write()`/`write_nowait()`. Accepted data types are iterables or `AxiStreamFrame` objects. Optionally, call `wait()` to wait for the transmit operation to complete. Example:
|
||||
|
||||
axis_source.send(b'test data')
|
||||
await axis_source.send(b'test data')
|
||||
# wait for operation to complete (optional)
|
||||
await axis_source.wait()
|
||||
|
||||
To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()` or `read()`. `recv()` is intended for use with a frame-oriented interface, and by default compacts `AxiStreamFrame`s before returning them. `read()` is intended for non-frame-oriented streams. Calling `read()` internally calls `recv()` for all frames currently in the queue, then compacts and coalesces `tdata` from all frames into a separate read queue, from which read data is returned. All sideband data is discarded. Call `wait()` to wait for new receive data.
|
||||
It is also possible to wait for the transmission of a specific frame to complete by passing an event in the tx_complete field of the `AxiStreamFrame` object, and then awaiting the event. The frame, with simulation time fields set, will be returned in the event data. Example:
|
||||
|
||||
await axis_sink.wait()
|
||||
data = axis_sink.recv()
|
||||
frame = AxiStreamFrame(b'test data', tx_complete=Event())
|
||||
await axis_source.send(frame)
|
||||
await frame.tx_complete.wait()
|
||||
print(frame.tx_complete.data.sim_time_start)
|
||||
|
||||
To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()`/`recv_nowait()` or `read()`/`read_nowait()`. Optionally call `wait()` to wait for new receive data. `recv()` is intended for use with a frame-oriented interface, and by default compacts `AxiStreamFrame`s before returning them. `read()` is intended for non-frame-oriented streams. Calling `read()` internally calls `recv()` for all frames currently in the queue, then compacts and coalesces `tdata` from all frames into a separate read queue, from which read data is returned. All sideband data is discarded.
|
||||
|
||||
data = await axis_sink.recv()
|
||||
|
||||
#### Signals
|
||||
|
||||
@@ -229,10 +241,10 @@ To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()` or
|
||||
|
||||
#### Constructor parameters:
|
||||
|
||||
* _entity_: object that contains the AXI stream interface signals
|
||||
* _name_: signal name prefix (e.g. for `m_axis_tdata`, the prefix is `m_axis`)
|
||||
* _bus_: `AxiStreamBus` object containing AXI stream interface signals
|
||||
* _clock_: clock signal
|
||||
* _reset_: reset signal (optional)
|
||||
* _reset_active_level_: reset active level (optional, default `True`)
|
||||
* _byte_size_: byte size (optional)
|
||||
* _byte_lanes_: byte lane count (optional)
|
||||
|
||||
@@ -256,17 +268,21 @@ Note: _byte_size_, _byte_lanes_, `len(tdata)`, and `len(tkeep)` are all related,
|
||||
* `recv_nowait(compact=True)`: receive a frame as a `GmiiFrame` (non-blocking) (sink)
|
||||
* `read(count)`: read _count_ bytes from buffer (blocking) (sink/monitor)
|
||||
* `read_nowait(count)`: read _count_ bytes from buffer (non-blocking) (sink/monitor)
|
||||
* `read(count)`: read _count_ bytes from buffer (sink/monitor)
|
||||
* `count()`: returns the number of items in the queue (all)
|
||||
* `empty()`: returns _True_ if the queue is empty (all)
|
||||
* `full()`: returns _True_ if the queue occupancy limits are met (sink)
|
||||
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
|
||||
* `clear()`: drop all data in queue (all)
|
||||
* `wait()`: wait for idle (source)
|
||||
* `wait(timeout=0, timeout_unit='ns')`: wait for frame received (sink)
|
||||
* `set_pause_generator(generator)`: set generator for pause signal, generator will be advanced on every clock cycle (source/sink)
|
||||
* `clear_pause_generator()`: remove generator for pause signal (source/sink)
|
||||
|
||||
#### AxiStreamFrame object
|
||||
#### `AxiStreamBus` object
|
||||
|
||||
The `AxiStreamBus` object is a container for the interface signals. Currently, it is an extension of `cocotb.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching.
|
||||
|
||||
#### `AxiStreamFrame` object
|
||||
|
||||
The `AxiStreamFrame` object is a container for a frame to be transferred via AXI stream. The `tdata` field contains the packet data in the form of a list of bytes, which is either a `bytearray` if the byte size is 8 bits or a `list` of `int`s otherwise. `tkeep`, `tid`, `tdest`, and `tuser` can either be `None`, an `int`, or a `list` of `int`s.
|
||||
|
||||
@@ -277,6 +293,9 @@ Attributes:
|
||||
* `tid`: tid field, optional; int or list with one entry per `tdata`, last value used per cycle when sending.
|
||||
* `tdest`: tdest field, optional; int or list with one entry per `tdata`, last value used per cycle when sending.
|
||||
* `tuser`: tuser field, optional; int or list with one entry per `tdata`, last value used per cycle when sending.
|
||||
* `sim_time_start`: simulation time of first transfer cycle of frame.
|
||||
* `sim_time_end`: simulation time of last transfer cycle of frame.
|
||||
* `tx_complete`: event or callable triggered when frame is transmitted.
|
||||
|
||||
Methods:
|
||||
|
||||
|
||||
@@ -26,10 +26,14 @@ from .version import __version__
|
||||
|
||||
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
||||
|
||||
from .axis import AxiStreamFrame, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
from .axis import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
|
||||
from .axil_channels import AxiLiteAWBus, AxiLiteWBus, AxiLiteBBus, AxiLiteARBus, AxiLiteRBus
|
||||
from .axil_channels import AxiLiteWriteBus, AxiLiteReadBus, AxiLiteBus
|
||||
from .axil_master import AxiLiteMasterWrite, AxiLiteMasterRead, AxiLiteMaster
|
||||
from .axil_ram import AxiLiteRamWrite, AxiLiteRamRead, AxiLiteRam
|
||||
|
||||
from .axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiARBus, AxiRBus
|
||||
from .axi_channels import AxiWriteBus, AxiReadBus, AxiBus
|
||||
from .axi_master import AxiMasterWrite, AxiMasterRead, AxiMaster
|
||||
from .axi_ram import AxiRamWrite, AxiRamRead, AxiRam
|
||||
|
||||
@@ -25,7 +25,7 @@ THE SOFTWARE.
|
||||
from .stream import define_stream
|
||||
|
||||
# Write address channel
|
||||
AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||
AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awprot", "awvalid", "awready"],
|
||||
optional_signals=["awlock", "awcache", "awqos", "awregion", "awuser"],
|
||||
signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1,
|
||||
@@ -33,21 +33,21 @@ AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||
)
|
||||
|
||||
# Write data channel
|
||||
AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
||||
AxiWBus, AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
||||
signals=["wdata", "wstrb", "wlast", "wvalid", "wready"],
|
||||
optional_signals=["wuser"],
|
||||
signal_widths={"wlast": 1}
|
||||
)
|
||||
|
||||
# Write response channel
|
||||
AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
|
||||
AxiBBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
|
||||
signals=["bid", "bresp", "bvalid", "bready"],
|
||||
optional_signals=["buser"],
|
||||
signal_widths={"bresp": 2}
|
||||
)
|
||||
|
||||
# Read address channel
|
||||
AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||
AxiARBus, AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arprot", "arvalid", "arready"],
|
||||
optional_signals=["arlock", "arcache", "arqos", "arregion", "aruser"],
|
||||
signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1,
|
||||
@@ -55,8 +55,79 @@ AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||
)
|
||||
|
||||
# Read data channel
|
||||
AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
||||
AxiRBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
||||
signals=["rid", "rdata", "rresp", "rlast", "rvalid", "rready"],
|
||||
optional_signals=["ruser"],
|
||||
signal_widths={"rresp": 2, "rlast": 1}
|
||||
)
|
||||
|
||||
|
||||
class AxiWriteBus:
|
||||
def __init__(self, aw=None, w=None, b=None):
|
||||
self.aw = aw
|
||||
self.w = w
|
||||
self.b = b
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
aw = AxiAWBus.from_entity(entity, **kwargs)
|
||||
w = AxiWBus.from_entity(entity, **kwargs)
|
||||
b = AxiBBus.from_entity(entity, **kwargs)
|
||||
return cls(aw, w, b)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
aw = AxiAWBus.from_prefix(entity, prefix, **kwargs)
|
||||
w = AxiWBus.from_prefix(entity, prefix, **kwargs)
|
||||
b = AxiBBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(aw, w, b)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, aw, w, b):
|
||||
return cls(aw, w, b)
|
||||
|
||||
|
||||
class AxiReadBus:
|
||||
def __init__(self, ar=None, r=None):
|
||||
self.ar = ar
|
||||
self.r = r
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
ar = AxiARBus.from_entity(entity, **kwargs)
|
||||
r = AxiRBus.from_entity(entity, **kwargs)
|
||||
return cls(ar, r)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
ar = AxiARBus.from_prefix(entity, prefix, **kwargs)
|
||||
r = AxiRBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(ar, r)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, ar, r):
|
||||
return cls(ar, r)
|
||||
|
||||
|
||||
class AxiBus:
|
||||
def __init__(self, write=None, read=None, **kwargs):
|
||||
self.write = write
|
||||
self.read = read
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
write = AxiWriteBus.from_entity(entity, **kwargs)
|
||||
read = AxiReadBus.from_entity(entity, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
write = AxiWriteBus.from_prefix(entity, prefix, **kwargs)
|
||||
read = AxiReadBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, aw, w, b, ar, r):
|
||||
write = AxiWriteBus.from_channels(aw, w, b)
|
||||
read = AxiReadBus.from_channels(ar, r)
|
||||
return cls(write, read)
|
||||
|
||||
@@ -31,6 +31,7 @@ from cocotb.triggers import Event
|
||||
from .version import __version__
|
||||
from .constants import AxiBurstType, AxiLockType, AxiProt, AxiResp
|
||||
from .axi_channels import AxiAWSource, AxiWSource, AxiBSink, AxiARSource, AxiRSink
|
||||
from .reset import Reset
|
||||
|
||||
# AXI master write helper objects
|
||||
AxiWriteCmd = namedtuple("AxiWriteCmd", ["address", "data", "awid", "burst", "size",
|
||||
@@ -47,26 +48,23 @@ AxiReadRespCmd = namedtuple("AxiReadRespCmd", ["address", "length", "size", "cyc
|
||||
AxiReadResp = namedtuple("AxiReadResp", ["address", "data", "resp", "user"])
|
||||
|
||||
|
||||
class AxiMasterWrite(object):
|
||||
def __init__(self, entity, name, clock, reset=None, max_burst_len=256):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiMasterWrite(Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||
|
||||
self.log.info("AXI master (write)")
|
||||
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")
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.aw_channel = AxiAWSource(entity, name, clock, reset)
|
||||
self.w_channel = AxiWSource(entity, name, clock, reset)
|
||||
self.b_channel = AxiBSink(entity, name, clock, reset)
|
||||
self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level)
|
||||
self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level)
|
||||
self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level)
|
||||
|
||||
self.write_command_queue = deque()
|
||||
self.write_command_sync = Event()
|
||||
self.write_resp_queue = deque()
|
||||
self.write_resp_sync = Event()
|
||||
self.write_resp_set = set()
|
||||
|
||||
self.id_count = 2**len(self.aw_channel.bus.awid)
|
||||
self.cur_id = 0
|
||||
@@ -77,6 +75,8 @@ class AxiMasterWrite(object):
|
||||
self.int_write_resp_queue_list = [deque() for k in range(self.id_count)]
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
@@ -100,8 +100,10 @@ class AxiMasterWrite(object):
|
||||
|
||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
cocotb.fork(self._process_write_resp())
|
||||
self._process_write_cr = None
|
||||
self._process_write_resp_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL,
|
||||
cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None):
|
||||
@@ -132,6 +134,7 @@ class AxiMasterWrite(object):
|
||||
wuser = list(wuser)
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
cmd = AxiWriteCmd(address, bytearray(data), awid, burst, size, lock,
|
||||
cache, prot, qos, region, user, wuser, event)
|
||||
@@ -143,8 +146,7 @@ class AxiMasterWrite(object):
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.write_resp_sync.clear()
|
||||
await self.write_resp_sync.wait()
|
||||
await self._idle.wait()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return bool(self.write_resp_queue)
|
||||
@@ -198,6 +200,41 @@ class AxiMasterWrite(object):
|
||||
await self.write_qwords(address, [data], byteorder, awid, burst, size,
|
||||
lock, cache, prot, qos, region, user, wuser)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_write_cr is not None:
|
||||
self._process_write_cr.kill()
|
||||
self._process_write_cr = None
|
||||
if self._process_write_resp_cr is not None:
|
||||
self._process_write_resp_cr.kill()
|
||||
self._process_write_resp_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
if self._process_write_resp_cr is None:
|
||||
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
while self.write_command_queue:
|
||||
cmd = self.write_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while self.int_write_resp_command_queue:
|
||||
cmd = self.int_write_resp_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
self.write_resp_queue.clear()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
if not self.write_command_queue:
|
||||
@@ -277,7 +314,7 @@ class AxiMasterWrite(object):
|
||||
aw.awuser = cmd.user
|
||||
|
||||
self.active_id[awid] += 1
|
||||
await self.aw_channel.drive(aw)
|
||||
await self.aw_channel.send(aw)
|
||||
|
||||
self.log.info("Write burst start awid: 0x%x awaddr: 0x%08x awlen: %d awsize: %d awprot: %s",
|
||||
awid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||
@@ -360,26 +397,26 @@ class AxiMasterWrite(object):
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
class AxiMasterRead(object):
|
||||
def __init__(self, entity, name, clock, reset=None, max_burst_len=256):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
|
||||
class AxiMasterRead(Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||
|
||||
self.log.info("AXI master (read)")
|
||||
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")
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.ar_channel = AxiARSource(entity, name, clock, reset)
|
||||
self.r_channel = AxiRSink(entity, name, clock, reset)
|
||||
self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level)
|
||||
self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level)
|
||||
|
||||
self.read_command_queue = deque()
|
||||
self.read_command_sync = Event()
|
||||
self.read_data_queue = deque()
|
||||
self.read_data_sync = Event()
|
||||
self.read_data_set = set()
|
||||
|
||||
self.id_count = 2**len(self.ar_channel.bus.arid)
|
||||
self.cur_id = 0
|
||||
@@ -390,6 +427,8 @@ class AxiMasterRead(object):
|
||||
self.int_read_resp_queue_list = [deque() for k in range(self.id_count)]
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
@@ -411,8 +450,10 @@ class AxiMasterRead(object):
|
||||
|
||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
cocotb.fork(self._process_read_resp())
|
||||
self._process_read_cr = None
|
||||
self._process_read_resp_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
||||
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
|
||||
@@ -439,6 +480,7 @@ class AxiMasterRead(object):
|
||||
prot = AxiProt(prot)
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||
self.read_command_queue.append(cmd)
|
||||
@@ -449,8 +491,7 @@ class AxiMasterRead(object):
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.read_data_sync.clear()
|
||||
await self.read_data_sync.wait()
|
||||
await self._idle.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return bool(self.read_data_queue)
|
||||
@@ -504,6 +545,40 @@ class AxiMasterRead(object):
|
||||
return (await self.read_qwords(address, 1, byteorder, arid, burst, size,
|
||||
lock, cache, prot, qos, region, user))[0]
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_read_cr is not None:
|
||||
self._process_read_cr.kill()
|
||||
self._process_read_cr = None
|
||||
if self._process_read_resp_cr is not None:
|
||||
self._process_read_resp_cr.kill()
|
||||
self._process_read_resp_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
if self._process_read_resp_cr is None:
|
||||
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
while self.read_command_queue:
|
||||
cmd = self.read_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while self.int_read_resp_command_queue:
|
||||
cmd = self.int_read_resp_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
self.read_data_queue.clear()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
if not self.read_command_queue:
|
||||
@@ -560,7 +635,7 @@ class AxiMasterRead(object):
|
||||
ar.aruser = cmd.user
|
||||
|
||||
self.active_id[arid] += 1
|
||||
await self.ar_channel.drive(ar)
|
||||
await self.ar_channel.send(ar)
|
||||
|
||||
self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s",
|
||||
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||
@@ -657,14 +732,17 @@ class AxiMasterRead(object):
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
class AxiMaster(object):
|
||||
def __init__(self, entity, name, clock, reset=None, max_burst_len=256):
|
||||
|
||||
class AxiMaster:
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
self.write_if = AxiMasterWrite(entity, name, clock, reset, max_burst_len)
|
||||
self.read_if = AxiMasterRead(entity, name, clock, reset, max_burst_len)
|
||||
self.write_if = AxiMasterWrite(bus.write, clock, reset, reset_active_level, max_burst_len)
|
||||
self.read_if = AxiMasterRead(bus.read, clock, reset, reset_active_level, max_burst_len)
|
||||
|
||||
def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
||||
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
|
||||
|
||||
@@ -30,11 +30,12 @@ from .version import __version__
|
||||
from .constants import AxiBurstType, AxiProt, AxiResp
|
||||
from .axi_channels import AxiAWSink, AxiWSink, AxiBSource, AxiARSink, AxiRSource
|
||||
from .memory import Memory
|
||||
from .reset import Reset
|
||||
|
||||
|
||||
class AxiRamWrite(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiRamWrite(Memory, Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||
|
||||
self.log.info("AXI RAM model (write)")
|
||||
self.log.info("cocotbext-axi version %s", __version__)
|
||||
@@ -43,13 +44,9 @@ class AxiRamWrite(Memory):
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.aw_channel = AxiAWSink(entity, name, clock, reset)
|
||||
self.w_channel = AxiWSink(entity, name, clock, reset)
|
||||
self.b_channel = AxiBSource(entity, name, clock, reset)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self.aw_channel = AxiAWSink(bus.aw, clock, reset, reset_active_level)
|
||||
self.w_channel = AxiWSink(bus.w, clock, reset, reset_active_level)
|
||||
self.b_channel = AxiBSource(bus.b, clock, reset, reset_active_level)
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
@@ -68,7 +65,24 @@ class AxiRamWrite(Memory):
|
||||
|
||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
self._process_write_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_write_cr is not None:
|
||||
self._process_write_cr.kill()
|
||||
self._process_write_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
@@ -142,9 +156,9 @@ class AxiRamWrite(Memory):
|
||||
await self.b_channel.send(b)
|
||||
|
||||
|
||||
class AxiRamRead(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiRamRead(Memory, Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||
|
||||
self.log.info("AXI RAM model (read)")
|
||||
self.log.info("cocotbext-axi version %s", __version__)
|
||||
@@ -153,12 +167,8 @@ class AxiRamRead(Memory):
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.ar_channel = AxiARSink(entity, name, clock, reset)
|
||||
self.r_channel = AxiRSource(entity, name, clock, reset)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self.ar_channel = AxiARSink(bus.ar, clock, reset, reset_active_level)
|
||||
self.r_channel = AxiRSource(bus.r, clock, reset, reset_active_level)
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
@@ -175,7 +185,23 @@ class AxiRamRead(Memory):
|
||||
|
||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
self._process_read_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_read_cr is not None:
|
||||
self._process_read_cr.kill()
|
||||
self._process_read_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
@@ -236,11 +262,11 @@ class AxiRamRead(Memory):
|
||||
|
||||
|
||||
class AxiRam(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.write_if = AxiRamWrite(entity, name, clock, reset, mem=self.mem)
|
||||
self.read_if = AxiRamRead(entity, name, clock, reset, mem=self.mem)
|
||||
self.write_if = AxiRamWrite(bus.write, clock, reset, reset_active_level, mem=self.mem)
|
||||
self.read_if = AxiRamRead(bus.read, clock, reset, reset_active_level, mem=self.mem)
|
||||
|
||||
@@ -25,30 +25,101 @@ THE SOFTWARE.
|
||||
from .stream import define_stream
|
||||
|
||||
# Write address channel
|
||||
AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
||||
AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
||||
signals=["awaddr", "awprot", "awvalid", "awready"],
|
||||
signal_widths={"awprot": 3}
|
||||
)
|
||||
|
||||
# Write data channel
|
||||
AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
||||
AxiLiteWBus, AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
||||
signals=["wdata", "wstrb", "wvalid", "wready"]
|
||||
)
|
||||
|
||||
# Write response channel
|
||||
AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
|
||||
AxiLiteBBus, AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
|
||||
signals=["bresp", "bvalid", "bready"],
|
||||
signal_widths={"bresp": 2}
|
||||
)
|
||||
|
||||
# Read address channel
|
||||
AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
||||
AxiLiteARBus, AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
||||
signals=["araddr", "arprot", "arvalid", "arready"],
|
||||
signal_widths={"arprot": 3}
|
||||
)
|
||||
|
||||
# Read data channel
|
||||
AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
||||
AxiLiteRBus, AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
||||
signals=["rdata", "rresp", "rvalid", "rready"],
|
||||
signal_widths={"rresp": 2}
|
||||
)
|
||||
|
||||
|
||||
class AxiLiteWriteBus:
|
||||
def __init__(self, aw=None, w=None, b=None):
|
||||
self.aw = aw
|
||||
self.w = w
|
||||
self.b = b
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
aw = AxiLiteAWBus.from_entity(entity, **kwargs)
|
||||
w = AxiLiteWBus.from_entity(entity, **kwargs)
|
||||
b = AxiLiteBBus.from_entity(entity, **kwargs)
|
||||
return cls(aw, w, b)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
aw = AxiLiteAWBus.from_prefix(entity, prefix, **kwargs)
|
||||
w = AxiLiteWBus.from_prefix(entity, prefix, **kwargs)
|
||||
b = AxiLiteBBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(aw, w, b)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, aw, w, b):
|
||||
return cls(aw, w, b)
|
||||
|
||||
|
||||
class AxiLiteReadBus:
|
||||
def __init__(self, ar=None, r=None):
|
||||
self.ar = ar
|
||||
self.r = r
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
ar = AxiLiteARBus.from_entity(entity, **kwargs)
|
||||
r = AxiLiteRBus.from_entity(entity, **kwargs)
|
||||
return cls(ar, r)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
ar = AxiLiteARBus.from_prefix(entity, prefix, **kwargs)
|
||||
r = AxiLiteRBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(ar, r)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, ar, r):
|
||||
return cls(ar, r)
|
||||
|
||||
|
||||
class AxiLiteBus:
|
||||
def __init__(self, write=None, read=None, **kwargs):
|
||||
self.write = write
|
||||
self.read = read
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
write = AxiLiteWriteBus.from_entity(entity, **kwargs)
|
||||
read = AxiLiteReadBus.from_entity(entity, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
write = AxiLiteWriteBus.from_prefix(entity, prefix, **kwargs)
|
||||
read = AxiLiteReadBus.from_prefix(entity, prefix, **kwargs)
|
||||
return cls(write, read)
|
||||
|
||||
@classmethod
|
||||
def from_channels(cls, aw, w, b, ar, r):
|
||||
write = AxiLiteWriteBus.from_channels(aw, w, b)
|
||||
read = AxiLiteReadBus.from_channels(ar, r)
|
||||
return cls(write, read)
|
||||
|
||||
@@ -31,6 +31,7 @@ from cocotb.triggers import Event
|
||||
from .version import __version__
|
||||
from .constants import AxiProt, AxiResp
|
||||
from .axil_channels import AxiLiteAWSource, AxiLiteWSource, AxiLiteBSink, AxiLiteARSource, AxiLiteRSink
|
||||
from .reset import Reset
|
||||
|
||||
# AXI lite master write
|
||||
AxiLiteWriteCmd = namedtuple("AxiLiteWriteCmd", ["address", "data", "prot", "event"])
|
||||
@@ -43,31 +44,30 @@ AxiLiteReadRespCmd = namedtuple("AxiLiteReadRespCmd", ["address", "length", "cyc
|
||||
AxiLiteReadResp = namedtuple("AxiLiteReadResp", ["address", "data", "resp"])
|
||||
|
||||
|
||||
class AxiLiteMasterWrite(object):
|
||||
def __init__(self, entity, name, clock, reset=None):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiLiteMasterWrite(Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||
|
||||
self.log.info("AXI lite master (write)")
|
||||
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")
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.aw_channel = AxiLiteAWSource(entity, name, clock, reset)
|
||||
self.w_channel = AxiLiteWSource(entity, name, clock, reset)
|
||||
self.b_channel = AxiLiteBSink(entity, name, clock, reset)
|
||||
self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level)
|
||||
self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level)
|
||||
self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level)
|
||||
|
||||
self.write_command_queue = deque()
|
||||
self.write_command_sync = Event()
|
||||
self.write_resp_queue = deque()
|
||||
self.write_resp_sync = Event()
|
||||
self.write_resp_set = set()
|
||||
|
||||
self.int_write_resp_command_queue = deque()
|
||||
self.int_write_resp_command_sync = Event()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
@@ -82,14 +82,17 @@ class AxiLiteMasterWrite(object):
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
cocotb.fork(self._process_write_resp())
|
||||
self._process_write_cr = None
|
||||
self._process_write_resp_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
|
||||
if event is not None and not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
self.write_command_queue.append(AxiLiteWriteCmd(address, bytearray(data), prot, event))
|
||||
self.write_command_sync.set()
|
||||
@@ -99,8 +102,7 @@ class AxiLiteMasterWrite(object):
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.write_resp_sync.clear()
|
||||
await self.write_resp_sync.wait()
|
||||
await self._idle.wait()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return bool(self.write_resp_queue)
|
||||
@@ -141,6 +143,41 @@ class AxiLiteMasterWrite(object):
|
||||
async def write_qword(self, address, data, byteorder='little', prot=AxiProt.NONSECURE):
|
||||
await self.write_qwords(address, [data], byteorder, prot)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_write_cr is not None:
|
||||
self._process_write_cr.kill()
|
||||
self._process_write_cr = None
|
||||
if self._process_write_resp_cr is not None:
|
||||
self._process_write_resp_cr.kill()
|
||||
self._process_write_resp_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
if self._process_write_resp_cr is None:
|
||||
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
while self.write_command_queue:
|
||||
cmd = self.write_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while self.int_write_resp_command_queue:
|
||||
cmd = self.int_write_resp_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
self.write_resp_queue.clear()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
if not self.write_command_queue:
|
||||
@@ -193,7 +230,7 @@ class AxiLiteMasterWrite(object):
|
||||
w.wdata = val
|
||||
w.wstrb = strb
|
||||
|
||||
await self.aw_channel.drive(aw)
|
||||
await self.aw_channel.send(aw)
|
||||
await self.w_channel.send(w)
|
||||
|
||||
async def _process_write_resp(self):
|
||||
@@ -227,31 +264,33 @@ class AxiLiteMasterWrite(object):
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
class AxiLiteMasterRead(object):
|
||||
def __init__(self, entity, name, clock, reset=None):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
|
||||
class AxiLiteMasterRead(Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||
|
||||
self.log.info("AXI lite master (read)")
|
||||
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")
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.ar_channel = AxiLiteARSource(entity, name, clock, reset)
|
||||
self.r_channel = AxiLiteRSink(entity, name, clock, reset)
|
||||
self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level)
|
||||
self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level)
|
||||
|
||||
self.read_command_queue = deque()
|
||||
self.read_command_sync = Event()
|
||||
self.read_data_queue = deque()
|
||||
self.read_data_sync = Event()
|
||||
self.read_data_set = set()
|
||||
|
||||
self.int_read_resp_command_queue = deque()
|
||||
self.int_read_resp_command_sync = Event()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
@@ -264,14 +303,17 @@ class AxiLiteMasterRead(object):
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
cocotb.fork(self._process_read_resp())
|
||||
self._process_read_cr = None
|
||||
self._process_read_resp_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
||||
if event is not None and not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
self.read_command_queue.append(AxiLiteReadCmd(address, length, prot, event))
|
||||
self.read_command_sync.set()
|
||||
@@ -281,8 +323,7 @@ class AxiLiteMasterRead(object):
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.read_data_sync.clear()
|
||||
await self.read_data_sync.wait()
|
||||
await self._idle.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return bool(self.read_data_queue)
|
||||
@@ -323,6 +364,40 @@ class AxiLiteMasterRead(object):
|
||||
async def read_qword(self, address, byteorder='little', prot=AxiProt.NONSECURE):
|
||||
return (await self.read_qwords(address, 1, byteorder, prot))[0]
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_read_cr is not None:
|
||||
self._process_read_cr.kill()
|
||||
self._process_read_cr = None
|
||||
if self._process_read_resp_cr is not None:
|
||||
self._process_read_resp_cr.kill()
|
||||
self._process_read_resp_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
if self._process_read_resp_cr is None:
|
||||
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
while self.read_command_queue:
|
||||
cmd = self.read_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while self.int_read_resp_command_queue:
|
||||
cmd = self.int_read_resp_command_queue.popleft()
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
self.read_data_queue.clear()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
if not self.read_command_queue:
|
||||
@@ -347,7 +422,7 @@ class AxiLiteMasterRead(object):
|
||||
ar.araddr = word_addr + k*self.byte_width
|
||||
ar.arprot = cmd.prot
|
||||
|
||||
await self.ar_channel.drive(ar)
|
||||
await self.ar_channel.send(ar)
|
||||
|
||||
async def _process_read_resp(self):
|
||||
while True:
|
||||
@@ -397,14 +472,17 @@ class AxiLiteMasterRead(object):
|
||||
|
||||
self.in_flight_operations -= 1
|
||||
|
||||
if self.in_flight_operations == 0:
|
||||
self._idle.set()
|
||||
|
||||
class AxiLiteMaster(object):
|
||||
def __init__(self, entity, name, clock, reset=None):
|
||||
|
||||
class AxiLiteMaster:
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
self.write_if = AxiLiteMasterWrite(entity, name, clock, reset)
|
||||
self.read_if = AxiLiteMasterRead(entity, name, clock, reset)
|
||||
self.write_if = AxiLiteMasterWrite(bus.write, clock, reset, reset_active_level)
|
||||
self.read_if = AxiLiteMasterRead(bus.read, clock, reset, reset_active_level)
|
||||
|
||||
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
||||
self.read_if.init_read(address, length, prot, event)
|
||||
|
||||
@@ -30,11 +30,12 @@ from .version import __version__
|
||||
from .constants import AxiProt, AxiResp
|
||||
from .axil_channels import AxiLiteAWSink, AxiLiteWSink, AxiLiteBSource, AxiLiteARSink, AxiLiteRSource
|
||||
from .memory import Memory
|
||||
from .reset import Reset
|
||||
|
||||
|
||||
class AxiLiteRamWrite(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiLiteRamWrite(Memory, Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||
|
||||
self.log.info("AXI lite RAM model (write)")
|
||||
self.log.info("cocotbext-axi version %s", __version__)
|
||||
@@ -43,13 +44,9 @@ class AxiLiteRamWrite(Memory):
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.aw_channel = AxiLiteAWSink(entity, name, clock, reset)
|
||||
self.w_channel = AxiLiteWSink(entity, name, clock, reset)
|
||||
self.b_channel = AxiLiteBSource(entity, name, clock, reset)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self.aw_channel = AxiLiteAWSink(bus.aw, clock, reset, reset_active_level)
|
||||
self.w_channel = AxiLiteWSink(bus.w, clock, reset, reset_active_level)
|
||||
self.b_channel = AxiLiteBSource(bus.b, clock, reset, reset_active_level)
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
@@ -65,7 +62,24 @@ class AxiLiteRamWrite(Memory):
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
self._process_write_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_write_cr is not None:
|
||||
self._process_write_cr.kill()
|
||||
self._process_write_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
@@ -100,9 +114,9 @@ class AxiLiteRamWrite(Memory):
|
||||
await self.b_channel.send(b)
|
||||
|
||||
|
||||
class AxiLiteRamRead(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
class AxiLiteRamRead(Memory, Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||
|
||||
self.log.info("AXI lite RAM model (read)")
|
||||
self.log.info("cocotbext-axi version %s", __version__)
|
||||
@@ -111,12 +125,8 @@ class AxiLiteRamRead(Memory):
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.reset = reset
|
||||
|
||||
self.ar_channel = AxiLiteARSink(entity, name, clock, reset)
|
||||
self.r_channel = AxiLiteRSource(entity, name, clock, reset)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self.ar_channel = AxiLiteARSink(bus.ar, clock, reset, reset_active_level)
|
||||
self.r_channel = AxiLiteRSource(bus.r, clock, reset, reset_active_level)
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
@@ -130,7 +140,23 @@ class AxiLiteRamRead(Memory):
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
self._process_read_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._process_read_cr is not None:
|
||||
self._process_read_cr.kill()
|
||||
self._process_read_cr = None
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
@@ -156,11 +182,11 @@ class AxiLiteRamRead(Memory):
|
||||
|
||||
|
||||
class AxiLiteRam(Memory):
|
||||
def __init__(self, entity, name, clock, reset=None, size=1024, mem=None, *args, **kwargs):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, size=1024, mem=None, *args, **kwargs):
|
||||
self.write_if = None
|
||||
self.read_if = None
|
||||
|
||||
super().__init__(size, mem, *args, **kwargs)
|
||||
|
||||
self.write_if = AxiLiteRamWrite(entity, name, clock, reset, mem=self.mem)
|
||||
self.read_if = AxiLiteRamRead(entity, name, clock, reset, mem=self.mem)
|
||||
self.write_if = AxiLiteRamWrite(bus.write, clock, reset, reset_active_level, mem=self.mem)
|
||||
self.read_if = AxiLiteRamRead(bus.read, clock, reset, reset_active_level, mem=self.mem)
|
||||
|
||||
@@ -26,19 +26,24 @@ import logging
|
||||
from collections import deque
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, ReadOnly, Timer, First, Event
|
||||
from cocotb.triggers import RisingEdge, Timer, First, Event
|
||||
from cocotb.utils import get_sim_time
|
||||
from cocotb.bus import Bus
|
||||
|
||||
from .version import __version__
|
||||
from .reset import Reset
|
||||
|
||||
|
||||
class AxiStreamFrame(object):
|
||||
def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None):
|
||||
class AxiStreamFrame:
|
||||
def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None, tx_complete=None):
|
||||
self.tdata = bytearray()
|
||||
self.tkeep = None
|
||||
self.tid = None
|
||||
self.tdest = None
|
||||
self.tuser = None
|
||||
self.sim_time_start = None
|
||||
self.sim_time_end = None
|
||||
self.tx_complete = None
|
||||
|
||||
if type(tdata) is AxiStreamFrame:
|
||||
if type(tdata.tdata) is bytearray:
|
||||
@@ -62,6 +67,9 @@ class AxiStreamFrame(object):
|
||||
self.tuser = tdata.tuser
|
||||
else:
|
||||
self.tuser = list(tdata.tuser)
|
||||
self.sim_time_start = tdata.sim_time_start
|
||||
self.sim_time_end = tdata.sim_time_end
|
||||
self.tx_complete = tdata.tx_complete
|
||||
elif type(tdata) in (bytes, bytearray):
|
||||
self.tdata = bytearray(tdata)
|
||||
self.tkeep = tkeep
|
||||
@@ -75,6 +83,9 @@ class AxiStreamFrame(object):
|
||||
self.tdest = tdest
|
||||
self.tuser = tuser
|
||||
|
||||
if tx_complete is not None:
|
||||
self.tx_complete = tx_complete
|
||||
|
||||
def normalize(self):
|
||||
# normalize all sideband signals to the same size as tdata
|
||||
n = len(self.tdata)
|
||||
@@ -144,6 +155,12 @@ class AxiStreamFrame(object):
|
||||
elif all(self.tuser[0] == i for i in self.tuser):
|
||||
self.tuser = self.tuser[0]
|
||||
|
||||
def handle_tx_complete(self):
|
||||
if isinstance(self.tx_complete, Event):
|
||||
self.tx_complete.set(self)
|
||||
elif callable(self.tx_complete):
|
||||
self.tx_complete(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AxiStreamFrame):
|
||||
return False
|
||||
@@ -195,11 +212,13 @@ class AxiStreamFrame(object):
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"{type(self).__name__}(tdata={repr(self.tdata)}, "
|
||||
f"tkeep={repr(self.tkeep)}, "
|
||||
f"tid={repr(self.tid)}, "
|
||||
f"tdest={repr(self.tdest)}, "
|
||||
f"tuser={repr(self.tuser)})"
|
||||
f"{type(self).__name__}(tdata={self.tdata!r}, "
|
||||
f"tkeep={self.tkeep!r}, "
|
||||
f"tid={self.tid!r}, "
|
||||
f"tdest={self.tdest!r}, "
|
||||
f"tuser={self.tuser!r}, "
|
||||
f"sim_time_start={self.sim_time_start!r}, "
|
||||
f"sim_time_end={self.sim_time_end!r})"
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
@@ -212,19 +231,44 @@ class AxiStreamFrame(object):
|
||||
return bytes(self.tdata)
|
||||
|
||||
|
||||
class AxiStreamSource(object):
|
||||
class AxiStreamBus(Bus):
|
||||
|
||||
_signals = ["tdata"]
|
||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
self.entity = entity
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
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.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
self.log.info("AXI stream source")
|
||||
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")
|
||||
@@ -233,10 +277,7 @@ class AxiStreamSource(object):
|
||||
|
||||
self.active = False
|
||||
self.queue = deque()
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
self.queue_sync = Event()
|
||||
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
@@ -244,21 +285,17 @@ class AxiStreamSource(object):
|
||||
self.width = len(self.bus.tdata)
|
||||
self.byte_lanes = 1
|
||||
|
||||
self.reset = reset
|
||||
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)
|
||||
|
||||
self.bus.tdata.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tlast"):
|
||||
self.bus.tlast.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
self.bus.tkeep.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tid"):
|
||||
self.bus.tid.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tdest"):
|
||||
self.bus.tdest.setimmediatevalue(0)
|
||||
if hasattr(self.bus, "tuser"):
|
||||
self.bus.tuser.setimmediatevalue(0)
|
||||
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)
|
||||
@@ -275,7 +312,7 @@ class AxiStreamSource(object):
|
||||
self.byte_size = self.width // self.byte_lanes
|
||||
self.byte_mask = 2**self.byte_size-1
|
||||
|
||||
self.log.info("AXI stream source configuration:")
|
||||
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")
|
||||
@@ -302,7 +339,73 @@ class AxiStreamSource(object):
|
||||
raise ValueError(f"Bus does not evenly divide into byte lanes "
|
||||
f"({self.byte_lanes} * {self.byte_size} != {self.width})")
|
||||
|
||||
cocotb.fork(self._run())
|
||||
self._run_cr = None
|
||||
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
|
||||
def clear(self):
|
||||
self.queue.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
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.fork(self._run())
|
||||
|
||||
self.active = False
|
||||
|
||||
async def _run(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AxiStreamPause:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
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 AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||
|
||||
_type = "source"
|
||||
|
||||
_init_x = True
|
||||
|
||||
_valid_init = 0
|
||||
_ready_init = None
|
||||
|
||||
async def send(self, frame):
|
||||
self.send_nowait(frame)
|
||||
@@ -319,12 +422,6 @@ class AxiStreamSource(object):
|
||||
def write_nowait(self, data):
|
||||
self.send_nowait(data)
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
|
||||
def idle(self):
|
||||
return self.empty() and not self.active
|
||||
|
||||
@@ -332,34 +429,9 @@ class AxiStreamSource(object):
|
||||
while not self.idle():
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
def set_pause_generator(self, generator=None):
|
||||
if self._pause_cr is not None:
|
||||
self._pause_cr.kill()
|
||||
self._pause_cr = None
|
||||
def _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
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(self):
|
||||
frame = None
|
||||
self.active = False
|
||||
|
||||
while True:
|
||||
await ReadOnly()
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
await RisingEdge(self.clock)
|
||||
frame = None
|
||||
self.active = False
|
||||
self.bus.tdata <= 0
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid <= 0
|
||||
@@ -373,15 +445,25 @@ class AxiStreamSource(object):
|
||||
self.bus.tdest <= 0
|
||||
if hasattr(self.bus, "tuser"):
|
||||
self.bus.tuser <= 0
|
||||
continue
|
||||
|
||||
async def _run(self):
|
||||
frame = None
|
||||
self.active = False
|
||||
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
|
||||
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
||||
if frame is None and self.queue:
|
||||
frame = self.queue.popleft()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
self.queue_occupancy_frames -= 1
|
||||
frame.sim_time_start = get_sim_time()
|
||||
frame.sim_time_end = None
|
||||
self.log.info("TX frame: %s", frame)
|
||||
frame.normalize()
|
||||
self.active = True
|
||||
@@ -403,6 +485,8 @@ class AxiStreamSource(object):
|
||||
|
||||
if len(frame.tdata) == 0:
|
||||
tlast_val = 1
|
||||
frame.sim_time_end = get_sim_time()
|
||||
frame.handle_tx_complete()
|
||||
frame = None
|
||||
break
|
||||
|
||||
@@ -426,101 +510,27 @@ class AxiStreamSource(object):
|
||||
self.bus.tlast <= 0
|
||||
self.active = bool(frame)
|
||||
|
||||
async def _run_pause(self):
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
class AxiStreamMonitor(AxiStreamBase):
|
||||
|
||||
class AxiStreamSink(object):
|
||||
_type = "monitor"
|
||||
|
||||
_signals = ["tdata"]
|
||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
||||
_init_x = False
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
self.entity = entity
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
_valid_init = None
|
||||
_ready_init = None
|
||||
|
||||
self.log.info("AXI stream sink")
|
||||
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")
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
||||
byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
||||
|
||||
self.active = False
|
||||
self.queue = deque()
|
||||
self.sync = Event()
|
||||
self.read_queue = []
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
self.queue_occupancy_limit_bytes = None
|
||||
self.queue_occupancy_limit_frames = None
|
||||
|
||||
self.width = len(self.bus.tdata)
|
||||
self.byte_lanes = 1
|
||||
|
||||
self.reset = reset
|
||||
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready.setimmediatevalue(0)
|
||||
|
||||
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 sink configuration:")
|
||||
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})")
|
||||
|
||||
cocotb.fork(self._run())
|
||||
|
||||
async def recv(self, compact=True):
|
||||
while self.empty():
|
||||
self.sync.clear()
|
||||
await self.sync.wait()
|
||||
self.queue_sync.clear()
|
||||
await self.queue_sync.wait()
|
||||
return self.recv_nowait(compact)
|
||||
|
||||
def recv_nowait(self, compact=True):
|
||||
@@ -549,65 +559,37 @@ class AxiStreamSink(object):
|
||||
del self.read_queue[:count]
|
||||
return data
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
|
||||
def full(self):
|
||||
if self.queue_occupancy_limit_bytes and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
||||
return True
|
||||
elif self.queue_occupancy_limit_frames 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
|
||||
self.sync.clear()
|
||||
self.queue_sync.clear()
|
||||
if timeout:
|
||||
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
||||
await First(self.queue_sync.wait(), Timer(timeout, timeout_unit))
|
||||
else:
|
||||
await self.sync.wait()
|
||||
|
||||
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)
|
||||
await self.queue_sync.wait()
|
||||
|
||||
async def _run(self):
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame = None
|
||||
self.active = False
|
||||
|
||||
while True:
|
||||
await ReadOnly()
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
await RisingEdge(self.clock)
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
self.active = False
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready <= 0
|
||||
continue
|
||||
|
||||
if tready_sample and tvalid_sample:
|
||||
if frame is None:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame.sim_time_start = get_sim_time()
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
|
||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||
@@ -621,174 +603,68 @@ class AxiStreamSink(object):
|
||||
frame.tuser.append(self.bus.tuser.value.integer)
|
||||
|
||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
||||
if self.byte_size == 8:
|
||||
frame.tdata = bytearray(frame.tdata)
|
||||
|
||||
frame.sim_time_end = get_sim_time()
|
||||
self.log.info("RX frame: %s", frame)
|
||||
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
self.queue.append(frame)
|
||||
self.sync.set()
|
||||
self.queue_sync.set()
|
||||
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame = None
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||
|
||||
_type = "sink"
|
||||
|
||||
_init_x = False
|
||||
|
||||
_valid_init = None
|
||||
_ready_init = 0
|
||||
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
||||
byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||
|
||||
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
||||
|
||||
self.queue_occupancy_limit_bytes = -1
|
||||
self.queue_occupancy_limit_frames = -1
|
||||
|
||||
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 _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready <= (not self.full() and not self.pause)
|
||||
|
||||
async def _run_pause(self):
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
|
||||
class AxiStreamMonitor(object):
|
||||
|
||||
_signals = ["tdata"]
|
||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
self.entity = entity
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
self.log.info("AXI stream monitor")
|
||||
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 = deque()
|
||||
self.sync = Event()
|
||||
self.read_queue = []
|
||||
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
|
||||
self.width = len(self.bus.tdata)
|
||||
self.byte_lanes = 1
|
||||
|
||||
self.reset = reset
|
||||
|
||||
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 monitor configuration:")
|
||||
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})")
|
||||
|
||||
cocotb.fork(self._run())
|
||||
|
||||
async def recv(self, compact=True):
|
||||
while self.empty():
|
||||
self.sync.clear()
|
||||
await self.sync.wait()
|
||||
return self.recv_nowait(compact)
|
||||
|
||||
def recv_nowait(self, compact=True):
|
||||
if self.queue:
|
||||
frame = self.queue.popleft()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
self.queue_occupancy_frames -= 1
|
||||
if compact:
|
||||
frame.compact()
|
||||
return frame
|
||||
return None
|
||||
|
||||
async def read(self, count=-1):
|
||||
while not self.read_queue:
|
||||
frame = await self.recv(compact=True)
|
||||
self.read_queue.extend(frame.tdata)
|
||||
return self.read_nowait(count)
|
||||
|
||||
def read_nowait(self, count=-1):
|
||||
while not self.empty():
|
||||
frame = self.recv_nowait(compact=True)
|
||||
self.read_queue.extend(frame.tdata)
|
||||
if count < 0:
|
||||
count = len(self.read_queue)
|
||||
data = self.read_queue[:count]
|
||||
del self.read_queue[:count]
|
||||
return data
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
|
||||
def idle(self):
|
||||
return not self.active
|
||||
|
||||
async def wait(self, timeout=0, timeout_unit='ns'):
|
||||
if not self.empty():
|
||||
return
|
||||
self.sync.clear()
|
||||
if timeout:
|
||||
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
||||
else:
|
||||
await self.sync.wait()
|
||||
self.bus.tready <= 0
|
||||
|
||||
async def _run(self):
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame = None
|
||||
self.active = False
|
||||
|
||||
while True:
|
||||
await ReadOnly()
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# read handshake signals
|
||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
await RisingEdge(self.clock)
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
self.active = False
|
||||
continue
|
||||
|
||||
if tready_sample and tvalid_sample:
|
||||
if frame is None:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame.sim_time_start = get_sim_time()
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
|
||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||
@@ -802,14 +678,16 @@ class AxiStreamMonitor(object):
|
||||
frame.tuser.append(self.bus.tuser.value.integer)
|
||||
|
||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
||||
if self.byte_size == 8:
|
||||
frame.tdata = bytearray(frame.tdata)
|
||||
|
||||
frame.sim_time_end = get_sim_time()
|
||||
self.log.info("RX frame: %s", frame)
|
||||
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
self.queue.append(frame)
|
||||
self.sync.set()
|
||||
self.queue_sync.set()
|
||||
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame = None
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready <= (not self.full() and not self.pause)
|
||||
|
||||
@@ -27,7 +27,7 @@ import mmap
|
||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||
|
||||
|
||||
class Memory(object):
|
||||
class Memory:
|
||||
def __init__(self, size=1024, mem=None, *args, **kwargs):
|
||||
if mem is not None:
|
||||
self.mem = mem
|
||||
|
||||
66
cocotbext/axi/reset.py
Normal file
66
cocotbext/axi/reset.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
|
||||
Copyright (c) 2020 Alex Forencich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
"""
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, FallingEdge
|
||||
|
||||
|
||||
class Reset:
|
||||
def _init_reset(self, reset_signal=None, active_level=True):
|
||||
self._local_reset = False
|
||||
self._ext_reset = False
|
||||
self._reset_state = True
|
||||
|
||||
if reset_signal is not None:
|
||||
cocotb.fork(self._run_reset(reset_signal, bool(active_level)))
|
||||
|
||||
self._update_reset()
|
||||
|
||||
def assert_reset(self, val=None):
|
||||
if val is None:
|
||||
self.assert_reset(True)
|
||||
self.assert_reset(False)
|
||||
else:
|
||||
self._local_reset = bool(val)
|
||||
self._update_reset()
|
||||
|
||||
def _update_reset(self):
|
||||
new_state = self._local_reset or self._ext_reset
|
||||
if self._reset_state != new_state:
|
||||
self._reset_state = new_state
|
||||
self._handle_reset(new_state)
|
||||
|
||||
def _handle_reset(self, state):
|
||||
pass
|
||||
|
||||
async def _run_reset(self, reset_signal, active_level):
|
||||
while True:
|
||||
if bool(reset_signal.value):
|
||||
await FallingEdge(reset_signal)
|
||||
self._ext_reset = not active_level
|
||||
self._update_reset()
|
||||
else:
|
||||
await RisingEdge(reset_signal)
|
||||
self._ext_reset = active_level
|
||||
self._update_reset()
|
||||
@@ -26,11 +26,30 @@ import logging
|
||||
from collections import deque
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, ReadOnly, Event, First, Timer
|
||||
from cocotb.triggers import RisingEdge, Event, First, Timer
|
||||
from cocotb.bus import Bus
|
||||
|
||||
from .reset import Reset
|
||||
|
||||
class StreamTransaction(object):
|
||||
|
||||
class StreamBus(Bus):
|
||||
|
||||
_signals = ["data"]
|
||||
_optional_signals = []
|
||||
|
||||
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity, **kwargs):
|
||||
return cls(entity, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_prefix(cls, entity, prefix, **kwargs):
|
||||
return cls(entity, prefix, **kwargs)
|
||||
|
||||
|
||||
class StreamTransaction:
|
||||
|
||||
_signals = ["data"]
|
||||
|
||||
@@ -48,7 +67,7 @@ class StreamTransaction(object):
|
||||
return f"{type(self).__name__}({', '.join(f'{s}={int(getattr(self, s))}' for s in self._signals)})"
|
||||
|
||||
|
||||
class StreamBase(object):
|
||||
class StreamBase(Reset):
|
||||
|
||||
_signals = ["data", "valid", "ready"]
|
||||
_optional_signals = []
|
||||
@@ -63,16 +82,18 @@ class StreamBase(object):
|
||||
_ready_init = None
|
||||
|
||||
_transaction_obj = StreamTransaction
|
||||
_bus_obj = StreamBus
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
||||
self.log = logging.getLogger(f"cocotb.{entity._name}.{name}")
|
||||
self.entity = entity
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, *args, **kwargs):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.bus = Bus(self.entity, name, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||
self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.active = False
|
||||
|
||||
self.queue = deque()
|
||||
self.queue_sync = Event()
|
||||
|
||||
@@ -98,6 +119,10 @@ class StreamBase(object):
|
||||
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 len(self.queue)
|
||||
|
||||
@@ -107,8 +132,24 @@ class StreamBase(object):
|
||||
def clear(self):
|
||||
self.queue.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
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.fork(self._run())
|
||||
|
||||
class StreamPause(object):
|
||||
self.active = False
|
||||
|
||||
async def _run(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class StreamPause:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -137,44 +178,16 @@ class StreamPause(object):
|
||||
|
||||
class StreamSource(StreamBase, StreamPause):
|
||||
|
||||
_signals = ["data", "valid", "ready"]
|
||||
_optional_signals = []
|
||||
|
||||
_signal_widths = {"valid": 1, "ready": 1}
|
||||
|
||||
_init_x = True
|
||||
|
||||
_valid_signal = "valid"
|
||||
_valid_init = 0
|
||||
_ready_signal = "ready"
|
||||
_ready_init = None
|
||||
|
||||
_transaction_obj = StreamTransaction
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
||||
super().__init__(entity, name, clock, reset, *args, **kwargs)
|
||||
|
||||
self.drive_obj = None
|
||||
self.drive_sync = Event()
|
||||
|
||||
self.active = False
|
||||
|
||||
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, obj):
|
||||
self.send_nowait(obj)
|
||||
|
||||
def send_nowait(self, obj):
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
|
||||
def idle(self):
|
||||
return self.empty() and not self.active
|
||||
@@ -183,142 +196,39 @@ class StreamSource(StreamBase, StreamPause):
|
||||
while not self.idle():
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
def clear(self):
|
||||
self.queue.clear()
|
||||
self.drive_obj = None
|
||||
self.drive_sync.set()
|
||||
def _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
async def _run_source(self):
|
||||
if self.valid is not None:
|
||||
self.valid <= 0
|
||||
|
||||
async def _run(self):
|
||||
while True:
|
||||
await ReadOnly()
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# read handshake signals
|
||||
ready_sample = self.ready is None or self.ready.value
|
||||
valid_sample = self.valid is None or self.valid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
await RisingEdge(self.clock)
|
||||
self.clear()
|
||||
if self.valid is not None:
|
||||
self.valid <= 0
|
||||
self.active = False
|
||||
continue
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
if (ready_sample and valid_sample) or (not valid_sample):
|
||||
if self.drive_obj and not self.pause:
|
||||
self.bus.drive(self.drive_obj)
|
||||
self.drive_obj = None
|
||||
self.drive_sync.set()
|
||||
if self.queue and not self.pause:
|
||||
self.bus.drive(self.queue.popleft())
|
||||
if self.valid is not None:
|
||||
self.valid <= 1
|
||||
self.active = True
|
||||
else:
|
||||
if self.valid is not None:
|
||||
self.valid <= 0
|
||||
self.active = bool(self.drive_obj)
|
||||
|
||||
async def _run(self):
|
||||
while True:
|
||||
while not self.queue:
|
||||
self.queue_sync.clear()
|
||||
await self.queue_sync.wait()
|
||||
|
||||
await self.drive(self.queue.popleft())
|
||||
|
||||
|
||||
class StreamSink(StreamBase, StreamPause):
|
||||
|
||||
_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 = 0
|
||||
|
||||
_transaction_obj = StreamTransaction
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
||||
super().__init__(entity, name, clock, reset, *args, **kwargs)
|
||||
|
||||
cocotb.fork(self._run_sink())
|
||||
|
||||
async def recv(self):
|
||||
while self.empty():
|
||||
self.queue_sync.clear()
|
||||
await self.queue_sync.wait()
|
||||
return self.recv_nowait()
|
||||
|
||||
def recv_nowait(self):
|
||||
if self.queue:
|
||||
return self.queue.popleft()
|
||||
return None
|
||||
|
||||
async def wait(self, timeout=0, timeout_unit=None):
|
||||
if not self.empty():
|
||||
return
|
||||
self.queue_sync.clear()
|
||||
if timeout:
|
||||
await First(self.queue_sync.wait(), Timer(timeout, timeout_unit))
|
||||
else:
|
||||
await self.queue_sync.wait()
|
||||
|
||||
def callback(self, obj):
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
|
||||
async def _run_sink(self):
|
||||
while True:
|
||||
await ReadOnly()
|
||||
|
||||
# read handshake signals
|
||||
ready_sample = self.ready is None or self.ready.value
|
||||
valid_sample = self.valid is None or self.valid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
await RisingEdge(self.clock)
|
||||
self.clear()
|
||||
if self.ready is not None:
|
||||
self.ready <= 0
|
||||
continue
|
||||
|
||||
if ready_sample and valid_sample:
|
||||
obj = self._transaction_obj()
|
||||
self.bus.sample(obj)
|
||||
self.callback(obj)
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
if self.ready is not None:
|
||||
self.ready <= (not self.pause)
|
||||
self.active = bool(self.queue)
|
||||
|
||||
|
||||
class StreamMonitor(StreamBase):
|
||||
|
||||
_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
|
||||
|
||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
||||
super().__init__(entity, name, clock, reset, *args, **kwargs)
|
||||
|
||||
cocotb.fork(self._run_monitor())
|
||||
|
||||
async def recv(self):
|
||||
while self.empty():
|
||||
self.queue_sync.clear()
|
||||
@@ -330,12 +240,6 @@ class StreamMonitor(StreamBase):
|
||||
return self.queue.popleft()
|
||||
return None
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
|
||||
async def wait(self, timeout=0, timeout_unit=None):
|
||||
if not self.empty():
|
||||
return
|
||||
@@ -345,29 +249,61 @@ class StreamMonitor(StreamBase):
|
||||
else:
|
||||
await self.queue_sync.wait()
|
||||
|
||||
def callback(self, obj):
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
|
||||
async def _run_monitor(self):
|
||||
async def _run(self):
|
||||
while True:
|
||||
await ReadOnly()
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
# read handshake signals
|
||||
ready_sample = self.ready is None or self.ready.value
|
||||
valid_sample = self.valid is None or self.valid.value
|
||||
|
||||
if self.reset is not None and self.reset.value:
|
||||
if ready_sample and valid_sample:
|
||||
obj = self._transaction_obj()
|
||||
self.bus.sample(obj)
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
|
||||
|
||||
class StreamSink(StreamMonitor, StreamPause):
|
||||
|
||||
_init_x = False
|
||||
|
||||
_valid_init = None
|
||||
_ready_init = 0
|
||||
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, *args, **kwargs):
|
||||
super().__init__(bus, clock, reset, reset_active_level, *args, **kwargs)
|
||||
|
||||
self.queue_occupancy_limit = -1
|
||||
|
||||
def full(self):
|
||||
if self.queue_occupancy_limit > 0 and len(self.queue) >= self.queue_occupancy_limit:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
if self.ready is not None:
|
||||
self.ready <= 0
|
||||
|
||||
async def _run(self):
|
||||
while True:
|
||||
await RisingEdge(self.clock)
|
||||
self.clear()
|
||||
continue
|
||||
|
||||
# read handshake signals
|
||||
ready_sample = self.ready is None or self.ready.value
|
||||
valid_sample = self.valid is None or self.valid.value
|
||||
|
||||
if ready_sample and valid_sample:
|
||||
obj = self._transaction_obj()
|
||||
self.bus.sample(obj)
|
||||
self.callback(obj)
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
if self.ready is not None:
|
||||
self.ready <= (not self.full() and not self.pause)
|
||||
|
||||
|
||||
def define_stream(name, signals, optional_signals=None, valid_signal=None, ready_signal=None, signal_widths=None):
|
||||
@@ -408,6 +344,11 @@ def define_stream(name, signals, optional_signals=None, valid_signal=None, ready
|
||||
if s not in (ready_signal, valid_signal):
|
||||
filtered_signals.append(s)
|
||||
|
||||
attrib = {}
|
||||
attrib['_signals'] = signals
|
||||
attrib['_optional_signals'] = optional_signals
|
||||
bus = type(name+"Bus", (StreamBus,), attrib)
|
||||
|
||||
attrib = {s: 0 for s in filtered_signals}
|
||||
attrib['_signals'] = filtered_signals
|
||||
|
||||
@@ -420,9 +361,10 @@ def define_stream(name, signals, optional_signals=None, valid_signal=None, ready
|
||||
attrib['_ready_signal'] = ready_signal
|
||||
attrib['_valid_signal'] = valid_signal
|
||||
attrib['_transaction_obj'] = transaction
|
||||
attrib['_bus_obj'] = bus
|
||||
|
||||
source = type(name+"Source", (StreamSource,), attrib)
|
||||
sink = type(name+"Sink", (StreamSink,), attrib)
|
||||
monitor = type(name+"Monitor", (StreamMonitor,), attrib)
|
||||
|
||||
return transaction, source, sink, monitor
|
||||
return bus, transaction, source, sink, monitor
|
||||
|
||||
@@ -23,28 +23,28 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
|
||||
def hexdump_line(data, offset):
|
||||
def hexdump_line(data, offset, row_size=16):
|
||||
h = ""
|
||||
c = ""
|
||||
for ch in data[0:16]:
|
||||
for ch in data[0:row_size]:
|
||||
h += f"{ch:02x} "
|
||||
c += chr(ch) if 32 < ch < 127 else "."
|
||||
return f"{offset:08x}: {h:48} {c}"
|
||||
return f"{offset:08x}: {h:{row_size*3}} {c}"
|
||||
|
||||
|
||||
def hexdump(data, start=0, length=None, prefix="", offset=0):
|
||||
def hexdump(data, start=0, length=None, row_size=16, prefix="", offset=0):
|
||||
stop = min(start+length, len(data)) if length else len(data)
|
||||
for k in range(start, stop, 16):
|
||||
print(prefix+hexdump_line(data[k:min(k+16, stop)], k+offset))
|
||||
for k in range(start, stop, row_size):
|
||||
print(prefix+hexdump_line(data[k:min(k+row_size, stop)], k+offset, row_size))
|
||||
|
||||
|
||||
def hexdump_lines(data, start=0, length=None, prefix="", offset=0):
|
||||
def hexdump_lines(data, start=0, length=None, row_size=16, prefix="", offset=0):
|
||||
lines = []
|
||||
stop = min(start+length, len(data)) if length else len(data)
|
||||
for k in range(start, stop, 16):
|
||||
lines.append(prefix+hexdump_line(data[k:min(k+16, stop)], k+offset))
|
||||
for k in range(start, stop, row_size):
|
||||
lines.append(prefix+hexdump_line(data[k:min(k+row_size, stop)], k+offset, row_size))
|
||||
return lines
|
||||
|
||||
|
||||
def hexdump_str(data, start=0, length=None, prefix="", offset=0):
|
||||
return "\n".join(hexdump_lines(data, start, length, prefix, offset))
|
||||
def hexdump_str(data, start=0, length=None, row_size=16, prefix="", offset=0):
|
||||
return "\n".join(hexdump_lines(data, start, length, row_size, prefix, offset))
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.4"
|
||||
__version__ = "0.1.6"
|
||||
|
||||
@@ -42,8 +42,6 @@ export PARAM_BUSER_WIDTH ?= 1
|
||||
export PARAM_ARUSER_WIDTH ?= 1
|
||||
export PARAM_RUSER_WIDTH ?= 1
|
||||
|
||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
@@ -79,6 +77,8 @@ else ifeq ($(SIM), verilator)
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
@@ -88,9 +88,5 @@ iverilog_dump.v:
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf sim_build_*
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
from cocotb.regression import TestFactory
|
||||
|
||||
from cocotbext.axi import AxiMaster, AxiRam
|
||||
from cocotbext.axi import AxiBus, AxiMaster, AxiRam
|
||||
|
||||
|
||||
class TB(object):
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
@@ -47,8 +47,8 @@ class TB(object):
|
||||
|
||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||
|
||||
self.axi_master = AxiMaster(dut, "axi", dut.clk, dut.rst)
|
||||
self.axi_ram = AxiRam(dut, "axi", dut.clk, dut.rst, size=2**16)
|
||||
self.axi_master = AxiMaster(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst)
|
||||
self.axi_ram = AxiRam(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst, size=2**16)
|
||||
|
||||
self.axi_ram.write_if.log.setLevel(logging.DEBUG)
|
||||
self.axi_ram.read_if.log.setLevel(logging.DEBUG)
|
||||
@@ -311,7 +311,6 @@ if cocotb.SIM_NAME:
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.dirname(__file__)
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||
@@ -338,8 +337,8 @@ def test_axi(request, data_width):
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir,
|
||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
|
||||
@@ -36,8 +36,6 @@ export PARAM_DATA_WIDTH ?= 32
|
||||
export PARAM_ADDR_WIDTH ?= 32
|
||||
export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||
|
||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
@@ -61,6 +59,8 @@ else ifeq ($(SIM), verilator)
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
@@ -70,9 +70,5 @@ iverilog_dump.v:
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf sim_build_*
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge, Timer
|
||||
from cocotb.regression import TestFactory
|
||||
|
||||
from cocotbext.axi import AxiLiteMaster, AxiLiteRam
|
||||
from cocotbext.axi import AxiLiteBus, AxiLiteMaster, AxiLiteRam
|
||||
|
||||
|
||||
class TB(object):
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
@@ -47,8 +47,8 @@ class TB(object):
|
||||
|
||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||
|
||||
self.axil_master = AxiLiteMaster(dut, "axil", dut.clk, dut.rst)
|
||||
self.axil_ram = AxiLiteRam(dut, "axil", dut.clk, dut.rst, size=2**16)
|
||||
self.axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst)
|
||||
self.axil_ram = AxiLiteRam(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst, size=2**16)
|
||||
|
||||
def set_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
@@ -295,7 +295,6 @@ if cocotb.SIM_NAME:
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.dirname(__file__)
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||
@@ -316,8 +315,8 @@ def test_axil(request, data_width):
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir,
|
||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
|
||||
@@ -38,8 +38,6 @@ export PARAM_ID_WIDTH ?= 8
|
||||
export PARAM_DEST_WIDTH ?= 8
|
||||
export PARAM_USER_WIDTH ?= 1
|
||||
|
||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
||||
|
||||
ifeq ($(SIM), icarus)
|
||||
PLUSARGS += -fst
|
||||
|
||||
@@ -67,6 +65,8 @@ else ifeq ($(SIM), verilator)
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
iverilog_dump.v:
|
||||
echo 'module iverilog_dump();' > $@
|
||||
echo 'initial begin' >> $@
|
||||
@@ -76,9 +76,5 @@ iverilog_dump.v:
|
||||
echo 'endmodule' >> $@
|
||||
|
||||
clean::
|
||||
@rm -rf sim_build_*
|
||||
@rm -rf iverilog_dump.v
|
||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
||||
|
||||
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ from cocotb.clock import Clock
|
||||
from cocotb.triggers import RisingEdge
|
||||
from cocotb.regression import TestFactory
|
||||
|
||||
from cocotbext.axi import AxiStreamFrame, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
from cocotbext.axi import AxiStreamFrame, AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor
|
||||
|
||||
|
||||
class TB(object):
|
||||
class TB:
|
||||
def __init__(self, dut):
|
||||
self.dut = dut
|
||||
|
||||
@@ -47,9 +47,9 @@ class TB(object):
|
||||
|
||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||
|
||||
self.source = AxiStreamSource(dut, "axis", dut.clk, dut.rst)
|
||||
self.sink = AxiStreamSink(dut, "axis", dut.clk, dut.rst)
|
||||
self.monitor = AxiStreamMonitor(dut, "axis", dut.clk, dut.rst)
|
||||
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||
self.monitor = AxiStreamMonitor(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||
|
||||
def set_idle_generator(self, generator=None):
|
||||
if generator:
|
||||
@@ -104,12 +104,15 @@ async def run_test(dut, payload_lengths=None, payload_data=None, idle_inserter=N
|
||||
assert rx_frame.tdest == test_frame.tdest
|
||||
assert not rx_frame.tuser
|
||||
|
||||
rx_frame = await tb.monitor.recv()
|
||||
mon_rx_frame = await tb.monitor.recv()
|
||||
|
||||
assert rx_frame.tdata == test_frame.tdata
|
||||
assert rx_frame.tid == test_frame.tid
|
||||
assert rx_frame.tdest == test_frame.tdest
|
||||
assert not rx_frame.tuser
|
||||
assert mon_rx_frame.tdata == test_frame.tdata
|
||||
assert mon_rx_frame.tid == test_frame.tid
|
||||
assert mon_rx_frame.tdest == test_frame.tdest
|
||||
assert not mon_rx_frame.tuser
|
||||
|
||||
assert rx_frame.sim_time_start == mon_rx_frame.sim_time_start
|
||||
assert rx_frame.sim_time_end == mon_rx_frame.sim_time_end
|
||||
|
||||
assert tb.sink.empty()
|
||||
assert tb.monitor.empty()
|
||||
@@ -145,7 +148,6 @@ if cocotb.SIM_NAME:
|
||||
# cocotb-test
|
||||
|
||||
tests_dir = os.path.dirname(__file__)
|
||||
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||
@@ -168,8 +170,8 @@ def test_axis(request, data_width):
|
||||
|
||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||
|
||||
sim_build = os.path.join(tests_dir,
|
||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
||||
sim_build = os.path.join(tests_dir, "sim_build",
|
||||
request.node.name.replace('[', '-').replace(']', ''))
|
||||
|
||||
cocotb_test.simulator.run(
|
||||
python_search=[tests_dir],
|
||||
|
||||
Reference in New Issue
Block a user