Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74dd47ca99 | ||
|
|
cde2056bb0 | ||
|
|
b6870716ed | ||
|
|
44da562db9 | ||
|
|
8dcdbfefb8 | ||
|
|
b8919a095b | ||
|
|
bc7edec289 | ||
|
|
e7c3a31eb0 | ||
|
|
ce907ffbb9 | ||
|
|
95e2d5800d | ||
|
|
82853b31ff | ||
|
|
8bbabd92df | ||
|
|
c060f6c963 | ||
|
|
a767e00ce5 | ||
|
|
d1d7313b98 | ||
|
|
01b43b97f2 | ||
|
|
b5b6df84fe | ||
|
|
babe69f4d3 | ||
|
|
c4873ad14c | ||
|
|
77a40bdc8f | ||
|
|
f991096272 | ||
|
|
9e28bd7fbb | ||
|
|
8c74f747a4 | ||
|
|
a285f008ca | ||
|
|
c677ab245c | ||
|
|
11f9db8b06 | ||
|
|
344ec8d4ce | ||
|
|
4ff390481e | ||
|
|
4bee96ea9a | ||
|
|
a66dfea6f7 | ||
|
|
f1a89e6c12 | ||
|
|
11205bde46 | ||
|
|
bce364eef5 | ||
|
|
e934b69776 | ||
|
|
7fb8c4e28b | ||
|
|
156fada616 | ||
|
|
d88ba7caf3 | ||
|
|
6c66776518 | ||
|
|
a71678c7e3 | ||
|
|
c0ebb90cd4 | ||
|
|
56caf57fa4 | ||
|
|
abb78308ff | ||
|
|
f19ca9f651 | ||
|
|
1c40b8fa58 | ||
|
|
cfd5dae6ea | ||
|
|
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 |
139
README.md
139
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,47 +38,38 @@ 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, as appropriate. 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.
|
||||
Once the module is instantiated, read and write operations can be initiated in a couple of different ways.
|
||||
|
||||
First, non-blocking operations can be started with `init_read()` and `init_write()`. These methods will queue up a read or write operation to be carried out over the interface. The result of the operation can be retrieved with `get_read_data()` and `get_write_resp()`. To monitor the status of the module, `idle()`, `wait()`, `wait_read()`, and `wait_write()` can be used. For example:
|
||||
|
||||
axi_master.init_write(0x0000, b'test')
|
||||
await axi_master.wait()
|
||||
resp = axi_master.get_write_resp()
|
||||
axi_master.init_read(0x0000, 4)
|
||||
await axi_master.wait()
|
||||
data = axi_master.get_read_data()
|
||||
|
||||
Alternatively, an event object can be provided as an argument to `init_read()` and `init_write()`, and the result can be retrieved from `Event.data`. For example:
|
||||
|
||||
event = Event()
|
||||
axi_master.init_write(0x0000, b'test', event=event)
|
||||
await event.wait()
|
||||
resp = event.data
|
||||
event = Event()
|
||||
axi_master.init_read(0x0000, 4, event=event)
|
||||
await event.wait()
|
||||
resp = event.data
|
||||
|
||||
Second, blocking operations can be carried out with `read()` and `write()` and their associated word-access wrappers. Multiple concurrent operations started from different coroutines are handled correctly. For example:
|
||||
First, operations can be carried out with async blocking `read()`, `write()`, and their associated word-access wrappers. Multiple concurrent operations started from different coroutines are handled correctly, with results returned in the order that the operations complete. For example:
|
||||
|
||||
await axi_master.write(0x0000, b'test')
|
||||
data = await axi_master.read(0x0000, 4)
|
||||
|
||||
`read()`, `write()`, `get_read_data()`, and `get_write_resp()` return `namedtuple` objects containing _address_, _data_ or _length_, and _resp_.
|
||||
Additional parameters can be specified to control sideband signals and burst settings. The transfer will be split into one or more bursts according to the AXI specification. All bursts generated from the same call to `read()` or `write()` will use the same ID, which will be automatically generated if not specified. `read()` and `write()` return `namedtuple` objects containing _address_, _data_ or _length_, and _resp_. This is the preferred style, and this is the only style supported by the word-access wrappers.
|
||||
|
||||
Alternatively, operations can be initiated with non-blocking `init_read()` and `init_write()`. These functions return `Event` objects which are triggered when the operation completes, and the result can be retrieved from `Event.data`. For example:
|
||||
|
||||
write_op = axi_master.init_write(0x0000, b'test')
|
||||
await write_op.wait()
|
||||
resp = write_op.data
|
||||
read_op = axi_master.init_read(0x0000, 4)
|
||||
await read_op.wait()
|
||||
resp = read_op.data
|
||||
|
||||
With this method, it is possible to start multiple concurrent operations from the same coroutine. It is also possible to use the events with `Combine`, `First`, and `with_timeout`.
|
||||
|
||||
#### `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`
|
||||
|
||||
@@ -85,16 +77,12 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
||||
|
||||
#### Methods
|
||||
|
||||
* `init_read(address, length, ...)`: initiate reading _length_ bytes, starting at _address_
|
||||
* `init_write(address, data, ...)`: initiate writing _data_ (bytes), starting from _address_
|
||||
* `init_read(address, length, ...)`: initiate reading _length_ bytes, starting at _address_. Returns an `Event` object.
|
||||
* `init_write(address, data, ...)`: initiate writing _data_ (bytes), starting from _address_. Returns an `Event` object.
|
||||
* `idle()`: returns _True_ when there are no outstanding operations in progress
|
||||
* `wait()`: blocking wait until all outstanding operations complete
|
||||
* `wait_read()`: wait until all outstanding read operations complete
|
||||
* `wait_write()`: wait until all outstanding write operations complete
|
||||
* `read_data_ready()`: determine if any read read data is available
|
||||
* `get_read_data()`: fetch first available read data
|
||||
* `write_resp_ready()`: determine if any write response is available
|
||||
* `get_write_resp()`: fetch first available write response
|
||||
* `read(address, length, ...)`: read _length_ bytes, starting at _address_
|
||||
* `read_words(address, count, byteorder='little', ws=2, ...)`: read _count_ _ws_-byte words, starting at _address_
|
||||
* `read_dwords(address, count, byteorder='little', ...)`: read _count_ 4-byte dwords, starting at _address_
|
||||
@@ -124,12 +112,16 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
||||
* _region_: AXI region field, default `0`
|
||||
* _user_: AXI user signal (awuser/aruser), default `0`
|
||||
* _wuser_: AXI wuser signal, default `0` (write-related methods only)
|
||||
* _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()`.
|
||||
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
||||
|
||||
#### Additional optional arguments for `AxiLiteMaster`
|
||||
|
||||
* _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()`.
|
||||
* _event_: `Event` object used to wait on and retrieve result for specific operation, default `None`. The event will be triggered when the operation completes and the result returned via `Event.data`. (`init_read()` and `init_write()` only)
|
||||
|
||||
#### `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 currently extensions of `cocotb_bus.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
|
||||
|
||||
@@ -139,30 +131,31 @@ 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:
|
||||
|
||||
axi_ram.write(0x0000, b'test')
|
||||
data = axi_ram.read(0x0000, 4)
|
||||
axi_ram.hexdump(0x0000, 4, prefix="RAM")
|
||||
|
||||
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
||||
|
||||
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 +181,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 +229,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)
|
||||
|
||||
@@ -243,8 +243,8 @@ Note: _byte_size_, _byte_lanes_, `len(tdata)`, and `len(tkeep)` are all related,
|
||||
* _pause_: stall the interface (deassert `tready` or `tvalid`) (source/sink only)
|
||||
* _queue_occupancy_bytes_: number of bytes in queue (all)
|
||||
* _queue_occupancy_frames_: number of frames in queue (all)
|
||||
* _queue_occupancy_limit_bytes_: max number of bytes in queue allowed before tready deassert (sink only)
|
||||
* _queue_occupancy_limit_frames_: max number of frames in queue allowed before tready deassert (sink only)
|
||||
* _queue_occupancy_limit_bytes_: max number of bytes in queue allowed before backpressure is applied (source/sink only)
|
||||
* _queue_occupancy_limit_frames_: max number of frames in queue allowed before backpressure is applied (source/sink only)
|
||||
|
||||
#### Methods
|
||||
|
||||
@@ -256,17 +256,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)
|
||||
* `full()`: returns _True_ if the queue occupancy limits are met (source/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 +281,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,38 +25,109 @@ THE SOFTWARE.
|
||||
from .stream import define_stream
|
||||
|
||||
# Write address channel
|
||||
AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awprot", "awvalid", "awready"],
|
||||
optional_signals=["awlock", "awcache", "awqos", "awregion", "awuser"],
|
||||
AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awvalid", "awready"],
|
||||
optional_signals=["awlock", "awcache", "awprot", "awqos", "awregion", "awuser"],
|
||||
signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1,
|
||||
"awcache": 4, "awprot": 3, "awqos": 4, "awregion": 4}
|
||||
)
|
||||
|
||||
# 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",
|
||||
signals=["bid", "bresp", "bvalid", "bready"],
|
||||
optional_signals=["buser"],
|
||||
AxiBBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
|
||||
signals=["bid", "bvalid", "bready"],
|
||||
optional_signals=["bresp", "buser"],
|
||||
signal_widths={"bresp": 2}
|
||||
)
|
||||
|
||||
# Read address channel
|
||||
AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arprot", "arvalid", "arready"],
|
||||
optional_signals=["arlock", "arcache", "arqos", "arregion", "aruser"],
|
||||
AxiARBus, AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arvalid", "arready"],
|
||||
optional_signals=["arlock", "arcache", "arprot", "arqos", "arregion", "aruser"],
|
||||
signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1,
|
||||
"arcache": 4, "arprot": 3, "arqos": 4, "arregion": 4}
|
||||
)
|
||||
|
||||
# Read data channel
|
||||
AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
||||
signals=["rid", "rdata", "rresp", "rlast", "rvalid", "rready"],
|
||||
optional_signals=["ruser"],
|
||||
AxiRBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
||||
signals=["rid", "rdata", "rlast", "rvalid", "rready"],
|
||||
optional_signals=["rresp", "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)
|
||||
|
||||
@@ -23,14 +23,16 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import deque, namedtuple, Counter
|
||||
from collections import namedtuple, Counter
|
||||
|
||||
import cocotb
|
||||
from cocotb.queue import Queue
|
||||
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,66 +49,175 @@ 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 TagContext:
|
||||
def __init__(self, manager):
|
||||
self.current_tag = 0
|
||||
self._cmd_queue = Queue()
|
||||
self._current_cmd = None
|
||||
self._resp_queue = Queue()
|
||||
self._cr = None
|
||||
self._manager = manager
|
||||
|
||||
async def get_resp(self):
|
||||
return await self._resp_queue.get()
|
||||
|
||||
def get_resp_nowait(self):
|
||||
return self._resp_queue.get_nowait()
|
||||
|
||||
def _start(self):
|
||||
if self._cr is None:
|
||||
self._cr = cocotb.fork(self._process_queue())
|
||||
|
||||
def _flush(self):
|
||||
flushed_cmds = []
|
||||
if self._cr is not None:
|
||||
self._cr.kill()
|
||||
self._cr = None
|
||||
self._manager._set_idle(self)
|
||||
if self._current_cmd is not None:
|
||||
flushed_cmds.append(self._current_cmd)
|
||||
self._current_cmd = None
|
||||
while not self._cmd_queue.empty():
|
||||
flushed_cmds.append(self._cmd_queue.get_nowait())
|
||||
while not self._resp_queue.empty():
|
||||
self._resp_queue.get_nowait()
|
||||
return flushed_cmds
|
||||
|
||||
async def _process_queue(self):
|
||||
while True:
|
||||
cmd = await self._cmd_queue.get()
|
||||
self._current_cmd = cmd
|
||||
await self._manager._process(self, cmd)
|
||||
self._current_cmd = None
|
||||
|
||||
if self._cmd_queue.empty() and self._resp_queue.empty():
|
||||
self._manager._set_idle(self)
|
||||
|
||||
|
||||
class TagContextManager:
|
||||
def __init__(self, process):
|
||||
self._context_list = []
|
||||
self._context_idle_list = []
|
||||
self._context_mapping = {}
|
||||
self._process = process
|
||||
|
||||
def _get_context(self, tag):
|
||||
if tag in self._context_mapping:
|
||||
return self._context_mapping[tag]
|
||||
elif self._context_idle_list:
|
||||
context = self._context_idle_list.pop()
|
||||
else:
|
||||
context = TagContext(self)
|
||||
self._context_list.append(context)
|
||||
context._start()
|
||||
context.current_tag = tag
|
||||
self._context_mapping[tag] = context
|
||||
return context
|
||||
|
||||
def start_cmd(self, tag, cmd):
|
||||
context = self._get_context(tag)
|
||||
context._cmd_queue.put_nowait(cmd)
|
||||
|
||||
def put_resp(self, tag, resp):
|
||||
context = self._get_context(tag)
|
||||
context._resp_queue.put_nowait(resp)
|
||||
|
||||
def _set_idle(self, context):
|
||||
if context.current_tag in self._context_mapping:
|
||||
del self._context_mapping[context.current_tag]
|
||||
self._context_idle_list.append(context)
|
||||
context.current_tag = None
|
||||
|
||||
def flush(self):
|
||||
flushed_cmds = []
|
||||
for c in self._context_list:
|
||||
flushed_cmds.extend(c._flush())
|
||||
return flushed_cmds
|
||||
|
||||
|
||||
class AxiMasterWrite(Reset):
|
||||
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||
self.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||
|
||||
self.log.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(bus.aw, clock, reset, reset_active_level)
|
||||
self.aw_channel.queue_occupancy_limit = 2
|
||||
self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level)
|
||||
self.w_channel.queue_occupancy_limit = 2
|
||||
self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level)
|
||||
self.b_channel.queue_occupancy_limit = 2
|
||||
|
||||
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.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.write_command_queue = Queue()
|
||||
self.current_write_command = None
|
||||
|
||||
self.id_count = 2**len(self.aw_channel.bus.awid)
|
||||
self.cur_id = 0
|
||||
self.active_id = Counter()
|
||||
|
||||
self.int_write_resp_command_queue = deque()
|
||||
self.int_write_resp_command_sync = Event()
|
||||
self.int_write_resp_queue_list = [deque() for k in range(self.id_count)]
|
||||
self.tag_context_manager = TagContextManager(self._process_write_resp_id)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_width-1
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_lanes-1
|
||||
|
||||
self.max_burst_len = max(min(max_burst_len, 256), 1)
|
||||
self.max_burst_size = (self.byte_width-1).bit_length()
|
||||
self.max_burst_size = (self.byte_lanes-1).bit_length()
|
||||
|
||||
self.awlock_present = hasattr(self.bus.aw, "awlock")
|
||||
self.awcache_present = hasattr(self.bus.aw, "awcache")
|
||||
self.awprot_present = hasattr(self.bus.aw, "awprot")
|
||||
self.awqos_present = hasattr(self.bus.aw, "awqos")
|
||||
self.awregion_present = hasattr(self.bus.aw, "awregion")
|
||||
self.awuser_present = hasattr(self.bus.aw, "awuser")
|
||||
self.wuser_present = hasattr(self.bus.w, "wuser")
|
||||
self.buser_present = hasattr(self.bus.b, "buser")
|
||||
|
||||
self.log.info("AXI master configuration:")
|
||||
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
|
||||
self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size)
|
||||
self.log.info(" Max burst length: %d cycles (%d bytes)",
|
||||
self.max_burst_len, self.max_burst_len*self.byte_width)
|
||||
self.max_burst_len, self.max_burst_len*self.byte_lanes)
|
||||
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI master signals:")
|
||||
for bus in (self.bus.aw, self.bus.w, self.bus.b):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||
|
||||
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):
|
||||
|
||||
if event is not None and not isinstance(event, Event):
|
||||
if event is None:
|
||||
event = Event()
|
||||
|
||||
if not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
if awid is None or awid < 0:
|
||||
@@ -124,6 +235,27 @@ class AxiMasterWrite(object):
|
||||
lock = AxiLockType(lock)
|
||||
prot = AxiProt(prot)
|
||||
|
||||
if not self.awlock_present and lock != AxiLockType.NORMAL:
|
||||
raise ValueError("awlock sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.awcache_present and cache != 0b0011:
|
||||
raise ValueError("awcache sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.awqos_present and qos != 0:
|
||||
raise ValueError("awqos sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.awregion_present and region != 0:
|
||||
raise ValueError("awregion sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.awuser_present and user != 0:
|
||||
raise ValueError("awuser sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.wuser_present and wuser != 0:
|
||||
raise ValueError("wuser sideband signal value specified, but signal is not connected")
|
||||
|
||||
if wuser is None:
|
||||
wuser = 0
|
||||
elif isinstance(wuser, int):
|
||||
@@ -132,32 +264,24 @@ 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)
|
||||
self.write_command_queue.append(cmd)
|
||||
self.write_command_sync.set()
|
||||
self.write_command_queue.put_nowait(cmd)
|
||||
|
||||
return event
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.write_resp_sync.clear()
|
||||
await self.write_resp_sync.wait()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return bool(self.write_resp_queue)
|
||||
|
||||
def get_write_resp(self):
|
||||
if self.write_resp_queue:
|
||||
return self.write_resp_queue.popleft()
|
||||
return None
|
||||
await self._idle.wait()
|
||||
|
||||
async def 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 = Event()
|
||||
self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)
|
||||
event = self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser)
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
@@ -198,21 +322,61 @@ 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
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
def flush_cmd(cmd):
|
||||
self.log.warning("Flushed write operation during reset: %s", cmd)
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while not self.write_command_queue.empty():
|
||||
cmd = self.write_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_write_command:
|
||||
cmd = self.current_write_command
|
||||
self.current_write_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
for cmd in self.tag_context_manager.flush():
|
||||
flush_cmd(cmd)
|
||||
|
||||
self.cur_id = 0
|
||||
self.active_id = Counter()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
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())
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
if not self.write_command_queue:
|
||||
self.write_command_sync.clear()
|
||||
await self.write_command_sync.wait()
|
||||
|
||||
cmd = self.write_command_queue.popleft()
|
||||
cmd = await self.write_command_queue.get()
|
||||
self.current_write_command = cmd
|
||||
|
||||
num_bytes = 2**cmd.size
|
||||
|
||||
aligned_addr = (cmd.address // num_bytes) * num_bytes
|
||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
||||
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
start_offset = cmd.address % self.byte_width
|
||||
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_width) + 1
|
||||
start_offset = cmd.address % self.byte_lanes
|
||||
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_lanes) + 1
|
||||
|
||||
cycles = (len(cmd.data) + (cmd.address % num_bytes) + num_bytes-1) // num_bytes
|
||||
|
||||
@@ -245,7 +409,7 @@ class AxiMasterWrite(object):
|
||||
if k == cycles-1:
|
||||
stop = end_offset
|
||||
|
||||
strb = (self.strb_mask << start) & self.strb_mask & (self.strb_mask >> (self.byte_width - stop))
|
||||
strb = (self.strb_mask << start) & self.strb_mask & (self.strb_mask >> (self.byte_lanes - stop))
|
||||
|
||||
val = 0
|
||||
for j in range(start, stop):
|
||||
@@ -261,7 +425,7 @@ class AxiMasterWrite(object):
|
||||
# split on 4k address boundary
|
||||
burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes
|
||||
|
||||
burst_list.append((awid, burst_length))
|
||||
burst_list.append(burst_length)
|
||||
|
||||
aw = self.aw_channel._transaction_obj()
|
||||
aw.awid = awid
|
||||
@@ -277,7 +441,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)
|
||||
@@ -292,45 +456,41 @@ class AxiMasterWrite(object):
|
||||
if isinstance(wuser, int):
|
||||
w.wuser = wuser
|
||||
else:
|
||||
if wuser:
|
||||
w.wuser = wuser.pop(0)
|
||||
if wuser and k < len(wuser):
|
||||
w.wuser = wuser[k]
|
||||
else:
|
||||
w.wuser = 0
|
||||
|
||||
await self.w_channel.send(w)
|
||||
|
||||
cur_addr += num_bytes
|
||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_width
|
||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_lanes
|
||||
|
||||
resp_cmd = AxiWriteRespCmd(cmd.address, len(cmd.data), cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
||||
self.int_write_resp_command_queue.append(resp_cmd)
|
||||
self.int_write_resp_command_sync.set()
|
||||
self.tag_context_manager.start_cmd(awid, resp_cmd)
|
||||
|
||||
self.current_write_command = None
|
||||
|
||||
async def _process_write_resp(self):
|
||||
while True:
|
||||
if not self.int_write_resp_command_queue:
|
||||
self.int_write_resp_command_sync.clear()
|
||||
await self.int_write_resp_command_sync.wait()
|
||||
b = await self.b_channel.recv()
|
||||
|
||||
cmd = self.int_write_resp_command_queue.popleft()
|
||||
bid = int(b.bid)
|
||||
|
||||
if self.active_id[bid] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {bid}")
|
||||
|
||||
self.tag_context_manager.put_resp(bid, b)
|
||||
|
||||
async def _process_write_resp_id(self, context, cmd):
|
||||
bid = context.current_tag
|
||||
|
||||
resp = AxiResp.OKAY
|
||||
user = []
|
||||
|
||||
for bid, burst_length in cmd.burst_list:
|
||||
while not self.int_write_resp_queue_list[bid]:
|
||||
b = await self.b_channel.recv()
|
||||
for burst_length in cmd.burst_list:
|
||||
b = await context.get_resp()
|
||||
|
||||
i = int(b.bid)
|
||||
|
||||
if self.active_id[i] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {bid}")
|
||||
|
||||
self.int_write_resp_queue_list[i].append(b)
|
||||
|
||||
b = self.int_write_resp_queue_list[bid].popleft()
|
||||
|
||||
burst_id = int(b.bid)
|
||||
burst_resp = AxiResp(b.bresp)
|
||||
burst_user = int(b.buser)
|
||||
|
||||
@@ -345,79 +505,102 @@ class AxiMasterWrite(object):
|
||||
|
||||
self.active_id[bid] -= 1
|
||||
|
||||
self.log.info("Write burst complete bid: 0x%x bresp: %s", burst_id, burst_resp)
|
||||
self.log.info("Write burst complete bid: 0x%x bresp: %s", bid, burst_resp)
|
||||
|
||||
if not self.buser_present:
|
||||
user = None
|
||||
|
||||
self.log.info("Write complete addr: 0x%08x prot: %s resp: %s length: %d",
|
||||
cmd.address, cmd.prot, resp, cmd.length)
|
||||
|
||||
write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user)
|
||||
|
||||
if cmd.event is not None:
|
||||
cmd.event.set(write_resp)
|
||||
else:
|
||||
self.write_resp_queue.append(write_resp)
|
||||
self.write_resp_sync.set()
|
||||
|
||||
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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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(bus.ar, clock, reset, reset_active_level)
|
||||
self.ar_channel.queue_occupancy_limit = 2
|
||||
self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level)
|
||||
self.r_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.ar_channel = AxiARSource(entity, name, clock, reset)
|
||||
self.r_channel = AxiRSink(entity, name, clock, reset)
|
||||
|
||||
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.read_command_queue = Queue()
|
||||
self.current_read_command = None
|
||||
|
||||
self.id_count = 2**len(self.ar_channel.bus.arid)
|
||||
self.cur_id = 0
|
||||
self.active_id = Counter()
|
||||
|
||||
self.int_read_resp_command_queue = deque()
|
||||
self.int_read_resp_command_sync = Event()
|
||||
self.int_read_resp_queue_list = [deque() for k in range(self.id_count)]
|
||||
self.tag_context_manager = TagContextManager(self._process_read_resp_id)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.max_burst_len = max(min(max_burst_len, 256), 1)
|
||||
self.max_burst_size = (self.byte_width-1).bit_length()
|
||||
self.max_burst_size = (self.byte_lanes-1).bit_length()
|
||||
|
||||
self.arlock_present = hasattr(self.bus.ar, "arlock")
|
||||
self.arcache_present = hasattr(self.bus.ar, "arcache")
|
||||
self.arprot_present = hasattr(self.bus.ar, "arprot")
|
||||
self.arqos_present = hasattr(self.bus.ar, "arqos")
|
||||
self.arregion_present = hasattr(self.bus.ar, "arregion")
|
||||
self.aruser_present = hasattr(self.bus.ar, "aruser")
|
||||
self.ruser_present = hasattr(self.bus.r, "ruser")
|
||||
|
||||
self.log.info("AXI master configuration:")
|
||||
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
|
||||
self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size)
|
||||
self.log.info(" Max burst length: %d cycles (%d bytes)",
|
||||
self.max_burst_len, self.max_burst_len*self.byte_width)
|
||||
self.max_burst_len, self.max_burst_len*self.byte_lanes)
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI master signals:")
|
||||
for bus in (self.bus.ar, self.bus.r):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||
|
||||
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):
|
||||
|
||||
if event is not None and not isinstance(event, Event):
|
||||
if event is None:
|
||||
event = Event()
|
||||
|
||||
if not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
if length < 0:
|
||||
@@ -438,32 +621,42 @@ class AxiMasterRead(object):
|
||||
lock = AxiLockType(lock)
|
||||
prot = AxiProt(prot)
|
||||
|
||||
if not self.arlock_present and lock != AxiLockType.NORMAL:
|
||||
raise ValueError("arlock sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.arcache_present and cache != 0b0011:
|
||||
raise ValueError("arcache sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.arqos_present and qos != 0:
|
||||
raise ValueError("arqos sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.arregion_present and region != 0:
|
||||
raise ValueError("arregion sideband signal value specified, but signal is not connected")
|
||||
|
||||
if not self.aruser_present and user != 0:
|
||||
raise ValueError("aruser sideband signal value specified, but signal is not connected")
|
||||
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||
self.read_command_queue.append(cmd)
|
||||
self.read_command_sync.set()
|
||||
self.read_command_queue.put_nowait(cmd)
|
||||
|
||||
return event
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.read_data_sync.clear()
|
||||
await self.read_data_sync.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return bool(self.read_data_queue)
|
||||
|
||||
def get_read_data(self):
|
||||
if self.read_data_queue:
|
||||
return self.read_data_queue.popleft()
|
||||
return None
|
||||
await self._idle.wait()
|
||||
|
||||
async def 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 = Event()
|
||||
self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||
event = self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user)
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
@@ -504,13 +697,52 @@ 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
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
def flush_cmd(cmd):
|
||||
self.log.warning("Flushed read operation during reset: %s", cmd)
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while not self.read_command_queue.empty():
|
||||
cmd = self.read_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_read_command:
|
||||
cmd = self.current_read_command
|
||||
self.current_read_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
for cmd in self.tag_context_manager.flush():
|
||||
flush_cmd(cmd)
|
||||
|
||||
self.cur_id = 0
|
||||
self.active_id = Counter()
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
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())
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
if not self.read_command_queue:
|
||||
self.read_command_sync.clear()
|
||||
await self.read_command_sync.wait()
|
||||
|
||||
cmd = self.read_command_queue.popleft()
|
||||
cmd = await self.read_command_queue.get()
|
||||
self.current_read_command = cmd
|
||||
|
||||
num_bytes = 2**cmd.size
|
||||
|
||||
@@ -544,7 +776,7 @@ class AxiMasterRead(object):
|
||||
# split on 4k address boundary
|
||||
burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes
|
||||
|
||||
burst_list.append((arid, burst_length))
|
||||
burst_list.append(burst_length)
|
||||
|
||||
ar = self.r_channel._transaction_obj()
|
||||
ar.arid = arid
|
||||
@@ -560,7 +792,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)
|
||||
@@ -568,23 +800,42 @@ class AxiMasterRead(object):
|
||||
cur_addr += num_bytes
|
||||
|
||||
resp_cmd = AxiReadRespCmd(cmd.address, cmd.length, cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
||||
self.int_read_resp_command_queue.append(resp_cmd)
|
||||
self.int_read_resp_command_sync.set()
|
||||
self.tag_context_manager.start_cmd(arid, resp_cmd)
|
||||
|
||||
self.current_read_command = None
|
||||
|
||||
async def _process_read_resp(self):
|
||||
while True:
|
||||
if not self.int_read_resp_command_queue:
|
||||
self.int_read_resp_command_sync.clear()
|
||||
await self.int_read_resp_command_sync.wait()
|
||||
burst = []
|
||||
cur_rid = None
|
||||
|
||||
cmd = self.int_read_resp_command_queue.popleft()
|
||||
while True:
|
||||
r = await self.r_channel.recv()
|
||||
|
||||
rid = int(r.rid)
|
||||
|
||||
if cur_rid is not None and cur_rid != rid:
|
||||
raise Exception(f"ID not constant within burst (expected {cur_rid}, got {rid})")
|
||||
|
||||
if self.active_id[rid] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {rid}")
|
||||
|
||||
burst.append(r)
|
||||
cur_rid = rid
|
||||
|
||||
if int(r.rlast):
|
||||
self.tag_context_manager.put_resp(rid, burst)
|
||||
burst = []
|
||||
cur_rid = None
|
||||
|
||||
async def _process_read_resp_id(self, context, cmd):
|
||||
rid = context.current_tag
|
||||
|
||||
num_bytes = 2**cmd.size
|
||||
|
||||
aligned_addr = (cmd.address // num_bytes) * num_bytes
|
||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
||||
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
start_offset = cmd.address % self.byte_width
|
||||
start_offset = cmd.address % self.byte_lanes
|
||||
|
||||
cycle_offset = aligned_addr - word_addr
|
||||
data = bytearray()
|
||||
@@ -594,24 +845,15 @@ class AxiMasterRead(object):
|
||||
|
||||
first = True
|
||||
|
||||
for rid, burst_length in cmd.burst_list:
|
||||
for k in range(burst_length):
|
||||
while not self.int_read_resp_queue_list[rid]:
|
||||
r = await self.r_channel.recv()
|
||||
for burst_length in cmd.burst_list:
|
||||
burst = await context.get_resp()
|
||||
|
||||
i = int(r.rid)
|
||||
if len(burst) != burst_length:
|
||||
raise Exception(f"Burst length incorrect (ID {rid}, expected {burst_length}, got {len(burst)}")
|
||||
|
||||
if self.active_id[i] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {rid}")
|
||||
|
||||
self.int_read_resp_queue_list[i].append(r)
|
||||
|
||||
r = self.int_read_resp_queue_list[rid].popleft()
|
||||
|
||||
cycle_id = int(r.rid)
|
||||
for r in burst:
|
||||
cycle_data = int(r.rdata)
|
||||
cycle_resp = AxiResp(r.rresp)
|
||||
cycle_last = int(r.rlast)
|
||||
cycle_user = int(r.ruser)
|
||||
|
||||
if cycle_resp != AxiResp.OKAY:
|
||||
@@ -626,53 +868,50 @@ class AxiMasterRead(object):
|
||||
if first:
|
||||
start = start_offset
|
||||
|
||||
assert cycle_last == (k == burst_length - 1)
|
||||
|
||||
for j in range(start, stop):
|
||||
data.append((cycle_data >> j*8) & 0xff)
|
||||
|
||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_width
|
||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_lanes
|
||||
|
||||
first = False
|
||||
|
||||
if self.active_id[rid] <= 0:
|
||||
raise Exception(f"Unexpected burst ID {rid}")
|
||||
|
||||
self.active_id[rid] -= 1
|
||||
|
||||
self.log.info("Read burst complete rid: 0x%x rresp: %s", cycle_id, resp)
|
||||
self.log.info("Read burst complete rid: 0x%x rresp: %s", rid, resp)
|
||||
|
||||
data = data[:cmd.length]
|
||||
|
||||
if not self.ruser_present:
|
||||
user = None
|
||||
|
||||
self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s",
|
||||
cmd.address, cmd.prot, resp, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
read_resp = AxiReadResp(cmd.address, data, resp, user)
|
||||
|
||||
if cmd.event is not None:
|
||||
cmd.event.set(read_resp)
|
||||
else:
|
||||
self.read_data_queue.append(read_resp)
|
||||
self.read_data_sync.set()
|
||||
|
||||
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):
|
||||
self.read_if.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||
return self.read_if.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||
|
||||
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):
|
||||
self.write_if.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)
|
||||
return self.write_if.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser, event)
|
||||
|
||||
def idle(self):
|
||||
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())
|
||||
@@ -688,18 +927,6 @@ class AxiMaster(object):
|
||||
async def wait_write(self):
|
||||
await self.write_if.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return self.read_if.read_data_ready()
|
||||
|
||||
def get_read_data(self):
|
||||
return self.read_if.get_read_data()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return self.write_if.write_resp_ready()
|
||||
|
||||
def get_write_resp(self):
|
||||
return self.write_if.get_write_resp()
|
||||
|
||||
async def 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):
|
||||
return await self.read_if.read(address, length, arid,
|
||||
|
||||
@@ -30,11 +30,15 @@ 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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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,32 +47,56 @@ 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.aw_channel.queue_occupancy_limit = 2
|
||||
self.w_channel = AxiWSink(bus.w, clock, reset, reset_active_level)
|
||||
self.w_channel.queue_occupancy_limit = 2
|
||||
self.b_channel = AxiBSource(bus.b, clock, reset, reset_active_level)
|
||||
self.b_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_width-1
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_lanes-1
|
||||
|
||||
self.log.info("AXI RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
|
||||
self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI RAM model signals:")
|
||||
for bus in (self.bus.aw, self.bus.w, self.bus.b):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||
|
||||
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
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
@@ -85,7 +113,7 @@ class AxiRamWrite(Memory):
|
||||
awid, addr, length, size, prot)
|
||||
|
||||
num_bytes = 2**size
|
||||
assert 0 < num_bytes <= self.byte_width
|
||||
assert 0 < num_bytes <= self.byte_lanes
|
||||
|
||||
aligned_addr = (addr // num_bytes) * num_bytes
|
||||
length += 1
|
||||
@@ -103,7 +131,7 @@ class AxiRamWrite(Memory):
|
||||
cur_addr = aligned_addr
|
||||
|
||||
for n in range(length):
|
||||
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width
|
||||
cur_word_addr = (cur_addr // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
w = await self.w_channel.recv()
|
||||
|
||||
@@ -115,12 +143,12 @@ class AxiRamWrite(Memory):
|
||||
|
||||
self.mem.seek(cur_word_addr % self.size)
|
||||
|
||||
data = data.to_bytes(self.byte_width, 'little')
|
||||
data = data.to_bytes(self.byte_lanes, 'little')
|
||||
|
||||
self.log.debug("Write word awid: 0x%x addr: 0x%08x wstrb: 0x%02x data: %s",
|
||||
awid, cur_addr, strb, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
for i in range(self.byte_width):
|
||||
for i in range(self.byte_lanes):
|
||||
if strb & (1 << i):
|
||||
self.mem.write(data[i:i+1])
|
||||
else:
|
||||
@@ -142,9 +170,12 @@ 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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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,29 +184,51 @@ 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.ar_channel.queue_occupancy_limit = 2
|
||||
self.r_channel = AxiRSource(bus.r, clock, reset, reset_active_level)
|
||||
self.r_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.log.info("AXI RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
|
||||
self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI RAM model signals:")
|
||||
for bus in (self.bus.ar, self.bus.r):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||
|
||||
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
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
@@ -192,7 +245,7 @@ class AxiRamRead(Memory):
|
||||
arid, addr, length, size, prot)
|
||||
|
||||
num_bytes = 2**size
|
||||
assert 0 < num_bytes <= self.byte_width
|
||||
assert 0 < num_bytes <= self.byte_lanes
|
||||
|
||||
aligned_addr = (addr // num_bytes) * num_bytes
|
||||
length += 1
|
||||
@@ -210,11 +263,11 @@ class AxiRamRead(Memory):
|
||||
cur_addr = aligned_addr
|
||||
|
||||
for n in range(length):
|
||||
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width
|
||||
cur_word_addr = (cur_addr // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
self.mem.seek(cur_word_addr % self.size)
|
||||
|
||||
data = self.mem.read(self.byte_width)
|
||||
data = self.mem.read(self.byte_lanes)
|
||||
|
||||
r = self.r_channel._transaction_obj()
|
||||
r.rid = arid
|
||||
@@ -236,11 +289,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,105 @@ THE SOFTWARE.
|
||||
from .stream import define_stream
|
||||
|
||||
# Write address channel
|
||||
AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
||||
signals=["awaddr", "awprot", "awvalid", "awready"],
|
||||
AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
||||
signals=["awaddr", "awvalid", "awready"],
|
||||
optional_signals=["awprot"],
|
||||
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",
|
||||
signals=["bresp", "bvalid", "bready"],
|
||||
AxiLiteBBus, AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
|
||||
signals=["bvalid", "bready"],
|
||||
optional_signals=["bresp"],
|
||||
signal_widths={"bresp": 2}
|
||||
)
|
||||
|
||||
# Read address channel
|
||||
AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
||||
signals=["araddr", "arprot", "arvalid", "arready"],
|
||||
AxiLiteARBus, AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
||||
signals=["araddr", "arvalid", "arready"],
|
||||
optional_signals=["arprot"],
|
||||
signal_widths={"arprot": 3}
|
||||
)
|
||||
|
||||
# Read data channel
|
||||
AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
||||
signals=["rdata", "rresp", "rvalid", "rready"],
|
||||
AxiLiteRBus, AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
||||
signals=["rdata", "rvalid", "rready"],
|
||||
optional_signals=["rresp"],
|
||||
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)
|
||||
|
||||
@@ -23,14 +23,16 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import deque, namedtuple
|
||||
from collections import namedtuple
|
||||
|
||||
import cocotb
|
||||
from cocotb.queue import Queue
|
||||
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,76 +45,89 @@ 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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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(bus.aw, clock, reset, reset_active_level)
|
||||
self.aw_channel.queue_occupancy_limit = 2
|
||||
self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level)
|
||||
self.w_channel.queue_occupancy_limit = 2
|
||||
self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level)
|
||||
self.b_channel.queue_occupancy_limit = 2
|
||||
|
||||
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.write_command_queue = Queue()
|
||||
self.current_write_command = None
|
||||
|
||||
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.int_write_resp_command_queue = Queue()
|
||||
self.current_write_resp_command = None
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_width-1
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_lanes-1
|
||||
|
||||
self.awprot_present = hasattr(self.bus.aw, "awprot")
|
||||
|
||||
self.log.info("AXI lite master configuration:")
|
||||
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI lite master signals:")
|
||||
for bus in (self.bus.aw, self.bus.w, self.bus.b):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
cocotb.fork(self._process_write_resp())
|
||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
self._process_write_cr = None
|
||||
self._process_write_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):
|
||||
if event is None:
|
||||
event = Event()
|
||||
|
||||
if not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
self.in_flight_operations += 1
|
||||
if not self.awprot_present and prot != AxiProt.NONSECURE:
|
||||
raise ValueError("awprot sideband signal value specified, but signal is not connected")
|
||||
|
||||
self.write_command_queue.append(AxiLiteWriteCmd(address, bytearray(data), prot, event))
|
||||
self.write_command_sync.set()
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
self.write_command_queue.put_nowait(AxiLiteWriteCmd(address, bytearray(data), prot, event))
|
||||
|
||||
return event
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.write_resp_sync.clear()
|
||||
await self.write_resp_sync.wait()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return bool(self.write_resp_queue)
|
||||
|
||||
def get_write_resp(self):
|
||||
if self.write_resp_queue:
|
||||
return self.write_resp_queue.popleft()
|
||||
return None
|
||||
await self._idle.wait()
|
||||
|
||||
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
||||
event = Event()
|
||||
self.init_write(address, data, prot, event)
|
||||
event = self.init_write(address, data, prot)
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
@@ -141,27 +156,69 @@ 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
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
|
||||
def flush_cmd(cmd):
|
||||
self.log.warning("Flushed write operation during reset: %s", cmd)
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while not self.write_command_queue.empty():
|
||||
cmd = self.write_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_write_command:
|
||||
cmd = self.current_write_command
|
||||
self.current_write_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
while not self.int_write_resp_command_queue.empty():
|
||||
cmd = self.int_write_resp_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_write_resp_command:
|
||||
cmd = self.current_write_resp_command
|
||||
self.current_write_resp_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
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())
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
if not self.write_command_queue:
|
||||
self.write_command_sync.clear()
|
||||
await self.write_command_sync.wait()
|
||||
cmd = await self.write_command_queue.get()
|
||||
self.current_write_command = cmd
|
||||
|
||||
cmd = self.write_command_queue.popleft()
|
||||
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
||||
|
||||
start_offset = cmd.address % self.byte_width
|
||||
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_width) + 1
|
||||
start_offset = cmd.address % self.byte_lanes
|
||||
end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_lanes) + 1
|
||||
|
||||
strb_start = (self.strb_mask << start_offset) & self.strb_mask
|
||||
strb_end = self.strb_mask >> (self.byte_width - end_offset)
|
||||
strb_end = self.strb_mask >> (self.byte_lanes - end_offset)
|
||||
|
||||
cycles = (len(cmd.data) + (cmd.address % self.byte_width) + self.byte_width-1) // self.byte_width
|
||||
cycles = (len(cmd.data) + (cmd.address % self.byte_lanes) + self.byte_lanes-1) // self.byte_lanes
|
||||
|
||||
resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event)
|
||||
self.int_write_resp_command_queue.append(resp_cmd)
|
||||
self.int_write_resp_command_sync.set()
|
||||
await self.int_write_resp_command_queue.put(resp_cmd)
|
||||
|
||||
offset = 0
|
||||
|
||||
@@ -170,7 +227,7 @@ class AxiLiteMasterWrite(object):
|
||||
|
||||
for k in range(cycles):
|
||||
start = 0
|
||||
stop = self.byte_width
|
||||
stop = self.byte_lanes
|
||||
strb = self.strb_mask
|
||||
|
||||
if k == 0:
|
||||
@@ -186,23 +243,22 @@ class AxiLiteMasterWrite(object):
|
||||
offset += 1
|
||||
|
||||
aw = self.aw_channel._transaction_obj()
|
||||
aw.awaddr = word_addr + k*self.byte_width
|
||||
aw.awaddr = word_addr + k*self.byte_lanes
|
||||
aw.awprot = cmd.prot
|
||||
|
||||
w = self.w_channel._transaction_obj()
|
||||
w.wdata = val
|
||||
w.wstrb = strb
|
||||
|
||||
await self.aw_channel.drive(aw)
|
||||
await self.aw_channel.send(aw)
|
||||
await self.w_channel.send(w)
|
||||
|
||||
self.current_write_command = None
|
||||
|
||||
async def _process_write_resp(self):
|
||||
while True:
|
||||
if not self.int_write_resp_command_queue:
|
||||
self.int_write_resp_command_sync.clear()
|
||||
await self.int_write_resp_command_sync.wait()
|
||||
|
||||
cmd = self.int_write_resp_command_queue.popleft()
|
||||
cmd = await self.int_write_resp_command_queue.get()
|
||||
self.current_write_resp_command = cmd
|
||||
|
||||
resp = AxiResp.OKAY
|
||||
|
||||
@@ -219,82 +275,95 @@ class AxiLiteMasterWrite(object):
|
||||
|
||||
write_resp = AxiLiteWriteResp(cmd.address, cmd.length, resp)
|
||||
|
||||
if cmd.event is not None:
|
||||
cmd.event.set(write_resp)
|
||||
else:
|
||||
self.write_resp_queue.append(write_resp)
|
||||
self.write_resp_sync.set()
|
||||
|
||||
self.current_write_resp_command = None
|
||||
|
||||
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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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(bus.ar, clock, reset, reset_active_level)
|
||||
self.ar_channel.queue_occupancy_limit = 2
|
||||
self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level)
|
||||
self.r_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.ar_channel = AxiLiteARSource(entity, name, clock, reset)
|
||||
self.r_channel = AxiLiteRSink(entity, name, clock, reset)
|
||||
self.read_command_queue = Queue()
|
||||
self.current_read_command = None
|
||||
|
||||
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.int_read_resp_command_queue = Queue()
|
||||
self.current_read_resp_command = None
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle = Event()
|
||||
self._idle.set()
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.arprot_present = hasattr(self.bus.ar, "arprot")
|
||||
|
||||
self.log.info("AXI lite master configuration:")
|
||||
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI lite master signals:")
|
||||
for bus in (self.bus.ar, self.bus.r):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
cocotb.fork(self._process_read_resp())
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
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):
|
||||
if event is None:
|
||||
event = Event()
|
||||
|
||||
if not isinstance(event, Event):
|
||||
raise ValueError("Expected event object")
|
||||
|
||||
self.in_flight_operations += 1
|
||||
if not self.arprot_present and prot != AxiProt.NONSECURE:
|
||||
raise ValueError("arprot sideband signal value specified, but signal is not connected")
|
||||
|
||||
self.read_command_queue.append(AxiLiteReadCmd(address, length, prot, event))
|
||||
self.read_command_sync.set()
|
||||
self.in_flight_operations += 1
|
||||
self._idle.clear()
|
||||
|
||||
self.read_command_queue.put_nowait(AxiLiteReadCmd(address, length, prot, event))
|
||||
|
||||
return event
|
||||
|
||||
def idle(self):
|
||||
return not self.in_flight_operations
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
self.read_data_sync.clear()
|
||||
await self.read_data_sync.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return bool(self.read_data_queue)
|
||||
|
||||
def get_read_data(self):
|
||||
if self.read_data_queue:
|
||||
return self.read_data_queue.popleft()
|
||||
return None
|
||||
await self._idle.wait()
|
||||
|
||||
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
||||
event = Event()
|
||||
self.init_read(address, length, prot, event)
|
||||
event = self.init_read(address, length, prot)
|
||||
await event.wait()
|
||||
return event.data
|
||||
|
||||
@@ -323,42 +392,82 @@ 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
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
|
||||
def flush_cmd(cmd):
|
||||
self.log.warning("Flushed read operation during reset: %s", cmd)
|
||||
if cmd.event:
|
||||
cmd.event.set(None)
|
||||
|
||||
while not self.read_command_queue.empty():
|
||||
cmd = self.read_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_read_command:
|
||||
cmd = self.current_read_command
|
||||
self.current_read_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
while not self.int_read_resp_command_queue.empty():
|
||||
cmd = self.int_read_resp_command_queue.get_nowait()
|
||||
flush_cmd(cmd)
|
||||
|
||||
if self.current_read_resp_command:
|
||||
cmd = self.current_read_resp_command
|
||||
self.current_read_resp_command = None
|
||||
flush_cmd(cmd)
|
||||
|
||||
self.in_flight_operations = 0
|
||||
self._idle.set()
|
||||
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())
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
if not self.read_command_queue:
|
||||
self.read_command_sync.clear()
|
||||
await self.read_command_sync.wait()
|
||||
cmd = await self.read_command_queue.get()
|
||||
self.current_read_command = cmd
|
||||
|
||||
cmd = self.read_command_queue.popleft()
|
||||
word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes
|
||||
|
||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
||||
|
||||
cycles = (cmd.length + self.byte_width-1 + (cmd.address % self.byte_width)) // self.byte_width
|
||||
cycles = (cmd.length + self.byte_lanes-1 + (cmd.address % self.byte_lanes)) // self.byte_lanes
|
||||
|
||||
resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event)
|
||||
self.int_read_resp_command_queue.append(resp_cmd)
|
||||
self.int_read_resp_command_sync.set()
|
||||
await self.int_read_resp_command_queue.put(resp_cmd)
|
||||
|
||||
self.log.info("Read start addr: 0x%08x prot: %s length: %d",
|
||||
cmd.address, cmd.prot, cmd.length)
|
||||
|
||||
for k in range(cycles):
|
||||
ar = self.ar_channel._transaction_obj()
|
||||
ar.araddr = word_addr + k*self.byte_width
|
||||
ar.araddr = word_addr + k*self.byte_lanes
|
||||
ar.arprot = cmd.prot
|
||||
|
||||
await self.ar_channel.drive(ar)
|
||||
await self.ar_channel.send(ar)
|
||||
|
||||
self.current_read_command = None
|
||||
|
||||
async def _process_read_resp(self):
|
||||
while True:
|
||||
if not self.int_read_resp_command_queue:
|
||||
self.int_read_resp_command_sync.clear()
|
||||
await self.int_read_resp_command_sync.wait()
|
||||
cmd = await self.int_read_resp_command_queue.get()
|
||||
self.current_read_resp_command = cmd
|
||||
|
||||
cmd = self.int_read_resp_command_queue.popleft()
|
||||
|
||||
start_offset = cmd.address % self.byte_width
|
||||
end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1
|
||||
start_offset = cmd.address % self.byte_lanes
|
||||
end_offset = ((cmd.address + cmd.length - 1) % self.byte_lanes) + 1
|
||||
|
||||
data = bytearray()
|
||||
|
||||
@@ -374,7 +483,7 @@ class AxiLiteMasterRead(object):
|
||||
resp = cycle_resp
|
||||
|
||||
start = 0
|
||||
stop = self.byte_width
|
||||
stop = self.byte_lanes
|
||||
|
||||
if k == 0:
|
||||
start = start_offset
|
||||
@@ -389,28 +498,29 @@ class AxiLiteMasterRead(object):
|
||||
|
||||
read_resp = AxiLiteReadResp(cmd.address, data, resp)
|
||||
|
||||
if cmd.event is not None:
|
||||
cmd.event.set(read_resp)
|
||||
else:
|
||||
self.read_data_queue.append(read_resp)
|
||||
self.read_data_sync.set()
|
||||
|
||||
self.current_read_resp_command = None
|
||||
|
||||
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)
|
||||
return self.read_if.init_read(address, length, prot, event)
|
||||
|
||||
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
|
||||
self.write_if.init_write(address, data, prot, event)
|
||||
return self.write_if.init_write(address, data, prot, event)
|
||||
|
||||
def idle(self):
|
||||
return (not self.read_if or self.read_if.idle()) and (not self.write_if or self.write_if.idle())
|
||||
@@ -426,18 +536,6 @@ class AxiLiteMaster(object):
|
||||
async def wait_write(self):
|
||||
await self.write_if.wait()
|
||||
|
||||
def read_data_ready(self):
|
||||
return self.read_if.read_data_ready()
|
||||
|
||||
def get_read_data(self):
|
||||
return self.read_if.get_read_data()
|
||||
|
||||
def write_resp_ready(self):
|
||||
return self.write_if.write_resp_ready()
|
||||
|
||||
def get_write_resp(self):
|
||||
return self.write_if.get_write_resp()
|
||||
|
||||
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
||||
return await self.read_if.read(address, length, prot)
|
||||
|
||||
|
||||
@@ -30,11 +30,15 @@ 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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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,35 +47,59 @@ 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.aw_channel.queue_occupancy_limit = 2
|
||||
self.w_channel = AxiLiteWSink(bus.w, clock, reset, reset_active_level)
|
||||
self.w_channel.queue_occupancy_limit = 2
|
||||
self.b_channel = AxiLiteBSource(bus.b, clock, reset, reset_active_level)
|
||||
self.b_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.width = len(self.w_channel.bus.wdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_width-1
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
self.strb_mask = 2**self.byte_lanes-1
|
||||
|
||||
self.log.info("AXI lite RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI lite RAM model signals:")
|
||||
for bus in (self.bus.aw, self.bus.w, self.bus.b):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
cocotb.fork(self._process_write())
|
||||
assert self.byte_lanes == len(self.w_channel.bus.wstrb)
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
self._process_write_cr = None
|
||||
|
||||
self._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
|
||||
|
||||
self.aw_channel.clear()
|
||||
self.w_channel.clear()
|
||||
self.b_channel.clear()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_write_cr is None:
|
||||
self._process_write_cr = cocotb.fork(self._process_write())
|
||||
|
||||
async def _process_write(self):
|
||||
while True:
|
||||
aw = await self.aw_channel.recv()
|
||||
|
||||
addr = (int(aw.awaddr) // self.byte_width) * self.byte_width
|
||||
addr = (int(aw.awaddr) // self.byte_lanes) * self.byte_lanes
|
||||
prot = AxiProt(aw.awprot)
|
||||
|
||||
w = await self.w_channel.recv()
|
||||
@@ -83,12 +111,12 @@ class AxiLiteRamWrite(Memory):
|
||||
|
||||
self.mem.seek(addr % self.size)
|
||||
|
||||
data = data.to_bytes(self.byte_width, 'little')
|
||||
data = data.to_bytes(self.byte_lanes, 'little')
|
||||
|
||||
self.log.info("Write data awaddr: 0x%08x awprot: %s wstrb: 0x%02x data: %s",
|
||||
addr, prot, strb, ' '.join((f'{c:02x}' for c in data)))
|
||||
|
||||
for i in range(self.byte_width):
|
||||
for i in range(self.byte_lanes):
|
||||
if strb & (1 << i):
|
||||
self.mem.write(data[i:i+1])
|
||||
else:
|
||||
@@ -100,9 +128,12 @@ 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.bus = bus
|
||||
self.clock = clock
|
||||
self.reset = reset
|
||||
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,39 +142,61 @@ 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.ar_channel.queue_occupancy_limit = 2
|
||||
self.r_channel = AxiLiteRSource(bus.r, clock, reset, reset_active_level)
|
||||
self.r_channel.queue_occupancy_limit = 2
|
||||
|
||||
self.width = len(self.r_channel.bus.rdata)
|
||||
self.byte_size = 8
|
||||
self.byte_width = self.width // self.byte_size
|
||||
self.byte_lanes = self.width // self.byte_size
|
||||
|
||||
self.log.info("AXI lite RAM model configuration:")
|
||||
self.log.info(" Memory size: %d bytes", len(self.mem))
|
||||
self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr))
|
||||
self.log.info(" Byte size: %d bits", self.byte_size)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width)
|
||||
self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_lanes)
|
||||
|
||||
assert self.byte_width * self.byte_size == self.width
|
||||
self.log.info("AXI lite RAM model signals:")
|
||||
for bus in (self.bus.ar, self.bus.r):
|
||||
for sig in sorted(list(set().union(bus._signals, bus._optional_signals))):
|
||||
if hasattr(bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig)))
|
||||
else:
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
cocotb.fork(self._process_read())
|
||||
assert self.byte_lanes * self.byte_size == self.width
|
||||
|
||||
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
|
||||
|
||||
self.ar_channel.clear()
|
||||
self.r_channel.clear()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._process_read_cr is None:
|
||||
self._process_read_cr = cocotb.fork(self._process_read())
|
||||
|
||||
async def _process_read(self):
|
||||
while True:
|
||||
ar = await self.ar_channel.recv()
|
||||
|
||||
addr = (int(ar.araddr) // self.byte_width) * self.byte_width
|
||||
addr = (int(ar.araddr) // self.byte_lanes) * self.byte_lanes
|
||||
prot = AxiProt(ar.arprot)
|
||||
|
||||
# todo latency
|
||||
|
||||
self.mem.seek(addr % self.size)
|
||||
|
||||
data = self.mem.read(self.byte_width)
|
||||
data = self.mem.read(self.byte_lanes)
|
||||
|
||||
r = self.r_channel._transaction_obj()
|
||||
r.rdata = int.from_bytes(data, 'little')
|
||||
@@ -156,11 +209,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)
|
||||
|
||||
@@ -23,22 +23,27 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import deque
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, ReadOnly, Timer, First, Event
|
||||
from cocotb.bus import Bus
|
||||
from cocotb.queue import Queue, QueueFull
|
||||
from cocotb.triggers import RisingEdge, Timer, First, Event
|
||||
from cocotb.utils import get_sim_time
|
||||
from cocotb_bus.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")
|
||||
@@ -232,11 +276,12 @@ class AxiStreamSource(object):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.active = False
|
||||
self.queue = deque()
|
||||
|
||||
self.pause = False
|
||||
self._pause_generator = None
|
||||
self._pause_cr = None
|
||||
self.queue = Queue()
|
||||
self.dequeue_event = Event()
|
||||
self.current_frame = None
|
||||
self.idle_event = Event()
|
||||
self.idle_event.set()
|
||||
self.active_event = Event()
|
||||
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
@@ -244,21 +289,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,62 +316,69 @@ 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")
|
||||
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))
|
||||
|
||||
self.log.info("AXI stream %s signals:", self._type)
|
||||
for sig in sorted(list(set().union(self.bus._signals, self.bus._optional_signals))):
|
||||
if hasattr(self.bus, sig):
|
||||
self.log.info(" %s width: %d bits", sig, len(getattr(self.bus, sig)))
|
||||
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")
|
||||
self.log.info(" %s: not present", sig)
|
||||
|
||||
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())
|
||||
self._run_cr = None
|
||||
|
||||
async def send(self, frame):
|
||||
self.send_nowait(frame)
|
||||
|
||||
def send_nowait(self, frame):
|
||||
frame = AxiStreamFrame(frame)
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
self.queue.append(frame)
|
||||
|
||||
async def write(self, data):
|
||||
await self.send(data)
|
||||
|
||||
def write_nowait(self, data):
|
||||
self.send_nowait(data)
|
||||
self._init_reset(reset, reset_active_level)
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
return self.queue.qsize()
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
return self.queue.empty()
|
||||
|
||||
def idle(self):
|
||||
return self.empty() and not self.active
|
||||
def clear(self):
|
||||
while not self.queue.empty():
|
||||
frame = self.queue.get_nowait()
|
||||
frame.sim_time_end = None
|
||||
frame.handle_tx_complete()
|
||||
self.dequeue_event.set()
|
||||
self.idle_event.set()
|
||||
self.active_event.clear()
|
||||
self.queue_occupancy_bytes = 0
|
||||
self.queue_occupancy_frames = 0
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
await RisingEdge(self.clock)
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._run_cr is not None:
|
||||
self._run_cr.kill()
|
||||
self._run_cr = None
|
||||
|
||||
self.active = False
|
||||
|
||||
if self.queue.empty():
|
||||
self.idle_event.set()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.fork(self._run())
|
||||
|
||||
async def _run(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class 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:
|
||||
@@ -345,46 +393,116 @@ class AxiStreamSource(object):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
async def send(self, frame):
|
||||
while self.full():
|
||||
self.dequeue_event.clear()
|
||||
await self.dequeue_event.wait()
|
||||
frame = AxiStreamFrame(frame)
|
||||
await self.queue.put(frame)
|
||||
self.idle_event.clear()
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
def send_nowait(self, frame):
|
||||
if self.full():
|
||||
raise QueueFull()
|
||||
frame = AxiStreamFrame(frame)
|
||||
self.queue.put_nowait(frame)
|
||||
self.idle_event.clear()
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
async def write(self, data):
|
||||
await self.send(data)
|
||||
|
||||
def write_nowait(self, data):
|
||||
self.send_nowait(data)
|
||||
|
||||
def full(self):
|
||||
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
||||
return True
|
||||
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def idle(self):
|
||||
return self.empty() and not self.active
|
||||
|
||||
async def wait(self):
|
||||
await self.idle_event.wait()
|
||||
|
||||
def _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
if state:
|
||||
self.bus.tdata.value = 0
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid.value = 0
|
||||
if hasattr(self.bus, "tlast"):
|
||||
self.bus.tlast.value = 0
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
self.bus.tkeep.value = 0
|
||||
if hasattr(self.bus, "tid"):
|
||||
self.bus.tid.value = 0
|
||||
if hasattr(self.bus, "tdest"):
|
||||
self.bus.tdest.value = 0
|
||||
if hasattr(self.bus, "tuser"):
|
||||
self.bus.tuser.value = 0
|
||||
|
||||
if self.current_frame:
|
||||
self.log.warning("Flushed transmit frame during reset: %s", self.current_frame)
|
||||
self.current_frame.handle_tx_complete()
|
||||
self.current_frame = None
|
||||
|
||||
async def _run(self):
|
||||
frame = None
|
||||
frame_offset = 0
|
||||
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 = None
|
||||
self.active = False
|
||||
self.bus.tdata <= 0
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid <= 0
|
||||
if hasattr(self.bus, "tlast"):
|
||||
self.bus.tlast <= 0
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
self.bus.tkeep <= 0
|
||||
if hasattr(self.bus, "tid"):
|
||||
self.bus.tid <= 0
|
||||
if hasattr(self.bus, "tdest"):
|
||||
self.bus.tdest <= 0
|
||||
if hasattr(self.bus, "tuser"):
|
||||
self.bus.tuser <= 0
|
||||
continue
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
|
||||
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
||||
if frame is None and self.queue:
|
||||
frame = self.queue.popleft()
|
||||
if frame is None and not self.queue.empty():
|
||||
frame = self.queue.get_nowait()
|
||||
self.dequeue_event.set()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
self.queue_occupancy_frames -= 1
|
||||
self.current_frame = frame
|
||||
frame.sim_time_start = get_sim_time()
|
||||
frame.sim_time_end = None
|
||||
self.log.info("TX frame: %s", frame)
|
||||
frame.normalize()
|
||||
self.active = True
|
||||
frame_offset = 0
|
||||
|
||||
if frame and not self.pause:
|
||||
tdata_val = 0
|
||||
@@ -395,143 +513,76 @@ class AxiStreamSource(object):
|
||||
tuser_val = 0
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
tdata_val |= (frame.tdata.pop(0) & self.byte_mask) << (offset * self.byte_size)
|
||||
tkeep_val |= (frame.tkeep.pop(0) & 1) << offset
|
||||
tid_val = frame.tid.pop(0)
|
||||
tdest_val = frame.tdest.pop(0)
|
||||
tuser_val = frame.tuser.pop(0)
|
||||
tdata_val |= (frame.tdata[frame_offset] & self.byte_mask) << (offset * self.byte_size)
|
||||
tkeep_val |= (frame.tkeep[frame_offset] & 1) << offset
|
||||
tid_val = frame.tid[frame_offset]
|
||||
tdest_val = frame.tdest[frame_offset]
|
||||
tuser_val = frame.tuser[frame_offset]
|
||||
frame_offset += 1
|
||||
|
||||
if len(frame.tdata) == 0:
|
||||
if frame_offset >= len(frame.tdata):
|
||||
tlast_val = 1
|
||||
frame.sim_time_end = get_sim_time()
|
||||
frame.handle_tx_complete()
|
||||
frame = None
|
||||
self.current_frame = None
|
||||
break
|
||||
|
||||
self.bus.tdata <= tdata_val
|
||||
self.bus.tdata.value = tdata_val
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid <= 1
|
||||
self.bus.tvalid.value = 1
|
||||
if hasattr(self.bus, "tlast"):
|
||||
self.bus.tlast <= tlast_val
|
||||
self.bus.tlast.value = tlast_val
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
self.bus.tkeep <= tkeep_val
|
||||
self.bus.tkeep.value = tkeep_val
|
||||
if hasattr(self.bus, "tid"):
|
||||
self.bus.tid <= tid_val
|
||||
self.bus.tid.value = tid_val
|
||||
if hasattr(self.bus, "tdest"):
|
||||
self.bus.tdest <= tdest_val
|
||||
self.bus.tdest.value = tdest_val
|
||||
if hasattr(self.bus, "tuser"):
|
||||
self.bus.tuser <= tuser_val
|
||||
self.bus.tuser.value = tuser_val
|
||||
else:
|
||||
if hasattr(self.bus, "tvalid"):
|
||||
self.bus.tvalid <= 0
|
||||
self.bus.tvalid.value = 0
|
||||
if hasattr(self.bus, "tlast"):
|
||||
self.bus.tlast <= 0
|
||||
self.bus.tlast.value = 0
|
||||
self.active = bool(frame)
|
||||
|
||||
async def _run_pause(self):
|
||||
for val in self._pause_generator:
|
||||
self.pause = val
|
||||
await RisingEdge(self.clock)
|
||||
if not frame and self.queue.empty():
|
||||
self.idle_event.set()
|
||||
|
||||
|
||||
class AxiStreamSink(object):
|
||||
class AxiStreamMonitor(AxiStreamBase):
|
||||
|
||||
_signals = ["tdata"]
|
||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
||||
_type = "monitor"
|
||||
|
||||
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)
|
||||
_init_x = False
|
||||
|
||||
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")
|
||||
_valid_init = None
|
||||
_ready_init = None
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
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.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()
|
||||
return self.recv_nowait(compact)
|
||||
|
||||
def recv_nowait(self, compact=True):
|
||||
if self.queue:
|
||||
frame = self.queue.popleft()
|
||||
def _recv(self, frame, compact=True):
|
||||
if self.queue.empty():
|
||||
self.active_event.clear()
|
||||
self.queue_occupancy_bytes -= len(frame)
|
||||
self.queue_occupancy_frames -= 1
|
||||
if compact:
|
||||
frame.compact()
|
||||
return frame
|
||||
return None
|
||||
|
||||
async def recv(self, compact=True):
|
||||
frame = await self.queue.get()
|
||||
return self._recv(frame, compact)
|
||||
|
||||
def recv_nowait(self, compact=True):
|
||||
frame = self.queue.get_nowait()
|
||||
return self._recv(frame, compact)
|
||||
|
||||
async def read(self, count=-1):
|
||||
while not self.read_queue:
|
||||
@@ -549,67 +600,38 @@ 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()
|
||||
if timeout:
|
||||
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
||||
await First(self.active_event.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.active_event.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:
|
||||
for offset in range(self.byte_lanes):
|
||||
if frame is None:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame.sim_time_start = get_sim_time()
|
||||
self.active = True
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1)
|
||||
@@ -621,176 +643,73 @@ 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.put_nowait(frame)
|
||||
self.active_event.set()
|
||||
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame = None
|
||||
else:
|
||||
self.active = bool(frame)
|
||||
|
||||
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 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.value = 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:
|
||||
for offset in range(self.byte_lanes):
|
||||
if frame is None:
|
||||
if self.byte_size == 8:
|
||||
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||
else:
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
frame.sim_time_start = get_sim_time()
|
||||
self.active = True
|
||||
|
||||
for offset in range(self.byte_lanes):
|
||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||
if hasattr(self.bus, "tkeep"):
|
||||
frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1)
|
||||
@@ -802,14 +721,18 @@ 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.append(frame)
|
||||
self.sync.set()
|
||||
self.queue_occupancy_bytes += len(frame)
|
||||
self.queue_occupancy_frames += 1
|
||||
|
||||
frame = AxiStreamFrame([], [], [], [], [])
|
||||
self.queue.put_nowait(frame)
|
||||
self.active_event.set()
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
frame = None
|
||||
else:
|
||||
self.active = bool(frame)
|
||||
|
||||
if hasattr(self.bus, "tready"):
|
||||
self.bus.tready.value = (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()
|
||||
@@ -23,14 +23,33 @@ THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections import deque
|
||||
|
||||
import cocotb
|
||||
from cocotb.triggers import RisingEdge, ReadOnly, Event, First, Timer
|
||||
from cocotb.bus import Bus
|
||||
from cocotb.queue import Queue, QueueFull
|
||||
from cocotb.triggers import RisingEdge, Event, First, Timer
|
||||
from cocotb_bus.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,18 +82,23 @@ 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.queue = deque()
|
||||
self.queue_sync = Event()
|
||||
self.active = False
|
||||
|
||||
self.queue = Queue()
|
||||
self.dequeue_event = Event()
|
||||
self.idle_event = Event()
|
||||
self.idle_event.set()
|
||||
self.active_event = Event()
|
||||
|
||||
self.ready = None
|
||||
self.valid = None
|
||||
@@ -98,17 +122,44 @@ 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)
|
||||
return self.queue.qsize()
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
return self.queue.empty()
|
||||
|
||||
def clear(self):
|
||||
self.queue.clear()
|
||||
while not self.queue.empty():
|
||||
self.queue.get_nowait()
|
||||
self.dequeue_event.set()
|
||||
self.idle_event.set()
|
||||
self.active_event.clear()
|
||||
|
||||
def _handle_reset(self, state):
|
||||
if state:
|
||||
self.log.info("Reset asserted")
|
||||
if self._run_cr is not None:
|
||||
self._run_cr.kill()
|
||||
self._run_cr = None
|
||||
|
||||
self.active = False
|
||||
|
||||
if self.queue.empty():
|
||||
self.idle_event.set()
|
||||
else:
|
||||
self.log.info("Reset de-asserted")
|
||||
if self._run_cr is None:
|
||||
self._run_cr = cocotb.fork(self._run())
|
||||
|
||||
async def _run(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class StreamPause(object):
|
||||
class StreamPause:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -137,237 +188,155 @@ 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, bus, clock, reset=None, reset_active_level=True, *args, **kwargs):
|
||||
super().__init__(bus, clock, reset, reset_active_level, *args, **kwargs)
|
||||
|
||||
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
|
||||
self.queue_occupancy_limit = -1
|
||||
|
||||
async def send(self, obj):
|
||||
self.send_nowait(obj)
|
||||
while self.full():
|
||||
self.dequeue_event.clear()
|
||||
await self.dequeue_event.wait()
|
||||
await self.queue.put(obj)
|
||||
self.idle_event.clear()
|
||||
|
||||
def send_nowait(self, obj):
|
||||
self.queue.append(obj)
|
||||
self.queue_sync.set()
|
||||
if self.full():
|
||||
raise QueueFull()
|
||||
self.queue.put_nowait(obj)
|
||||
self.idle_event.clear()
|
||||
|
||||
def full(self):
|
||||
if self.queue_occupancy_limit > 0 and self.count() >= self.queue_occupancy_limit:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def idle(self):
|
||||
return self.empty() and not self.active
|
||||
|
||||
async def wait(self):
|
||||
while not self.idle():
|
||||
await RisingEdge(self.clock)
|
||||
await self.idle_event.wait()
|
||||
|
||||
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):
|
||||
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 state:
|
||||
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.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)
|
||||
self.valid.value = 0
|
||||
|
||||
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()
|
||||
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.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)
|
||||
if (ready_sample and valid_sample) or (not valid_sample):
|
||||
if not self.queue.empty() and not self.pause:
|
||||
self.bus.drive(self.queue.get_nowait())
|
||||
self.dequeue_event.set()
|
||||
if self.valid is not None:
|
||||
self.valid.value = 1
|
||||
self.active = True
|
||||
else:
|
||||
if self.valid is not None:
|
||||
self.valid.value = 0
|
||||
self.active = not self.queue.empty()
|
||||
if self.queue.empty():
|
||||
self.idle_event.set()
|
||||
|
||||
|
||||
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())
|
||||
def _recv(self, item):
|
||||
if self.queue.empty():
|
||||
self.active_event.clear()
|
||||
return item
|
||||
|
||||
async def recv(self):
|
||||
while self.empty():
|
||||
self.queue_sync.clear()
|
||||
await self.queue_sync.wait()
|
||||
return self.recv_nowait()
|
||||
item = await self.queue.get()
|
||||
return self._recv(item)
|
||||
|
||||
def recv_nowait(self):
|
||||
if self.queue:
|
||||
return self.queue.popleft()
|
||||
return None
|
||||
|
||||
def count(self):
|
||||
return len(self.queue)
|
||||
|
||||
def empty(self):
|
||||
return not self.queue
|
||||
item = self.queue.get_nowait()
|
||||
return self._recv(item)
|
||||
|
||||
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))
|
||||
await First(self.active_event.wait(), Timer(timeout, timeout_unit))
|
||||
else:
|
||||
await self.queue_sync.wait()
|
||||
await self.active_event.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.put_nowait(obj)
|
||||
self.active_event.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 self.count() >= self.queue_occupancy_limit:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _handle_reset(self, state):
|
||||
super()._handle_reset(state)
|
||||
|
||||
if state:
|
||||
if self.ready is not None:
|
||||
self.ready.value = 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.put_nowait(obj)
|
||||
self.active_event.set()
|
||||
|
||||
await RisingEdge(self.clock)
|
||||
if self.ready is not None:
|
||||
self.ready.value = (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 +377,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 +394,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.14"
|
||||
|
||||
@@ -17,9 +17,10 @@ long-description-content-type = text/markdown
|
||||
platforms = any
|
||||
classifiers =
|
||||
Development Status :: 3 - Alpha
|
||||
Programming Language :: Python :: 3
|
||||
Framework :: cocotb
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python :: 3
|
||||
Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
||||
|
||||
[options]
|
||||
@@ -27,6 +28,7 @@ packages = find_namespace:
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
cocotb
|
||||
cocotb-bus
|
||||
|
||||
[options.extras_require]
|
||||
test =
|
||||
|
||||
@@ -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)
|
||||
@@ -73,10 +73,10 @@ class TB(object):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 1
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 0
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
@@ -85,7 +85,7 @@ async def run_test_write(dut, idle_inserter=None, backpressure_inserter=None, si
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axi_master.write_if.byte_width
|
||||
byte_lanes = tb.axi_master.write_if.byte_lanes
|
||||
max_burst_size = tb.axi_master.write_if.max_burst_size
|
||||
|
||||
if size is None:
|
||||
@@ -96,8 +96,8 @@ async def run_test_write(dut, idle_inserter=None, backpressure_inserter=None, si
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
for length in list(range(1, byte_width*2))+[1024]:
|
||||
for offset in list(range(byte_width))+list(range(4096-byte_width, 4096)):
|
||||
for length in list(range(1, byte_lanes*2))+[1024]:
|
||||
for offset in list(range(byte_lanes))+list(range(4096-byte_lanes, 4096)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
@@ -120,7 +120,7 @@ async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None, siz
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axi_master.write_if.byte_width
|
||||
byte_lanes = tb.axi_master.write_if.byte_lanes
|
||||
max_burst_size = tb.axi_master.write_if.max_burst_size
|
||||
|
||||
if size is None:
|
||||
@@ -131,8 +131,8 @@ async def run_test_read(dut, idle_inserter=None, backpressure_inserter=None, siz
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
for length in list(range(1, byte_width*2))+[1024]:
|
||||
for offset in list(range(byte_width))+list(range(4096-byte_width, 4096)):
|
||||
for length in list(range(1, byte_lanes*2))+[1024]:
|
||||
for offset in list(range(byte_lanes))+list(range(4096-byte_lanes, 4096)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
@@ -151,15 +151,20 @@ async def run_test_write_words(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axi_master.write_if.byte_width
|
||||
byte_lanes = tb.axi_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
for length in list(range(1, 4)):
|
||||
for offset in list(range(byte_width)):
|
||||
for offset in list(range(byte_lanes)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
event = tb.axi_master.init_write(addr, test_data)
|
||||
await event.wait()
|
||||
assert tb.axi_ram.read(addr, length) == test_data
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
await tb.axi_master.write(addr, test_data)
|
||||
assert tb.axi_ram.read(addr, length) == test_data
|
||||
@@ -200,15 +205,21 @@ async def run_test_read_words(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axi_master.write_if.byte_width
|
||||
byte_lanes = tb.axi_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
for length in list(range(1, 4)):
|
||||
for offset in list(range(byte_width)):
|
||||
for offset in list(range(byte_lanes)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
tb.axi_ram.write(addr, test_data)
|
||||
event = tb.axi_master.init_read(addr, length)
|
||||
await event.wait()
|
||||
assert event.data.data == test_data
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
tb.axi_ram.write(addr, test_data)
|
||||
assert (await tb.axi_master.read(addr, length)).data == test_data
|
||||
@@ -287,9 +298,9 @@ def cycle_pause():
|
||||
|
||||
if cocotb.SIM_NAME:
|
||||
|
||||
data_width = int(os.getenv("PARAM_DATA_WIDTH"))
|
||||
byte_width = data_width // 8
|
||||
max_burst_size = (byte_width-1).bit_length()
|
||||
data_width = len(cocotb.top.axi_wdata)
|
||||
byte_lanes = data_width // 8
|
||||
max_burst_size = (byte_lanes-1).bit_length()
|
||||
|
||||
for test in [run_test_write, run_test_read]:
|
||||
|
||||
@@ -311,7 +322,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 +348,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:
|
||||
@@ -70,10 +70,10 @@ class TB(object):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 1
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 0
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
@@ -82,15 +82,15 @@ async def run_test_write(dut, data_in=None, idle_inserter=None, backpressure_ins
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axil_master.write_if.byte_width
|
||||
byte_lanes = tb.axil_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
for length in range(1, byte_width*2):
|
||||
for offset in range(byte_width):
|
||||
for length in range(1, byte_lanes*2):
|
||||
for offset in range(byte_lanes):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
@@ -113,15 +113,15 @@ async def run_test_read(dut, data_in=None, idle_inserter=None, backpressure_inse
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axil_master.write_if.byte_width
|
||||
byte_lanes = tb.axil_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
tb.set_idle_generator(idle_inserter)
|
||||
tb.set_backpressure_generator(backpressure_inserter)
|
||||
|
||||
for length in range(1, byte_width*2):
|
||||
for offset in range(byte_width):
|
||||
for length in range(1, byte_lanes*2):
|
||||
for offset in range(byte_lanes):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
@@ -140,15 +140,20 @@ async def run_test_write_words(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axil_master.write_if.byte_width
|
||||
byte_lanes = tb.axil_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
for length in list(range(1, 4)):
|
||||
for offset in list(range(byte_width)):
|
||||
for offset in list(range(byte_lanes)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
event = tb.axil_master.init_write(addr, test_data)
|
||||
await event.wait()
|
||||
assert tb.axil_ram.read(addr, length) == test_data
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
await tb.axil_master.write(addr, test_data)
|
||||
assert tb.axil_ram.read(addr, length) == test_data
|
||||
@@ -189,15 +194,21 @@ async def run_test_read_words(dut):
|
||||
|
||||
tb = TB(dut)
|
||||
|
||||
byte_width = tb.axil_master.write_if.byte_width
|
||||
byte_lanes = tb.axil_master.write_if.byte_lanes
|
||||
|
||||
await tb.cycle_reset()
|
||||
|
||||
for length in list(range(1, 4)):
|
||||
for offset in list(range(byte_width)):
|
||||
for offset in list(range(byte_lanes)):
|
||||
tb.log.info("length %d, offset %d", length, offset)
|
||||
addr = offset+0x1000
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
tb.axil_ram.write(addr, test_data)
|
||||
event = tb.axil_master.init_read(addr, length)
|
||||
await event.wait()
|
||||
assert event.data.data == test_data
|
||||
|
||||
test_data = bytearray([x % 256 for x in range(length)])
|
||||
tb.axil_ram.write(addr, test_data)
|
||||
assert (await tb.axil_master.read(addr, length)).data == test_data
|
||||
@@ -295,7 +306,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 +326,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:
|
||||
@@ -63,10 +63,10 @@ class TB(object):
|
||||
self.dut.rst.setimmediatevalue(0)
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 1
|
||||
self.dut.rst.value = 1
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
self.dut.rst <= 0
|
||||
self.dut.rst.value = 0
|
||||
await RisingEdge(self.dut.clk)
|
||||
await RisingEdge(self.dut.clk)
|
||||
|
||||
@@ -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()
|
||||
@@ -123,7 +126,7 @@ def cycle_pause():
|
||||
|
||||
|
||||
def size_list():
|
||||
data_width = int(os.getenv("PARAM_DATA_WIDTH"))
|
||||
data_width = len(cocotb.top.axis_tdata)
|
||||
byte_width = data_width // 8
|
||||
return list(range(1, byte_width*4+1)) + [512] + [1]*64
|
||||
|
||||
@@ -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