Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8668a6bb4f | ||
|
|
bdaeaad66b | ||
|
|
cd272b2a59 | ||
|
|
01212e37cd | ||
|
|
40a9bb9eac | ||
|
|
1103783b08 | ||
|
|
2451921923 | ||
|
|
e6e8a06dfe | ||
|
|
3dd8114c05 | ||
|
|
a84b52077b | ||
|
|
200c8c0b26 | ||
|
|
0ff3d64540 | ||
|
|
237745792c |
155
README.md
155
README.md
@@ -3,12 +3,13 @@
|
|||||||
[](https://github.com/alexforencich/cocotbext-axi/actions/)
|
[](https://github.com/alexforencich/cocotbext-axi/actions/)
|
||||||
[](https://codecov.io/gh/alexforencich/cocotbext-axi)
|
[](https://codecov.io/gh/alexforencich/cocotbext-axi)
|
||||||
[](https://pypi.org/project/cocotbext-axi)
|
[](https://pypi.org/project/cocotbext-axi)
|
||||||
|
[](https://pepy.tech/project/cocotbext-axi)
|
||||||
|
|
||||||
GitHub repository: https://github.com/alexforencich/cocotbext-axi
|
GitHub repository: https://github.com/alexforencich/cocotbext-axi
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
AXI, AXI lite, and AXI stream simulation models for cocotb.
|
AXI, AXI lite, and AXI stream simulation models for [cocotb](https://github.com/cocotb/cocotb).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -37,11 +38,11 @@ The `AxiMaster` is a wrapper around `AxiMasterWrite` and `AxiMasterRead`. Simil
|
|||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
from cocotbext.axi import AxiMaster
|
from cocotbext.axi import AxiBus, AxiMaster
|
||||||
|
|
||||||
axi_master = AxiMaster(dut, "s_axi", dut.clk, dut.rst)
|
axi_master = AxiMaster(AxiBus.from_prefix(dut, "s_axi"), dut.clk, dut.rst)
|
||||||
|
|
||||||
The modules use `cocotb.bus.Bus` internally to automatically connect to the corresponding signals in the bus, presuming they are named according to the AXI spec and have a common prefix.
|
The first argument to the constructor accepts an `AxiBus` or `AxiLiteBus` object. These objects are containers for the interface signals and include class methods to automate connections.
|
||||||
|
|
||||||
Once the module is instantiated, read and write operations can be initiated in a few different ways.
|
Once the module is instantiated, read and write operations can be initiated in a few different ways.
|
||||||
|
|
||||||
@@ -74,10 +75,10 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
|||||||
|
|
||||||
#### `AxiMaster` and `AxiLiteMaster` constructor parameters
|
#### `AxiMaster` and `AxiLiteMaster` constructor parameters
|
||||||
|
|
||||||
* _entity_: object that contains the AXI slave interface signals
|
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
||||||
* _name_: signal name prefix (e.g. for `s_axi_awaddr`, the prefix is `s_axi`)
|
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
|
|
||||||
#### Additional parameters for `AxiMaster`
|
#### Additional parameters for `AxiMaster`
|
||||||
|
|
||||||
@@ -96,21 +97,21 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
|||||||
* `write_resp_ready()`: determine if any write response is available
|
* `write_resp_ready()`: determine if any write response is available
|
||||||
* `get_write_resp()`: fetch first available write response
|
* `get_write_resp()`: fetch first available write response
|
||||||
* `read(address, length, ...)`: read _length_ bytes, starting at _address_
|
* `read(address, length, ...)`: read _length_ bytes, starting at _address_
|
||||||
* `read_words(address, count, byteorder, ws, ...)`: read _count_ _ws_-byte words, starting at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `read_words(address, count, byteorder='little', ws=2, ...)`: read _count_ _ws_-byte words, starting at _address_
|
||||||
* `read_dwords(address, count, byteorder, ...)`: read _count_ 4-byte dwords, starting at _address_, default _byteorder_ `"little"`
|
* `read_dwords(address, count, byteorder='little', ...)`: read _count_ 4-byte dwords, starting at _address_
|
||||||
* `read_qwords(address, count, byteorder, ...)`: read _count_ 8-byte qwords, starting at _address_, default _byteorder_ `"little"`
|
* `read_qwords(address, count, byteorder='little', ...)`: read _count_ 8-byte qwords, starting at _address_
|
||||||
* `read_byte(address, ...)`: read single byte at _address_
|
* `read_byte(address, ...)`: read single byte at _address_
|
||||||
* `read_word(address, byteorder, ws, ...)`: read single _ws_-byte word at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `read_word(address, byteorder='little', ws=2, ...)`: read single _ws_-byte word at _address_
|
||||||
* `read_dword(address, byteorder, ...)`: read single 4-byte dword at _address_, default _byteorder_ `"little"`
|
* `read_dword(address, byteorder='little', ...)`: read single 4-byte dword at _address_
|
||||||
* `read_qword(address, byteorder, ...)`: read single 8-byte qword at _address_, default _byteorder_ `"little"`
|
* `read_qword(address, byteorder='little', ...)`: read single 8-byte qword at _address_
|
||||||
* `write(address, data, ...)`: write _data_ (bytes), starting at _address_
|
* `write(address, data, ...)`: write _data_ (bytes), starting at _address_
|
||||||
* `write_words(address, data, byteorder, ws, ...)`: write _data_ (_ws_-byte words), starting at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `write_words(address, data, byteorder='little', ws=2, ...)`: write _data_ (_ws_-byte words), starting at _address_
|
||||||
* `write_dwords(address, data, byteorder, ...)`: write _data_ (4-byte dwords), starting at _address_, default _byteorder_ `"little"`
|
* `write_dwords(address, data, byteorder='little', ...)`: write _data_ (4-byte dwords), starting at _address_
|
||||||
* `write_qwords(address, data, byteorder, ...)`: write _data_ (8-byte qwords), starting at _address_, default _byteorder_ `"little"`
|
* `write_qwords(address, data, byteorder='little', ...)`: write _data_ (8-byte qwords), starting at _address_
|
||||||
* `write_byte(address, data, ...)`: write single byte at _address_
|
* `write_byte(address, data, ...)`: write single byte at _address_
|
||||||
* `write_word(address, data, byteorder, ws, ...)`: write single _ws_-byte word at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `write_word(address, data, byteorder='little', ws=2, ...)`: write single _ws_-byte word at _address_
|
||||||
* `write_dword(address, data, byteorder, ...)`: write single 4-byte dword at _address_, default _byteorder_ `"little"`
|
* `write_dword(address, data, byteorder='little', ...)`: write single 4-byte dword at _address_
|
||||||
* `write_qword(address, data, byteorder, ...)`: write single 8-byte qword at _address_, default _byteorder_ `"little"`
|
* `write_qword(address, data, byteorder='little', ...)`: write single 8-byte qword at _address_
|
||||||
|
|
||||||
#### Additional optional arguments for `AxiMaster`
|
#### Additional optional arguments for `AxiMaster`
|
||||||
|
|
||||||
@@ -131,6 +132,10 @@ Second, blocking operations can be carried out with `read()` and `write()` and t
|
|||||||
* _prot_: AXI protection flags, default `AxiProt.NONSECURE`
|
* _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` (`init_read()` and `init_write()` only). If provided, the event will be triggered when the operation completes and the result returned via `Event.data` instead of `get_read_data()` or `get_write_resp()`.
|
||||||
|
|
||||||
|
#### `AxiBus` and `AxiLiteBus` objects
|
||||||
|
|
||||||
|
The `AxiBus`, `AxiLiteBus`, and related objects are containers for the interface signals. These hold instances of bus objects for the individual channels, which are extensions of `cocotb.bus.Bus`. Class methods `from_entity` and `from_prefix` are provided to facilitate signal name matching. For AXI interfaces use `AxiBus`, `AxiReadBus`, or `AxiWriteBus`, as appropriate. For AXI lite interfaces, use `AxiLiteBus`, `AxiLiteReadBus`, or `AxiLiteWriteBus`, as appropriate.
|
||||||
|
|
||||||
### AXI and AXI lite RAM
|
### AXI and AXI lite RAM
|
||||||
|
|
||||||
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts.
|
The `AxiRam` and `AxiLiteRam` classes implement AXI RAMs and are capable of completing read and write operations from upstream AXI masters. The `AxiRam` module is capable of handling narrow bursts.
|
||||||
@@ -139,11 +144,11 @@ The `AxiRam` is a wrapper around `AxiRamWrite` and `AxiRamRead`. Similarly, `Ax
|
|||||||
|
|
||||||
To use these modules, import the one you need and connect it to the DUT:
|
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:
|
Once the module is instantiated, the memory contents can be accessed in a couple of different ways. First, the `mmap` object can be accessed directly via the `mem` attribute. Second, `read()`, `write()`, and various word-access wrappers are available. Hex dump helper methods are also provided for debugging. For example:
|
||||||
|
|
||||||
@@ -152,17 +157,17 @@ Once the module is instantiated, the memory contents can be accessed in a couple
|
|||||||
|
|
||||||
Multi-port memories can be constructed by passing the `mem` object of the first instance to the other instances. For example, here is how to create a four-port RAM:
|
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_p1 = AxiRam(AxiBus.from_prefix(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_p2 = AxiRam(AxiBus.from_prefix(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_p3 = AxiRam(AxiBus.from_prefix(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_p4 = AxiRam(AxiBus.from_prefix(dut, "m03_axi"), dut.clk, dut.rst, mem=axi_ram_p1.mem)
|
||||||
|
|
||||||
#### `AxiRam` and `AxiLiteRam` constructor parameters
|
#### `AxiRam` and `AxiLiteRam` constructor parameters
|
||||||
|
|
||||||
* _entity_: object that contains the AXI master interface signals
|
* _bus_: `AxiBus` or `AxiLiteBus` object containing AXI interface signals
|
||||||
* _name_: signal name prefix (e.g. for `m_axi_awaddr`, the prefix is `m_axi`)
|
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
* _size_: memory size in bytes (optional, default 1024)
|
* _size_: memory size in bytes (optional, default 1024)
|
||||||
* _mem_: mmap object to use (optional, overrides _size_)
|
* _mem_: mmap object to use (optional, overrides _size_)
|
||||||
|
|
||||||
@@ -173,48 +178,55 @@ Multi-port memories can be constructed by passing the `mem` object of the first
|
|||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
* `read(address, length)`: read _length_ bytes, starting at _address_
|
* `read(address, length)`: read _length_ bytes, starting at _address_
|
||||||
* `read_words(address, count, byteorder, ws)`: read _count_ _ws_-byte words, starting at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `read_words(address, count, byteorder='little', ws=2)`: read _count_ _ws_-byte words, starting at _address_
|
||||||
* `read_dwords(address, count, byteorder)`: read _count_ 4-byte dwords, starting at _address_, default _byteorder_ `"little"`
|
* `read_dwords(address, count, byteorder='little')`: read _count_ 4-byte dwords, starting at _address_
|
||||||
* `read_qwords(address, count, byteorder)`: read _count_ 8-byte qwords, starting at _address_, default _byteorder_ `"little"`
|
* `read_qwords(address, count, byteorder='little')`: read _count_ 8-byte qwords, starting at _address_
|
||||||
* `read_byte(address)`: read single byte at _address_
|
* `read_byte(address)`: read single byte at _address_
|
||||||
* `read_word(address, byteorder, ws)`: read single _ws_-byte word at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `read_word(address, byteorder='little', ws=2)`: read single _ws_-byte word at _address_
|
||||||
* `read_dword(address, byteorder)`: read single 4-byte dword at _address_, default _byteorder_ `"little"`
|
* `read_dword(address, byteorder='little')`: read single 4-byte dword at _address_
|
||||||
* `read_qword(address, byteorder)`: read single 8-byte qword at _address_, default _byteorder_ `"little"`
|
* `read_qword(address, byteorder='little')`: read single 8-byte qword at _address_
|
||||||
* `write(address, data)`: write _data_ (bytes), starting at _address_
|
* `write(address, data)`: write _data_ (bytes), starting at _address_
|
||||||
* `write_words(address, data, byteorder, ws)`: write _data_ (_ws_-byte words), starting at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `write_words(address, data, byteorder='little', ws=2)`: write _data_ (_ws_-byte words), starting at _address_
|
||||||
* `write_dwords(address, data, byteorder)`: write _data_ (4-byte dwords), starting at _address_, default _byteorder_ `"little"`
|
* `write_dwords(address, data, byteorder='little')`: write _data_ (4-byte dwords), starting at _address_
|
||||||
* `write_qwords(address, data, byteorder)`: write _data_ (8-byte qwords), starting at _address_, default _byteorder_ `"little"`
|
* `write_qwords(address, data, byteorder='little')`: write _data_ (8-byte qwords), starting at _address_
|
||||||
* `write_byte(address, data)`: write single byte at _address_
|
* `write_byte(address, data)`: write single byte at _address_
|
||||||
* `write_word(address, data, byteorder, ws)`: write single _ws_-byte word at _address_, default word size of `2`, default _byteorder_ `"little"`
|
* `write_word(address, data, byteorder='little', ws=2)`: write single _ws_-byte word at _address_
|
||||||
* `write_dword(address, data, byteorder)`: write single 4-byte dword at _address_, default _byteorder_ `"little"`
|
* `write_dword(address, data, byteorder='little')`: write single 4-byte dword at _address_
|
||||||
* `write_qword(address, data, byteorder)`: write single 8-byte qword at _address_, default _byteorder_ `"little"`
|
* `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(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_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_str(address, length, prefix='')`: return hex dump (str) of _length_ bytes starting from _address_, prefix lines with optional _prefix_
|
||||||
|
|
||||||
### AXI stream
|
### 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:
|
To use these modules, import the one you need and connect it to the DUT:
|
||||||
|
|
||||||
from cocotbext.axi import AxiStreamSource, AxiStreamSink
|
from cocotbext.axi import (AxiStreamBus, AxiStreamSource, AxiStreamSink, AxiStreamMonitor)
|
||||||
|
|
||||||
axis_source = AxiStreamSource(dut, "s_axis", dut.clk, dut.rst)
|
axis_source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "s_axis"), dut.clk, dut.rst)
|
||||||
axis_sink = AxiStreamSink(dut, "m_axis", dut.clk, dut.rst)
|
axis_sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "m_axis"), dut.clk, dut.rst)
|
||||||
axis_monitor = AxiStreamMonitor(dut.inst, "int_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()
|
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()
|
frame = AxiStreamFrame(b'test data', tx_complete=Event())
|
||||||
data = axis_sink.recv()
|
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
|
#### Signals
|
||||||
|
|
||||||
@@ -229,10 +241,14 @@ To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()` or
|
|||||||
|
|
||||||
#### Constructor parameters:
|
#### Constructor parameters:
|
||||||
|
|
||||||
* _entity_: object that contains the AXI stream interface signals
|
* _bus_: `AxiStreamBus` object containing AXI stream interface signals
|
||||||
* _name_: signal name prefix (e.g. for `m_axis_tdata`, the prefix is `m_axis`)
|
|
||||||
* _clock_: clock signal
|
* _clock_: clock signal
|
||||||
* _reset_: reset signal (optional)
|
* _reset_: reset signal (optional)
|
||||||
|
* _reset_active_level_: reset active level (optional, default `True`)
|
||||||
|
* _byte_size_: byte size (optional)
|
||||||
|
* _byte_lanes_: byte lane count (optional)
|
||||||
|
|
||||||
|
Note: _byte_size_, _byte_lanes_, `len(tdata)`, and `len(tkeep)` are all related, in that _byte_lanes_ is set from `tkeep` if it is connected, and `byte_size*byte_lanes == len(tdata)`. So, if `tkeep` is connected, both _byte_size_ and _byte_lanes_ will be computed internally and cannot be overridden. If `tkeep` is not connected, then either _byte_size_ or _byte_lanes_ can be specified, and the other will be computed such that `byte_size*byte_lanes == len(tdata)`.
|
||||||
|
|
||||||
#### Attributes:
|
#### Attributes:
|
||||||
|
|
||||||
@@ -244,21 +260,31 @@ To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()` or
|
|||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
* `send(frame)`: send _frame_ (source)
|
* `send(frame)`: send _frame_ (blocking) (source)
|
||||||
* `write(data)`: send _data_ (alias of send) (source)
|
* `send_nowait(frame)`: send _frame_ (non-blocking) (source)
|
||||||
* `recv(compact)`: receive a frame, optionally compact frame (sink/monitor)
|
* `write(data)`: send _data_ (alias of send) (blocking) (source)
|
||||||
* `read(count)`: read _count_ bytes from buffer (sink/monitor)
|
* `write_nowait(data)`: send _data_ (alias of send_nowait) (non-blocking) (source)
|
||||||
|
* `recv(compact=True)`: receive a frame as a `GmiiFrame` (blocking) (sink)
|
||||||
|
* `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)
|
||||||
* `count()`: returns the number of items in the queue (all)
|
* `count()`: returns the number of items in the queue (all)
|
||||||
* `empty()`: returns _True_ if the queue is empty (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 (sink)
|
||||||
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
|
* `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source)
|
||||||
* `wait(timeout=0, timeout_unit='ns')`: wait for idle (source) or frame received (sink/monitor)
|
* `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)
|
* `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
|
* `clear_pause_generator()`: remove generator for pause signal (source/sink)
|
||||||
|
|
||||||
#### AxiStreamFrame object
|
#### `AxiStreamBus` 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, 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.
|
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.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
@@ -267,6 +293,9 @@ Attributes:
|
|||||||
* `tid`: tid field, optional; int or list with one entry per `tdata`, last value used per cycle when sending.
|
* `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.
|
* `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.
|
* `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:
|
Methods:
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ from .version import __version__
|
|||||||
|
|
||||||
from .constants import AxiBurstType, AxiBurstSize, AxiLockType, AxiCacheBit, AxiProt, AxiResp
|
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_master import AxiLiteMasterWrite, AxiLiteMasterRead, AxiLiteMaster
|
||||||
from .axil_ram import AxiLiteRamWrite, AxiLiteRamRead, AxiLiteRam
|
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_master import AxiMasterWrite, AxiMasterRead, AxiMaster
|
||||||
from .axi_ram import AxiRamWrite, AxiRamRead, AxiRam
|
from .axi_ram import AxiRamWrite, AxiRamRead, AxiRam
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ THE SOFTWARE.
|
|||||||
from .stream import define_stream
|
from .stream import define_stream
|
||||||
|
|
||||||
# Write address channel
|
# Write address channel
|
||||||
AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
AxiAWBus, AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
||||||
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awprot", "awvalid", "awready"],
|
signals=["awid", "awaddr", "awlen", "awsize", "awburst", "awprot", "awvalid", "awready"],
|
||||||
optional_signals=["awlock", "awcache", "awqos", "awregion", "awuser"],
|
optional_signals=["awlock", "awcache", "awqos", "awregion", "awuser"],
|
||||||
signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1,
|
signal_widths={"awlen": 8, "awsize": 3, "awburst": 2, "awlock": 1,
|
||||||
@@ -33,21 +33,21 @@ AxiAWTransaction, AxiAWSource, AxiAWSink, AxiAWMonitor = define_stream("AxiAW",
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Write data channel
|
# Write data channel
|
||||||
AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
AxiWBus, AxiWTransaction, AxiWSource, AxiWSink, AxiWMonitor = define_stream("AxiW",
|
||||||
signals=["wdata", "wstrb", "wlast", "wvalid", "wready"],
|
signals=["wdata", "wstrb", "wlast", "wvalid", "wready"],
|
||||||
optional_signals=["wuser"],
|
optional_signals=["wuser"],
|
||||||
signal_widths={"wlast": 1}
|
signal_widths={"wlast": 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write response channel
|
# Write response channel
|
||||||
AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
|
AxiBBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor = define_stream("AxiB",
|
||||||
signals=["bid", "bresp", "bvalid", "bready"],
|
signals=["bid", "bresp", "bvalid", "bready"],
|
||||||
optional_signals=["buser"],
|
optional_signals=["buser"],
|
||||||
signal_widths={"bresp": 2}
|
signal_widths={"bresp": 2}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read address channel
|
# Read address channel
|
||||||
AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
AxiARBus, AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
||||||
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arprot", "arvalid", "arready"],
|
signals=["arid", "araddr", "arlen", "arsize", "arburst", "arprot", "arvalid", "arready"],
|
||||||
optional_signals=["arlock", "arcache", "arqos", "arregion", "aruser"],
|
optional_signals=["arlock", "arcache", "arqos", "arregion", "aruser"],
|
||||||
signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1,
|
signal_widths={"arlen": 8, "arsize": 3, "arburst": 2, "arlock": 1,
|
||||||
@@ -55,8 +55,79 @@ AxiARTransaction, AxiARSource, AxiARSink, AxiARMonitor = define_stream("AxiAR",
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Read data channel
|
# Read data channel
|
||||||
AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
AxiRBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor = define_stream("AxiR",
|
||||||
signals=["rid", "rdata", "rresp", "rlast", "rvalid", "rready"],
|
signals=["rid", "rdata", "rresp", "rlast", "rvalid", "rready"],
|
||||||
optional_signals=["ruser"],
|
optional_signals=["ruser"],
|
||||||
signal_widths={"rresp": 2, "rlast": 1}
|
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)
|
||||||
|
|||||||
@@ -22,15 +22,17 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import deque, namedtuple, Counter
|
import logging
|
||||||
|
from collections import namedtuple, Counter
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
|
from cocotb.queue import Queue
|
||||||
from cocotb.triggers import Event
|
from cocotb.triggers import Event
|
||||||
from cocotb.log import SimLog
|
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import AxiBurstType, AxiLockType, AxiProt, AxiResp
|
from .constants import AxiBurstType, AxiLockType, AxiProt, AxiResp
|
||||||
from .axi_channels import AxiAWSource, AxiWSource, AxiBSink, AxiARSource, AxiRSink
|
from .axi_channels import AxiAWSource, AxiWSource, AxiBSink, AxiARSource, AxiRSink
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
# AXI master write helper objects
|
# AXI master write helper objects
|
||||||
AxiWriteCmd = namedtuple("AxiWriteCmd", ["address", "data", "awid", "burst", "size",
|
AxiWriteCmd = namedtuple("AxiWriteCmd", ["address", "data", "awid", "burst", "size",
|
||||||
@@ -47,36 +49,32 @@ AxiReadRespCmd = namedtuple("AxiReadRespCmd", ["address", "length", "size", "cyc
|
|||||||
AxiReadResp = namedtuple("AxiReadResp", ["address", "data", "resp", "user"])
|
AxiReadResp = namedtuple("AxiReadResp", ["address", "data", "resp", "user"])
|
||||||
|
|
||||||
|
|
||||||
class AxiMasterWrite(object):
|
class AxiMasterWrite(Reset):
|
||||||
def __init__(self, entity, name, clock, reset=None, max_burst_len=256):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
|
||||||
self.log.info("AXI master (write)")
|
self.log.info("AXI master (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.reset = reset
|
self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level)
|
||||||
|
self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level)
|
||||||
|
self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level)
|
||||||
|
|
||||||
self.aw_channel = AxiAWSource(entity, name, clock, reset)
|
self.write_command_queue = Queue()
|
||||||
self.w_channel = AxiWSource(entity, name, clock, reset)
|
self.write_resp_queue = Queue()
|
||||||
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.id_count = 2**len(self.aw_channel.bus.awid)
|
self.id_count = 2**len(self.aw_channel.bus.awid)
|
||||||
self.cur_id = 0
|
self.cur_id = 0
|
||||||
self.active_id = Counter()
|
self.active_id = Counter()
|
||||||
|
|
||||||
self.int_write_resp_command_queue = deque()
|
self.int_write_resp_command_queue = Queue()
|
||||||
self.int_write_resp_command_sync = Event()
|
self.int_write_resp_queue_list = [Queue() for k in range(self.id_count)]
|
||||||
self.int_write_resp_queue_list = [deque() for k in range(self.id_count)]
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
self.in_flight_operations = 0
|
||||||
|
self._idle = Event()
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
self.width = len(self.w_channel.bus.wdata)
|
self.width = len(self.w_channel.bus.wdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -100,8 +98,10 @@ class AxiMasterWrite(object):
|
|||||||
|
|
||||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||||
|
|
||||||
cocotb.fork(self._process_write())
|
self._process_write_cr = None
|
||||||
cocotb.fork(self._process_write_resp())
|
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,
|
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):
|
cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None):
|
||||||
@@ -132,26 +132,25 @@ class AxiMasterWrite(object):
|
|||||||
wuser = list(wuser)
|
wuser = list(wuser)
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
cmd = AxiWriteCmd(address, bytearray(data), awid, burst, size, lock,
|
cmd = AxiWriteCmd(address, bytearray(data), awid, burst, size, lock,
|
||||||
cache, prot, qos, region, user, wuser, event)
|
cache, prot, qos, region, user, wuser, event)
|
||||||
self.write_command_queue.append(cmd)
|
self.write_command_queue.put_nowait(cmd)
|
||||||
self.write_command_sync.set()
|
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.in_flight_operations
|
return not self.in_flight_operations
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
self.write_resp_sync.clear()
|
await self._idle.wait()
|
||||||
await self.write_resp_sync.wait()
|
|
||||||
|
|
||||||
def write_resp_ready(self):
|
def write_resp_ready(self):
|
||||||
return bool(self.write_resp_queue)
|
return not self.write_resp_queue.empty()
|
||||||
|
|
||||||
def get_write_resp(self):
|
def get_write_resp(self):
|
||||||
if self.write_resp_queue:
|
if not self.write_resp_queue.empty():
|
||||||
return self.write_resp_queue.popleft()
|
return self.write_resp_queue.get_nowait()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None,
|
async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None,
|
||||||
@@ -198,13 +197,45 @@ class AxiMasterWrite(object):
|
|||||||
await self.write_qwords(address, [data], byteorder, awid, burst, size,
|
await self.write_qwords(address, [data], byteorder, awid, burst, size,
|
||||||
lock, cache, prot, qos, region, user, wuser)
|
lock, cache, prot, qos, region, user, wuser)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_write_cr is not None:
|
||||||
|
self._process_write_cr.kill()
|
||||||
|
self._process_write_cr = None
|
||||||
|
if self._process_write_resp_cr is not None:
|
||||||
|
self._process_write_resp_cr.kill()
|
||||||
|
self._process_write_resp_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_write_cr is None:
|
||||||
|
self._process_write_cr = cocotb.fork(self._process_write())
|
||||||
|
if self._process_write_resp_cr is None:
|
||||||
|
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
||||||
|
|
||||||
|
self.aw_channel.clear()
|
||||||
|
self.w_channel.clear()
|
||||||
|
self.b_channel.clear()
|
||||||
|
|
||||||
|
while not self.write_command_queue.empty():
|
||||||
|
cmd = self.write_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.int_write_resp_command_queue.empty():
|
||||||
|
cmd = self.int_write_resp_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.write_resp_queue.empty():
|
||||||
|
self.write_resp_queue.get_nowait()
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.write_command_queue:
|
cmd = await self.write_command_queue.get()
|
||||||
self.write_command_sync.clear()
|
|
||||||
await self.write_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.write_command_queue.popleft()
|
|
||||||
|
|
||||||
num_bytes = 2**cmd.size
|
num_bytes = 2**cmd.size
|
||||||
|
|
||||||
@@ -277,7 +308,7 @@ class AxiMasterWrite(object):
|
|||||||
aw.awuser = cmd.user
|
aw.awuser = cmd.user
|
||||||
|
|
||||||
self.active_id[awid] += 1
|
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",
|
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)
|
awid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||||
@@ -297,40 +328,33 @@ class AxiMasterWrite(object):
|
|||||||
else:
|
else:
|
||||||
w.wuser = 0
|
w.wuser = 0
|
||||||
|
|
||||||
self.w_channel.send(w)
|
await self.w_channel.send(w)
|
||||||
|
|
||||||
cur_addr += num_bytes
|
cur_addr += num_bytes
|
||||||
cycle_offset = (cycle_offset + num_bytes) % self.byte_width
|
cycle_offset = (cycle_offset + num_bytes) % self.byte_width
|
||||||
|
|
||||||
resp_cmd = AxiWriteRespCmd(cmd.address, len(cmd.data), cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
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)
|
await self.int_write_resp_command_queue.put(resp_cmd)
|
||||||
self.int_write_resp_command_sync.set()
|
|
||||||
|
|
||||||
async def _process_write_resp(self):
|
async def _process_write_resp(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.int_write_resp_command_queue:
|
cmd = await self.int_write_resp_command_queue.get()
|
||||||
self.int_write_resp_command_sync.clear()
|
|
||||||
await self.int_write_resp_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.int_write_resp_command_queue.popleft()
|
|
||||||
|
|
||||||
resp = AxiResp.OKAY
|
resp = AxiResp.OKAY
|
||||||
user = []
|
user = []
|
||||||
|
|
||||||
for bid, burst_length in cmd.burst_list:
|
for bid, burst_length in cmd.burst_list:
|
||||||
while True:
|
while self.int_write_resp_queue_list[bid].empty():
|
||||||
if self.int_write_resp_queue_list[bid]:
|
b = await self.b_channel.recv()
|
||||||
break
|
|
||||||
|
|
||||||
await self.b_channel.wait()
|
i = int(b.bid)
|
||||||
b = self.b_channel.recv()
|
|
||||||
|
|
||||||
if self.active_id[int(b.bid)] <= 0:
|
if self.active_id[i] <= 0:
|
||||||
raise Exception(f"Unexpected burst ID {bid}")
|
raise Exception(f"Unexpected burst ID {bid}")
|
||||||
|
|
||||||
self.int_write_resp_queue_list[int(b.bid)].append(b)
|
self.int_write_resp_queue_list[i].put_nowait(b)
|
||||||
|
|
||||||
b = self.int_write_resp_queue_list[bid].popleft()
|
b = self.int_write_resp_queue_list[bid].get_nowait()
|
||||||
|
|
||||||
burst_id = int(b.bid)
|
burst_id = int(b.bid)
|
||||||
burst_resp = AxiResp(b.bresp)
|
burst_resp = AxiResp(b.bresp)
|
||||||
@@ -357,41 +381,39 @@ class AxiMasterWrite(object):
|
|||||||
if cmd.event is not None:
|
if cmd.event is not None:
|
||||||
cmd.event.set(write_resp)
|
cmd.event.set(write_resp)
|
||||||
else:
|
else:
|
||||||
self.write_resp_queue.append(write_resp)
|
self.write_resp_queue.put_nowait(write_resp)
|
||||||
self.write_resp_sync.set()
|
|
||||||
|
|
||||||
self.in_flight_operations -= 1
|
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):
|
class AxiMasterRead(Reset):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256):
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
|
||||||
self.log.info("AXI master (read)")
|
self.log.info("AXI master (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.reset = reset
|
self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level)
|
||||||
|
self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level)
|
||||||
|
|
||||||
self.ar_channel = AxiARSource(entity, name, clock, reset)
|
self.read_command_queue = Queue()
|
||||||
self.r_channel = AxiRSink(entity, name, clock, reset)
|
self.read_data_queue = Queue()
|
||||||
|
|
||||||
self.read_command_queue = deque()
|
|
||||||
self.read_command_sync = Event()
|
|
||||||
self.read_data_queue = deque()
|
|
||||||
self.read_data_sync = Event()
|
|
||||||
self.read_data_set = set()
|
|
||||||
|
|
||||||
self.id_count = 2**len(self.ar_channel.bus.arid)
|
self.id_count = 2**len(self.ar_channel.bus.arid)
|
||||||
self.cur_id = 0
|
self.cur_id = 0
|
||||||
self.active_id = Counter()
|
self.active_id = Counter()
|
||||||
|
|
||||||
self.int_read_resp_command_queue = deque()
|
self.int_read_resp_command_queue = Queue()
|
||||||
self.int_read_resp_command_sync = Event()
|
self.int_read_resp_queue_list = [Queue() for k in range(self.id_count)]
|
||||||
self.int_read_resp_queue_list = [deque() for k in range(self.id_count)]
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
self.in_flight_operations = 0
|
||||||
|
self._idle = Event()
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
self.width = len(self.r_channel.bus.rdata)
|
self.width = len(self.r_channel.bus.rdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -413,8 +435,10 @@ class AxiMasterRead(object):
|
|||||||
|
|
||||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||||
|
|
||||||
cocotb.fork(self._process_read())
|
self._process_read_cr = None
|
||||||
cocotb.fork(self._process_read_resp())
|
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,
|
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):
|
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
|
||||||
@@ -441,25 +465,24 @@ class AxiMasterRead(object):
|
|||||||
prot = AxiProt(prot)
|
prot = AxiProt(prot)
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event)
|
||||||
self.read_command_queue.append(cmd)
|
self.read_command_queue.put_nowait(cmd)
|
||||||
self.read_command_sync.set()
|
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.in_flight_operations
|
return not self.in_flight_operations
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
self.read_data_sync.clear()
|
await self._idle.wait()
|
||||||
await self.read_data_sync.wait()
|
|
||||||
|
|
||||||
def read_data_ready(self):
|
def read_data_ready(self):
|
||||||
return bool(self.read_data_queue)
|
return not self.read_data_queue.empty()
|
||||||
|
|
||||||
def get_read_data(self):
|
def get_read_data(self):
|
||||||
if self.read_data_queue:
|
if not self.read_data_queue.empty():
|
||||||
return self.read_data_queue.popleft()
|
return self.read_data_queue.get_nowait()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None,
|
||||||
@@ -506,13 +529,44 @@ class AxiMasterRead(object):
|
|||||||
return (await self.read_qwords(address, 1, byteorder, arid, burst, size,
|
return (await self.read_qwords(address, 1, byteorder, arid, burst, size,
|
||||||
lock, cache, prot, qos, region, user))[0]
|
lock, cache, prot, qos, region, user))[0]
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_read_cr is not None:
|
||||||
|
self._process_read_cr.kill()
|
||||||
|
self._process_read_cr = None
|
||||||
|
if self._process_read_resp_cr is not None:
|
||||||
|
self._process_read_resp_cr.kill()
|
||||||
|
self._process_read_resp_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_read_cr is None:
|
||||||
|
self._process_read_cr = cocotb.fork(self._process_read())
|
||||||
|
if self._process_read_resp_cr is None:
|
||||||
|
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
||||||
|
|
||||||
|
self.ar_channel.clear()
|
||||||
|
self.r_channel.clear()
|
||||||
|
|
||||||
|
while not self.read_command_queue.empty():
|
||||||
|
cmd = self.read_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.int_read_resp_command_queue.empty():
|
||||||
|
cmd = self.int_read_resp_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.read_data_queue.empty():
|
||||||
|
self.read_data_queue.get_nowait()
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.read_command_queue:
|
cmd = await self.read_command_queue.get()
|
||||||
self.read_command_sync.clear()
|
|
||||||
await self.read_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.read_command_queue.popleft()
|
|
||||||
|
|
||||||
num_bytes = 2**cmd.size
|
num_bytes = 2**cmd.size
|
||||||
|
|
||||||
@@ -562,7 +616,7 @@ class AxiMasterRead(object):
|
|||||||
ar.aruser = cmd.user
|
ar.aruser = cmd.user
|
||||||
|
|
||||||
self.active_id[arid] += 1
|
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",
|
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)
|
arid, cur_addr, burst_length-1, cmd.size, cmd.prot)
|
||||||
@@ -570,16 +624,11 @@ class AxiMasterRead(object):
|
|||||||
cur_addr += num_bytes
|
cur_addr += num_bytes
|
||||||
|
|
||||||
resp_cmd = AxiReadRespCmd(cmd.address, cmd.length, cmd.size, cycles, cmd.prot, burst_list, cmd.event)
|
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)
|
await self.int_read_resp_command_queue.put(resp_cmd)
|
||||||
self.int_read_resp_command_sync.set()
|
|
||||||
|
|
||||||
async def _process_read_resp(self):
|
async def _process_read_resp(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.int_read_resp_command_queue:
|
cmd = await self.int_read_resp_command_queue.get()
|
||||||
self.int_read_resp_command_sync.clear()
|
|
||||||
await self.int_read_resp_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.int_read_resp_command_queue.popleft()
|
|
||||||
|
|
||||||
num_bytes = 2**cmd.size
|
num_bytes = 2**cmd.size
|
||||||
|
|
||||||
@@ -598,19 +647,17 @@ class AxiMasterRead(object):
|
|||||||
|
|
||||||
for rid, burst_length in cmd.burst_list:
|
for rid, burst_length in cmd.burst_list:
|
||||||
for k in range(burst_length):
|
for k in range(burst_length):
|
||||||
while True:
|
while self.int_read_resp_queue_list[rid].empty():
|
||||||
if self.int_read_resp_queue_list[rid]:
|
r = await self.r_channel.recv()
|
||||||
break
|
|
||||||
|
|
||||||
await self.r_channel.wait()
|
i = int(r.rid)
|
||||||
r = self.r_channel.recv()
|
|
||||||
|
|
||||||
if self.active_id[int(r.rid)] <= 0:
|
if self.active_id[i] <= 0:
|
||||||
raise Exception(f"Unexpected burst ID {rid}")
|
raise Exception(f"Unexpected burst ID {rid}")
|
||||||
|
|
||||||
self.int_read_resp_queue_list[int(r.rid)].append(r)
|
self.int_read_resp_queue_list[i].put_nowait(r)
|
||||||
|
|
||||||
r = self.int_read_resp_queue_list[rid].popleft()
|
r = self.int_read_resp_queue_list[rid].get_nowait()
|
||||||
|
|
||||||
cycle_id = int(r.rid)
|
cycle_id = int(r.rid)
|
||||||
cycle_data = int(r.rdata)
|
cycle_data = int(r.rdata)
|
||||||
@@ -656,19 +703,21 @@ class AxiMasterRead(object):
|
|||||||
if cmd.event is not None:
|
if cmd.event is not None:
|
||||||
cmd.event.set(read_resp)
|
cmd.event.set(read_resp)
|
||||||
else:
|
else:
|
||||||
self.read_data_queue.append(read_resp)
|
self.read_data_queue.put_nowait(read_resp)
|
||||||
self.read_data_sync.set()
|
|
||||||
|
|
||||||
self.in_flight_operations -= 1
|
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.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
self.write_if = AxiMasterWrite(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(entity, name, clock, reset, 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,
|
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):
|
lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None):
|
||||||
|
|||||||
@@ -22,18 +22,20 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.log import SimLog
|
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import AxiBurstType, AxiProt, AxiResp
|
from .constants import AxiBurstType, AxiProt, AxiResp
|
||||||
from .axi_channels import AxiAWSink, AxiWSink, AxiBSource, AxiARSink, AxiRSource
|
from .axi_channels import AxiAWSink, AxiWSink, AxiBSource, AxiARSink, AxiRSource
|
||||||
from .memory import Memory
|
from .memory import Memory
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class AxiRamWrite(Memory):
|
class AxiRamWrite(Memory, Reset):
|
||||||
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.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
|
||||||
self.log.info("AXI RAM model (write)")
|
self.log.info("AXI RAM model (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
@@ -42,13 +44,9 @@ class AxiRamWrite(Memory):
|
|||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.reset = reset
|
self.aw_channel = AxiAWSink(bus.aw, clock, reset, reset_active_level)
|
||||||
|
self.w_channel = AxiWSink(bus.w, clock, reset, reset_active_level)
|
||||||
self.aw_channel = AxiAWSink(entity, name, clock, reset)
|
self.b_channel = AxiBSource(bus.b, clock, reset, reset_active_level)
|
||||||
self.w_channel = AxiWSink(entity, name, clock, reset)
|
|
||||||
self.b_channel = AxiBSource(entity, name, clock, reset)
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
|
||||||
|
|
||||||
self.width = len(self.w_channel.bus.wdata)
|
self.width = len(self.w_channel.bus.wdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -67,12 +65,28 @@ class AxiRamWrite(Memory):
|
|||||||
|
|
||||||
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid)
|
||||||
|
|
||||||
cocotb.fork(self._process_write())
|
self._process_write_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_write_cr is not None:
|
||||||
|
self._process_write_cr.kill()
|
||||||
|
self._process_write_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_write_cr is None:
|
||||||
|
self._process_write_cr = cocotb.fork(self._process_write())
|
||||||
|
|
||||||
|
self.aw_channel.clear()
|
||||||
|
self.w_channel.clear()
|
||||||
|
self.b_channel.clear()
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
await self.aw_channel.wait()
|
aw = await self.aw_channel.recv()
|
||||||
aw = self.aw_channel.recv()
|
|
||||||
|
|
||||||
awid = int(aw.awid)
|
awid = int(aw.awid)
|
||||||
addr = int(aw.awaddr)
|
addr = int(aw.awaddr)
|
||||||
@@ -105,8 +119,7 @@ class AxiRamWrite(Memory):
|
|||||||
for n in range(length):
|
for n in range(length):
|
||||||
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width
|
cur_word_addr = (cur_addr // self.byte_width) * self.byte_width
|
||||||
|
|
||||||
await self.w_channel.wait()
|
w = await self.w_channel.recv()
|
||||||
w = self.w_channel.recv()
|
|
||||||
|
|
||||||
data = int(w.wdata)
|
data = int(w.wdata)
|
||||||
strb = int(w.wstrb)
|
strb = int(w.wstrb)
|
||||||
@@ -140,12 +153,12 @@ class AxiRamWrite(Memory):
|
|||||||
b.bid = awid
|
b.bid = awid
|
||||||
b.bresp = AxiResp.OKAY
|
b.bresp = AxiResp.OKAY
|
||||||
|
|
||||||
self.b_channel.send(b)
|
await self.b_channel.send(b)
|
||||||
|
|
||||||
|
|
||||||
class AxiRamRead(Memory):
|
class AxiRamRead(Memory, Reset):
|
||||||
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.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
|
||||||
self.log.info("AXI RAM model (read)")
|
self.log.info("AXI RAM model (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
@@ -154,12 +167,8 @@ class AxiRamRead(Memory):
|
|||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.reset = reset
|
self.ar_channel = AxiARSink(bus.ar, clock, reset, reset_active_level)
|
||||||
|
self.r_channel = AxiRSource(bus.r, clock, reset, reset_active_level)
|
||||||
self.ar_channel = AxiARSink(entity, name, clock, reset)
|
|
||||||
self.r_channel = AxiRSource(entity, name, clock, reset)
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
|
||||||
|
|
||||||
self.width = len(self.r_channel.bus.rdata)
|
self.width = len(self.r_channel.bus.rdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -176,12 +185,27 @@ class AxiRamRead(Memory):
|
|||||||
|
|
||||||
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid)
|
||||||
|
|
||||||
cocotb.fork(self._process_read())
|
self._process_read_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_read_cr is not None:
|
||||||
|
self._process_read_cr.kill()
|
||||||
|
self._process_read_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_read_cr is None:
|
||||||
|
self._process_read_cr = cocotb.fork(self._process_read())
|
||||||
|
|
||||||
|
self.ar_channel.clear()
|
||||||
|
self.r_channel.clear()
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
await self.ar_channel.wait()
|
ar = await self.ar_channel.recv()
|
||||||
ar = self.ar_channel.recv()
|
|
||||||
|
|
||||||
arid = int(ar.arid)
|
arid = int(ar.arid)
|
||||||
addr = int(ar.araddr)
|
addr = int(ar.araddr)
|
||||||
@@ -224,7 +248,7 @@ class AxiRamRead(Memory):
|
|||||||
r.rlast = n == length-1
|
r.rlast = n == length-1
|
||||||
r.rresp = AxiResp.OKAY
|
r.rresp = AxiResp.OKAY
|
||||||
|
|
||||||
self.r_channel.send(r)
|
await self.r_channel.send(r)
|
||||||
|
|
||||||
self.log.debug("Read word awid: 0x%x addr: 0x%08x data: %s",
|
self.log.debug("Read word awid: 0x%x addr: 0x%08x data: %s",
|
||||||
arid, cur_addr, ' '.join((f'{c:02x}' for c in data)))
|
arid, cur_addr, ' '.join((f'{c:02x}' for c in data)))
|
||||||
@@ -238,11 +262,11 @@ class AxiRamRead(Memory):
|
|||||||
|
|
||||||
|
|
||||||
class AxiRam(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.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.write_if = AxiRamWrite(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(entity, name, clock, reset, mem=self.mem)
|
self.read_if = AxiRamRead(bus.read, clock, reset, reset_active_level, mem=self.mem)
|
||||||
|
|||||||
@@ -25,30 +25,101 @@ THE SOFTWARE.
|
|||||||
from .stream import define_stream
|
from .stream import define_stream
|
||||||
|
|
||||||
# Write address channel
|
# Write address channel
|
||||||
AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
AxiLiteAWBus, AxiLiteAWTransaction, AxiLiteAWSource, AxiLiteAWSink, AxiLiteAWMonitor = define_stream("AxiLiteAW",
|
||||||
signals=["awaddr", "awprot", "awvalid", "awready"],
|
signals=["awaddr", "awprot", "awvalid", "awready"],
|
||||||
signal_widths={"awprot": 3}
|
signal_widths={"awprot": 3}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write data channel
|
# Write data channel
|
||||||
AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
AxiLiteWBus, AxiLiteWTransaction, AxiLiteWSource, AxiLiteWSink, AxiLiteWMonitor = define_stream("AxiLiteW",
|
||||||
signals=["wdata", "wstrb", "wvalid", "wready"]
|
signals=["wdata", "wstrb", "wvalid", "wready"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write response channel
|
# Write response channel
|
||||||
AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
|
AxiLiteBBus, AxiLiteBTransaction, AxiLiteBSource, AxiLiteBSink, AxiLiteBMonitor = define_stream("AxiLiteB",
|
||||||
signals=["bresp", "bvalid", "bready"],
|
signals=["bresp", "bvalid", "bready"],
|
||||||
signal_widths={"bresp": 2}
|
signal_widths={"bresp": 2}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read address channel
|
# Read address channel
|
||||||
AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
AxiLiteARBus, AxiLiteARTransaction, AxiLiteARSource, AxiLiteARSink, AxiLiteARMonitor = define_stream("AxiLiteAR",
|
||||||
signals=["araddr", "arprot", "arvalid", "arready"],
|
signals=["araddr", "arprot", "arvalid", "arready"],
|
||||||
signal_widths={"arprot": 3}
|
signal_widths={"arprot": 3}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read data channel
|
# Read data channel
|
||||||
AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
AxiLiteRBus, AxiLiteRTransaction, AxiLiteRSource, AxiLiteRSink, AxiLiteRMonitor = define_stream("AxiLiteR",
|
||||||
signals=["rdata", "rresp", "rvalid", "rready"],
|
signals=["rdata", "rresp", "rvalid", "rready"],
|
||||||
signal_widths={"rresp": 2}
|
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)
|
||||||
|
|||||||
@@ -22,15 +22,17 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import deque, namedtuple
|
import logging
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
|
from cocotb.queue import Queue
|
||||||
from cocotb.triggers import Event
|
from cocotb.triggers import Event
|
||||||
from cocotb.log import SimLog
|
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import AxiProt, AxiResp
|
from .constants import AxiProt, AxiResp
|
||||||
from .axil_channels import AxiLiteAWSource, AxiLiteWSource, AxiLiteBSink, AxiLiteARSource, AxiLiteRSink
|
from .axil_channels import AxiLiteAWSource, AxiLiteWSource, AxiLiteBSink, AxiLiteARSource, AxiLiteRSink
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
# AXI lite master write
|
# AXI lite master write
|
||||||
AxiLiteWriteCmd = namedtuple("AxiLiteWriteCmd", ["address", "data", "prot", "event"])
|
AxiLiteWriteCmd = namedtuple("AxiLiteWriteCmd", ["address", "data", "prot", "event"])
|
||||||
@@ -43,31 +45,27 @@ AxiLiteReadRespCmd = namedtuple("AxiLiteReadRespCmd", ["address", "length", "cyc
|
|||||||
AxiLiteReadResp = namedtuple("AxiLiteReadResp", ["address", "data", "resp"])
|
AxiLiteReadResp = namedtuple("AxiLiteReadResp", ["address", "data", "resp"])
|
||||||
|
|
||||||
|
|
||||||
class AxiLiteMasterWrite(object):
|
class AxiLiteMasterWrite(Reset):
|
||||||
def __init__(self, entity, name, clock, reset=None):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite master (write)")
|
self.log.info("AXI lite master (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.reset = reset
|
self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level)
|
||||||
|
self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level)
|
||||||
|
self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level)
|
||||||
|
|
||||||
self.aw_channel = AxiLiteAWSource(entity, name, clock, reset)
|
self.write_command_queue = Queue()
|
||||||
self.w_channel = AxiLiteWSource(entity, name, clock, reset)
|
self.write_resp_queue = Queue()
|
||||||
self.b_channel = AxiLiteBSink(entity, name, clock, reset)
|
|
||||||
|
|
||||||
self.write_command_queue = deque()
|
self.int_write_resp_command_queue = Queue()
|
||||||
self.write_command_sync = Event()
|
|
||||||
self.write_resp_queue = deque()
|
|
||||||
self.write_resp_sync = Event()
|
|
||||||
self.write_resp_set = set()
|
|
||||||
|
|
||||||
self.int_write_resp_command_queue = deque()
|
|
||||||
self.int_write_resp_command_sync = Event()
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
self.in_flight_operations = 0
|
||||||
|
self._idle = Event()
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
self.width = len(self.w_channel.bus.wdata)
|
self.width = len(self.w_channel.bus.wdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -82,32 +80,33 @@ class AxiLiteMasterWrite(object):
|
|||||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_width * self.byte_size == self.width
|
assert self.byte_width * self.byte_size == self.width
|
||||||
|
|
||||||
cocotb.fork(self._process_write())
|
self._process_write_cr = None
|
||||||
cocotb.fork(self._process_write_resp())
|
self._process_write_resp_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
|
def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None):
|
||||||
if event is not None and not isinstance(event, Event):
|
if event is not None and not isinstance(event, Event):
|
||||||
raise ValueError("Expected event object")
|
raise ValueError("Expected event object")
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
self.write_command_queue.append(AxiLiteWriteCmd(address, bytearray(data), prot, event))
|
self.write_command_queue.put_nowait(AxiLiteWriteCmd(address, bytearray(data), prot, event))
|
||||||
self.write_command_sync.set()
|
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.in_flight_operations
|
return not self.in_flight_operations
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
self.write_resp_sync.clear()
|
await self._idle.wait()
|
||||||
await self.write_resp_sync.wait()
|
|
||||||
|
|
||||||
def write_resp_ready(self):
|
def write_resp_ready(self):
|
||||||
return bool(self.write_resp_queue)
|
return not self.write_resp_queue.empty()
|
||||||
|
|
||||||
def get_write_resp(self):
|
def get_write_resp(self):
|
||||||
if self.write_resp_queue:
|
if not self.write_resp_queue.empty():
|
||||||
return self.write_resp_queue.popleft()
|
return self.write_resp_queue.get_nowait()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
async def write(self, address, data, prot=AxiProt.NONSECURE):
|
||||||
@@ -141,13 +140,45 @@ class AxiLiteMasterWrite(object):
|
|||||||
async def write_qword(self, address, data, byteorder='little', prot=AxiProt.NONSECURE):
|
async def write_qword(self, address, data, byteorder='little', prot=AxiProt.NONSECURE):
|
||||||
await self.write_qwords(address, [data], byteorder, prot)
|
await self.write_qwords(address, [data], byteorder, prot)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_write_cr is not None:
|
||||||
|
self._process_write_cr.kill()
|
||||||
|
self._process_write_cr = None
|
||||||
|
if self._process_write_resp_cr is not None:
|
||||||
|
self._process_write_resp_cr.kill()
|
||||||
|
self._process_write_resp_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_write_cr is None:
|
||||||
|
self._process_write_cr = cocotb.fork(self._process_write())
|
||||||
|
if self._process_write_resp_cr is None:
|
||||||
|
self._process_write_resp_cr = cocotb.fork(self._process_write_resp())
|
||||||
|
|
||||||
|
self.aw_channel.clear()
|
||||||
|
self.w_channel.clear()
|
||||||
|
self.b_channel.clear()
|
||||||
|
|
||||||
|
while not self.write_command_queue.empty():
|
||||||
|
cmd = self.write_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.int_write_resp_command_queue.empty():
|
||||||
|
cmd = self.int_write_resp_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.write_resp_queue.empty():
|
||||||
|
self.write_resp_queue.get_nowait()
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.write_command_queue:
|
cmd = await self.write_command_queue.get()
|
||||||
self.write_command_sync.clear()
|
|
||||||
await self.write_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.write_command_queue.popleft()
|
|
||||||
|
|
||||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
||||||
|
|
||||||
@@ -160,8 +191,7 @@ class AxiLiteMasterWrite(object):
|
|||||||
cycles = (len(cmd.data) + (cmd.address % self.byte_width) + self.byte_width-1) // self.byte_width
|
cycles = (len(cmd.data) + (cmd.address % self.byte_width) + self.byte_width-1) // self.byte_width
|
||||||
|
|
||||||
resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event)
|
resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event)
|
||||||
self.int_write_resp_command_queue.append(resp_cmd)
|
await self.int_write_resp_command_queue.put(resp_cmd)
|
||||||
self.int_write_resp_command_sync.set()
|
|
||||||
|
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
@@ -193,22 +223,17 @@ class AxiLiteMasterWrite(object):
|
|||||||
w.wdata = val
|
w.wdata = val
|
||||||
w.wstrb = strb
|
w.wstrb = strb
|
||||||
|
|
||||||
await self.aw_channel.drive(aw)
|
await self.aw_channel.send(aw)
|
||||||
self.w_channel.send(w)
|
await self.w_channel.send(w)
|
||||||
|
|
||||||
async def _process_write_resp(self):
|
async def _process_write_resp(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.int_write_resp_command_queue:
|
cmd = await self.int_write_resp_command_queue.get()
|
||||||
self.int_write_resp_command_sync.clear()
|
|
||||||
await self.int_write_resp_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.int_write_resp_command_queue.popleft()
|
|
||||||
|
|
||||||
resp = AxiResp.OKAY
|
resp = AxiResp.OKAY
|
||||||
|
|
||||||
for k in range(cmd.cycles):
|
for k in range(cmd.cycles):
|
||||||
await self.b_channel.wait()
|
b = await self.b_channel.recv()
|
||||||
b = self.b_channel.recv()
|
|
||||||
|
|
||||||
cycle_resp = AxiResp(b.bresp)
|
cycle_resp = AxiResp(b.bresp)
|
||||||
|
|
||||||
@@ -223,36 +248,34 @@ class AxiLiteMasterWrite(object):
|
|||||||
if cmd.event is not None:
|
if cmd.event is not None:
|
||||||
cmd.event.set(write_resp)
|
cmd.event.set(write_resp)
|
||||||
else:
|
else:
|
||||||
self.write_resp_queue.append(write_resp)
|
self.write_resp_queue.put_nowait(write_resp)
|
||||||
self.write_resp_sync.set()
|
|
||||||
|
|
||||||
self.in_flight_operations -= 1
|
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):
|
class AxiLiteMasterRead(Reset):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
def __init__(self, bus, clock, reset=None, reset_active_level=True):
|
||||||
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite master (read)")
|
self.log.info("AXI lite master (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
|
|
||||||
self.reset = reset
|
self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level)
|
||||||
|
self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level)
|
||||||
|
|
||||||
self.ar_channel = AxiLiteARSource(entity, name, clock, reset)
|
self.read_command_queue = Queue()
|
||||||
self.r_channel = AxiLiteRSink(entity, name, clock, reset)
|
self.read_data_queue = Queue()
|
||||||
|
|
||||||
self.read_command_queue = deque()
|
self.int_read_resp_command_queue = Queue()
|
||||||
self.read_command_sync = Event()
|
|
||||||
self.read_data_queue = deque()
|
|
||||||
self.read_data_sync = Event()
|
|
||||||
self.read_data_set = set()
|
|
||||||
|
|
||||||
self.int_read_resp_command_queue = deque()
|
|
||||||
self.int_read_resp_command_sync = Event()
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
self.in_flight_operations = 0
|
||||||
|
self._idle = Event()
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
self.width = len(self.r_channel.bus.rdata)
|
self.width = len(self.r_channel.bus.rdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -265,32 +288,33 @@ class AxiLiteMasterRead(object):
|
|||||||
|
|
||||||
assert self.byte_width * self.byte_size == self.width
|
assert self.byte_width * self.byte_size == self.width
|
||||||
|
|
||||||
cocotb.fork(self._process_read())
|
self._process_read_cr = None
|
||||||
cocotb.fork(self._process_read_resp())
|
self._process_read_resp_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
||||||
if event is not None and not isinstance(event, Event):
|
if event is not None and not isinstance(event, Event):
|
||||||
raise ValueError("Expected event object")
|
raise ValueError("Expected event object")
|
||||||
|
|
||||||
self.in_flight_operations += 1
|
self.in_flight_operations += 1
|
||||||
|
self._idle.clear()
|
||||||
|
|
||||||
self.read_command_queue.append(AxiLiteReadCmd(address, length, prot, event))
|
self.read_command_queue.put_nowait(AxiLiteReadCmd(address, length, prot, event))
|
||||||
self.read_command_sync.set()
|
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return not self.in_flight_operations
|
return not self.in_flight_operations
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
while not self.idle():
|
||||||
self.read_data_sync.clear()
|
await self._idle.wait()
|
||||||
await self.read_data_sync.wait()
|
|
||||||
|
|
||||||
def read_data_ready(self):
|
def read_data_ready(self):
|
||||||
return bool(self.read_data_queue)
|
return not self.read_data_queue.empty()
|
||||||
|
|
||||||
def get_read_data(self):
|
def get_read_data(self):
|
||||||
if self.read_data_queue:
|
if not self.read_data_queue.empty():
|
||||||
return self.read_data_queue.popleft()
|
return self.read_data_queue.get_nowait()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
async def read(self, address, length, prot=AxiProt.NONSECURE):
|
||||||
@@ -324,21 +348,51 @@ class AxiLiteMasterRead(object):
|
|||||||
async def read_qword(self, address, byteorder='little', prot=AxiProt.NONSECURE):
|
async def read_qword(self, address, byteorder='little', prot=AxiProt.NONSECURE):
|
||||||
return (await self.read_qwords(address, 1, byteorder, prot))[0]
|
return (await self.read_qwords(address, 1, byteorder, prot))[0]
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_read_cr is not None:
|
||||||
|
self._process_read_cr.kill()
|
||||||
|
self._process_read_cr = None
|
||||||
|
if self._process_read_resp_cr is not None:
|
||||||
|
self._process_read_resp_cr.kill()
|
||||||
|
self._process_read_resp_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_read_cr is None:
|
||||||
|
self._process_read_cr = cocotb.fork(self._process_read())
|
||||||
|
if self._process_read_resp_cr is None:
|
||||||
|
self._process_read_resp_cr = cocotb.fork(self._process_read_resp())
|
||||||
|
|
||||||
|
self.ar_channel.clear()
|
||||||
|
self.r_channel.clear()
|
||||||
|
|
||||||
|
while not self.read_command_queue.empty():
|
||||||
|
cmd = self.read_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.int_read_resp_command_queue.empty():
|
||||||
|
cmd = self.int_read_resp_command_queue.get_nowait()
|
||||||
|
if cmd.event:
|
||||||
|
cmd.event.set(None)
|
||||||
|
|
||||||
|
while not self.read_data_queue.empty():
|
||||||
|
self.read_data_queue.get_nowait()
|
||||||
|
|
||||||
|
self.in_flight_operations = 0
|
||||||
|
self._idle.set()
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.read_command_queue:
|
cmd = await self.read_command_queue.get()
|
||||||
self.read_command_sync.clear()
|
|
||||||
await self.read_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.read_command_queue.popleft()
|
|
||||||
|
|
||||||
word_addr = (cmd.address // self.byte_width) * self.byte_width
|
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_width-1 + (cmd.address % self.byte_width)) // self.byte_width
|
||||||
|
|
||||||
resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event)
|
resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event)
|
||||||
self.int_read_resp_command_queue.append(resp_cmd)
|
await self.int_read_resp_command_queue.put(resp_cmd)
|
||||||
self.int_read_resp_command_sync.set()
|
|
||||||
|
|
||||||
self.log.info("Read start addr: 0x%08x prot: %s length: %d",
|
self.log.info("Read start addr: 0x%08x prot: %s length: %d",
|
||||||
cmd.address, cmd.prot, cmd.length)
|
cmd.address, cmd.prot, cmd.length)
|
||||||
@@ -348,15 +402,11 @@ class AxiLiteMasterRead(object):
|
|||||||
ar.araddr = word_addr + k*self.byte_width
|
ar.araddr = word_addr + k*self.byte_width
|
||||||
ar.arprot = cmd.prot
|
ar.arprot = cmd.prot
|
||||||
|
|
||||||
await self.ar_channel.drive(ar)
|
await self.ar_channel.send(ar)
|
||||||
|
|
||||||
async def _process_read_resp(self):
|
async def _process_read_resp(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.int_read_resp_command_queue:
|
cmd = await self.int_read_resp_command_queue.get()
|
||||||
self.int_read_resp_command_sync.clear()
|
|
||||||
await self.int_read_resp_command_sync.wait()
|
|
||||||
|
|
||||||
cmd = self.int_read_resp_command_queue.popleft()
|
|
||||||
|
|
||||||
start_offset = cmd.address % self.byte_width
|
start_offset = cmd.address % self.byte_width
|
||||||
end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1
|
end_offset = ((cmd.address + cmd.length - 1) % self.byte_width) + 1
|
||||||
@@ -366,8 +416,7 @@ class AxiLiteMasterRead(object):
|
|||||||
resp = AxiResp.OKAY
|
resp = AxiResp.OKAY
|
||||||
|
|
||||||
for k in range(cmd.cycles):
|
for k in range(cmd.cycles):
|
||||||
await self.r_channel.wait()
|
r = await self.r_channel.recv()
|
||||||
r = self.r_channel.recv()
|
|
||||||
|
|
||||||
cycle_data = int(r.rdata)
|
cycle_data = int(r.rdata)
|
||||||
cycle_resp = AxiResp(r.rresp)
|
cycle_resp = AxiResp(r.rresp)
|
||||||
@@ -394,19 +443,21 @@ class AxiLiteMasterRead(object):
|
|||||||
if cmd.event is not None:
|
if cmd.event is not None:
|
||||||
cmd.event.set(read_resp)
|
cmd.event.set(read_resp)
|
||||||
else:
|
else:
|
||||||
self.read_data_queue.append(read_resp)
|
self.read_data_queue.put_nowait(read_resp)
|
||||||
self.read_data_sync.set()
|
|
||||||
|
|
||||||
self.in_flight_operations -= 1
|
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.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
self.write_if = AxiLiteMasterWrite(entity, name, clock, reset)
|
self.write_if = AxiLiteMasterWrite(bus.write, clock, reset, reset_active_level)
|
||||||
self.read_if = AxiLiteMasterRead(entity, name, clock, reset)
|
self.read_if = AxiLiteMasterRead(bus.read, clock, reset, reset_active_level)
|
||||||
|
|
||||||
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None):
|
||||||
self.read_if.init_read(address, length, prot, event)
|
self.read_if.init_read(address, length, prot, event)
|
||||||
|
|||||||
@@ -22,18 +22,20 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.log import SimLog
|
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from .constants import AxiProt, AxiResp
|
from .constants import AxiProt, AxiResp
|
||||||
from .axil_channels import AxiLiteAWSink, AxiLiteWSink, AxiLiteBSource, AxiLiteARSink, AxiLiteRSource
|
from .axil_channels import AxiLiteAWSink, AxiLiteWSink, AxiLiteBSource, AxiLiteARSink, AxiLiteRSource
|
||||||
from .memory import Memory
|
from .memory import Memory
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class AxiLiteRamWrite(Memory):
|
class AxiLiteRamWrite(Memory, Reset):
|
||||||
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.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite RAM model (write)")
|
self.log.info("AXI lite RAM model (write)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
@@ -42,13 +44,9 @@ class AxiLiteRamWrite(Memory):
|
|||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.reset = reset
|
self.aw_channel = AxiLiteAWSink(bus.aw, clock, reset, reset_active_level)
|
||||||
|
self.w_channel = AxiLiteWSink(bus.w, clock, reset, reset_active_level)
|
||||||
self.aw_channel = AxiLiteAWSink(entity, name, clock, reset)
|
self.b_channel = AxiLiteBSource(bus.b, clock, reset, reset_active_level)
|
||||||
self.w_channel = AxiLiteWSink(entity, name, clock, reset)
|
|
||||||
self.b_channel = AxiLiteBSource(entity, name, clock, reset)
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
|
||||||
|
|
||||||
self.width = len(self.w_channel.bus.wdata)
|
self.width = len(self.w_channel.bus.wdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -64,18 +62,33 @@ class AxiLiteRamWrite(Memory):
|
|||||||
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
assert self.byte_width == len(self.w_channel.bus.wstrb)
|
||||||
assert self.byte_width * self.byte_size == self.width
|
assert self.byte_width * self.byte_size == self.width
|
||||||
|
|
||||||
cocotb.fork(self._process_write())
|
self._process_write_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_write_cr is not None:
|
||||||
|
self._process_write_cr.kill()
|
||||||
|
self._process_write_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_write_cr is None:
|
||||||
|
self._process_write_cr = cocotb.fork(self._process_write())
|
||||||
|
|
||||||
|
self.aw_channel.clear()
|
||||||
|
self.w_channel.clear()
|
||||||
|
self.b_channel.clear()
|
||||||
|
|
||||||
async def _process_write(self):
|
async def _process_write(self):
|
||||||
while True:
|
while True:
|
||||||
await self.aw_channel.wait()
|
aw = await self.aw_channel.recv()
|
||||||
aw = self.aw_channel.recv()
|
|
||||||
|
|
||||||
addr = (int(aw.awaddr) // self.byte_width) * self.byte_width
|
addr = (int(aw.awaddr) // self.byte_width) * self.byte_width
|
||||||
prot = AxiProt(aw.awprot)
|
prot = AxiProt(aw.awprot)
|
||||||
|
|
||||||
await self.w_channel.wait()
|
w = await self.w_channel.recv()
|
||||||
w = self.w_channel.recv()
|
|
||||||
|
|
||||||
data = int(w.wdata)
|
data = int(w.wdata)
|
||||||
strb = int(w.wstrb)
|
strb = int(w.wstrb)
|
||||||
@@ -98,12 +111,12 @@ class AxiLiteRamWrite(Memory):
|
|||||||
b = self.b_channel._transaction_obj()
|
b = self.b_channel._transaction_obj()
|
||||||
b.bresp = AxiResp.OKAY
|
b.bresp = AxiResp.OKAY
|
||||||
|
|
||||||
self.b_channel.send(b)
|
await self.b_channel.send(b)
|
||||||
|
|
||||||
|
|
||||||
class AxiLiteRamRead(Memory):
|
class AxiLiteRamRead(Memory, Reset):
|
||||||
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.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}")
|
||||||
|
|
||||||
self.log.info("AXI lite RAM model (read)")
|
self.log.info("AXI lite RAM model (read)")
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
self.log.info("cocotbext-axi version %s", __version__)
|
||||||
@@ -112,12 +125,8 @@ class AxiLiteRamRead(Memory):
|
|||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.reset = reset
|
self.ar_channel = AxiLiteARSink(bus.ar, clock, reset, reset_active_level)
|
||||||
|
self.r_channel = AxiLiteRSource(bus.r, clock, reset, reset_active_level)
|
||||||
self.ar_channel = AxiLiteARSink(entity, name, clock, reset)
|
|
||||||
self.r_channel = AxiLiteRSource(entity, name, clock, reset)
|
|
||||||
|
|
||||||
self.in_flight_operations = 0
|
|
||||||
|
|
||||||
self.width = len(self.r_channel.bus.rdata)
|
self.width = len(self.r_channel.bus.rdata)
|
||||||
self.byte_size = 8
|
self.byte_size = 8
|
||||||
@@ -131,12 +140,27 @@ class AxiLiteRamRead(Memory):
|
|||||||
|
|
||||||
assert self.byte_width * self.byte_size == self.width
|
assert self.byte_width * self.byte_size == self.width
|
||||||
|
|
||||||
cocotb.fork(self._process_read())
|
self._process_read_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
if state:
|
||||||
|
self.log.info("Reset asserted")
|
||||||
|
if self._process_read_cr is not None:
|
||||||
|
self._process_read_cr.kill()
|
||||||
|
self._process_read_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._process_read_cr is None:
|
||||||
|
self._process_read_cr = cocotb.fork(self._process_read())
|
||||||
|
|
||||||
|
self.ar_channel.clear()
|
||||||
|
self.r_channel.clear()
|
||||||
|
|
||||||
async def _process_read(self):
|
async def _process_read(self):
|
||||||
while True:
|
while True:
|
||||||
await self.ar_channel.wait()
|
ar = await self.ar_channel.recv()
|
||||||
ar = self.ar_channel.recv()
|
|
||||||
|
|
||||||
addr = (int(ar.araddr) // self.byte_width) * self.byte_width
|
addr = (int(ar.araddr) // self.byte_width) * self.byte_width
|
||||||
prot = AxiProt(ar.arprot)
|
prot = AxiProt(ar.arprot)
|
||||||
@@ -151,18 +175,18 @@ class AxiLiteRamRead(Memory):
|
|||||||
r.rdata = int.from_bytes(data, 'little')
|
r.rdata = int.from_bytes(data, 'little')
|
||||||
r.rresp = AxiResp.OKAY
|
r.rresp = AxiResp.OKAY
|
||||||
|
|
||||||
self.r_channel.send(r)
|
await self.r_channel.send(r)
|
||||||
|
|
||||||
self.log.info("Read data araddr: 0x%08x arprot: %s data: %s",
|
self.log.info("Read data araddr: 0x%08x arprot: %s data: %s",
|
||||||
addr, prot, ' '.join((f'{c:02x}' for c in data)))
|
addr, prot, ' '.join((f'{c:02x}' for c in data)))
|
||||||
|
|
||||||
|
|
||||||
class AxiLiteRam(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.write_if = None
|
||||||
self.read_if = None
|
self.read_if = None
|
||||||
|
|
||||||
super().__init__(size, mem, *args, **kwargs)
|
super().__init__(size, mem, *args, **kwargs)
|
||||||
|
|
||||||
self.write_if = AxiLiteRamWrite(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(entity, name, clock, reset, mem=self.mem)
|
self.read_if = AxiLiteRamRead(bus.read, clock, reset, reset_active_level, mem=self.mem)
|
||||||
|
|||||||
@@ -22,23 +22,28 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import deque
|
import logging
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, ReadOnly, Timer, First, Event
|
from cocotb.queue import Queue
|
||||||
from cocotb.bus import Bus
|
from cocotb.triggers import RisingEdge, Timer, First, Event
|
||||||
from cocotb.log import SimLog
|
from cocotb.utils import get_sim_time
|
||||||
|
from cocotb_bus.bus import Bus
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
from .reset import Reset
|
||||||
|
|
||||||
|
|
||||||
class AxiStreamFrame(object):
|
class AxiStreamFrame:
|
||||||
def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None):
|
def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None, tx_complete=None):
|
||||||
self.tdata = bytearray()
|
self.tdata = bytearray()
|
||||||
self.tkeep = None
|
self.tkeep = None
|
||||||
self.tid = None
|
self.tid = None
|
||||||
self.tdest = None
|
self.tdest = None
|
||||||
self.tuser = 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) is AxiStreamFrame:
|
||||||
if type(tdata.tdata) is bytearray:
|
if type(tdata.tdata) is bytearray:
|
||||||
@@ -62,6 +67,9 @@ class AxiStreamFrame(object):
|
|||||||
self.tuser = tdata.tuser
|
self.tuser = tdata.tuser
|
||||||
else:
|
else:
|
||||||
self.tuser = list(tdata.tuser)
|
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):
|
elif type(tdata) in (bytes, bytearray):
|
||||||
self.tdata = bytearray(tdata)
|
self.tdata = bytearray(tdata)
|
||||||
self.tkeep = tkeep
|
self.tkeep = tkeep
|
||||||
@@ -75,6 +83,9 @@ class AxiStreamFrame(object):
|
|||||||
self.tdest = tdest
|
self.tdest = tdest
|
||||||
self.tuser = tuser
|
self.tuser = tuser
|
||||||
|
|
||||||
|
if tx_complete is not None:
|
||||||
|
self.tx_complete = tx_complete
|
||||||
|
|
||||||
def normalize(self):
|
def normalize(self):
|
||||||
# normalize all sideband signals to the same size as tdata
|
# normalize all sideband signals to the same size as tdata
|
||||||
n = len(self.tdata)
|
n = len(self.tdata)
|
||||||
@@ -144,6 +155,12 @@ class AxiStreamFrame(object):
|
|||||||
elif all(self.tuser[0] == i for i in self.tuser):
|
elif all(self.tuser[0] == i for i in self.tuser):
|
||||||
self.tuser = self.tuser[0]
|
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):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, AxiStreamFrame):
|
if not isinstance(other, AxiStreamFrame):
|
||||||
return False
|
return False
|
||||||
@@ -195,11 +212,13 @@ class AxiStreamFrame(object):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return (
|
return (
|
||||||
f"{type(self).__name__}(tdata={repr(self.tdata)}, "
|
f"{type(self).__name__}(tdata={self.tdata!r}, "
|
||||||
f"tkeep={repr(self.tkeep)}, "
|
f"tkeep={self.tkeep!r}, "
|
||||||
f"tid={repr(self.tid)}, "
|
f"tid={self.tid!r}, "
|
||||||
f"tdest={repr(self.tdest)}, "
|
f"tdest={self.tdest!r}, "
|
||||||
f"tuser={repr(self.tuser)})"
|
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):
|
def __len__(self):
|
||||||
@@ -208,20 +227,48 @@ class AxiStreamFrame(object):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.tdata.__iter__()
|
return self.tdata.__iter__()
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return bytes(self.tdata)
|
||||||
|
|
||||||
class AxiStreamSource(object):
|
|
||||||
|
class AxiStreamBus(Bus):
|
||||||
|
|
||||||
_signals = ["tdata"]
|
_signals = ["tdata"]
|
||||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
||||||
|
|
||||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
def __init__(self, entity=None, prefix=None, **kwargs):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs)
|
||||||
self.entity = entity
|
|
||||||
|
@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.clock = clock
|
||||||
self.reset = reset
|
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("cocotbext-axi version %s", __version__)
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
self.log.info("Copyright (c) 2020 Alex Forencich")
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
||||||
@@ -229,41 +276,47 @@ class AxiStreamSource(object):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.active = False
|
self.active = False
|
||||||
self.queue = deque()
|
self.queue = Queue()
|
||||||
|
self.idle_event = Event()
|
||||||
self.pause = False
|
self.idle_event.set()
|
||||||
self._pause_generator = None
|
self.active_event = Event()
|
||||||
self._pause_cr = None
|
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
self.queue_occupancy_bytes = 0
|
||||||
self.queue_occupancy_frames = 0
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
self.width = len(self.bus.tdata)
|
self.width = len(self.bus.tdata)
|
||||||
self.byte_width = 1
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
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"):
|
if hasattr(self.bus, "tkeep"):
|
||||||
self.byte_width = len(self.bus.tkeep)
|
self.byte_lanes = len(self.bus.tkeep)
|
||||||
self.bus.tkeep.setimmediatevalue(0)
|
if byte_size is not None or byte_lanes is not None:
|
||||||
if hasattr(self.bus, "tid"):
|
raise ValueError("Cannot specify byte_size or byte_lanes if tkeep is connected")
|
||||||
self.bus.tid.setimmediatevalue(0)
|
else:
|
||||||
if hasattr(self.bus, "tdest"):
|
if byte_lanes is not None:
|
||||||
self.bus.tdest.setimmediatevalue(0)
|
self.byte_lanes = byte_lanes
|
||||||
if hasattr(self.bus, "tuser"):
|
if byte_size is not None:
|
||||||
self.bus.tuser.setimmediatevalue(0)
|
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_width
|
self.byte_size = self.width // self.byte_lanes
|
||||||
self.byte_mask = 2**self.byte_size-1
|
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(" 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(" tvalid: %s", "present" if hasattr(self.bus, "tvalid") else "not present")
|
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(" 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")
|
self.log.info(" tlast: %s", "present" if hasattr(self.bus, "tlast") else "not present")
|
||||||
@@ -284,29 +337,52 @@ class AxiStreamSource(object):
|
|||||||
else:
|
else:
|
||||||
self.log.info(" tuser: not present")
|
self.log.info(" tuser: not present")
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
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})")
|
||||||
|
|
||||||
def send(self, frame):
|
self._run_cr = None
|
||||||
frame = AxiStreamFrame(frame)
|
|
||||||
self.queue_occupancy_bytes += len(frame)
|
|
||||||
self.queue_occupancy_frames += 1
|
|
||||||
self.queue.append(frame)
|
|
||||||
|
|
||||||
def write(self, data):
|
self._init_reset(reset, reset_active_level)
|
||||||
self.send(data)
|
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return len(self.queue)
|
return self.queue.qsize()
|
||||||
|
|
||||||
def empty(self):
|
def empty(self):
|
||||||
return not self.queue
|
return self.queue.empty()
|
||||||
|
|
||||||
def idle(self):
|
def clear(self):
|
||||||
return self.empty() and not self.active
|
while not self.queue.empty():
|
||||||
|
self.queue.get_nowait()
|
||||||
|
self.idle_event.set()
|
||||||
|
self.active_event.clear()
|
||||||
|
self.queue_occupancy_bytes = 0
|
||||||
|
self.queue_occupancy_frames = 0
|
||||||
|
|
||||||
async def wait(self):
|
def _handle_reset(self, state):
|
||||||
while not self.idle():
|
if state:
|
||||||
await RisingEdge(self.clock)
|
self.log.info("Reset asserted")
|
||||||
|
if self._run_cr is not None:
|
||||||
|
self._run_cr.kill()
|
||||||
|
self._run_cr = None
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._run_cr is None:
|
||||||
|
self._run_cr = cocotb.fork(self._run())
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class AxiStreamPause:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.pause = False
|
||||||
|
self._pause_generator = None
|
||||||
|
self._pause_cr = None
|
||||||
|
|
||||||
def set_pause_generator(self, generator=None):
|
def set_pause_generator(self, generator=None):
|
||||||
if self._pause_cr is not None:
|
if self._pause_cr is not None:
|
||||||
@@ -321,21 +397,50 @@ class AxiStreamSource(object):
|
|||||||
def clear_pause_generator(self):
|
def clear_pause_generator(self):
|
||||||
self.set_pause_generator(None)
|
self.set_pause_generator(None)
|
||||||
|
|
||||||
async def _run(self):
|
async def _run_pause(self):
|
||||||
frame = None
|
for val in self._pause_generator:
|
||||||
self.active = False
|
self.pause = val
|
||||||
|
|
||||||
while True:
|
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
# read handshake signals
|
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
frame = None
|
|
||||||
self.active = False
|
|
||||||
|
class AxiStreamSource(AxiStreamBase, AxiStreamPause):
|
||||||
|
|
||||||
|
_type = "source"
|
||||||
|
|
||||||
|
_init_x = True
|
||||||
|
|
||||||
|
_valid_init = 0
|
||||||
|
_ready_init = None
|
||||||
|
|
||||||
|
async def send(self, frame):
|
||||||
|
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):
|
||||||
|
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 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)
|
||||||
|
|
||||||
self.bus.tdata <= 0
|
self.bus.tdata <= 0
|
||||||
if hasattr(self.bus, "tvalid"):
|
if hasattr(self.bus, "tvalid"):
|
||||||
self.bus.tvalid <= 0
|
self.bus.tvalid <= 0
|
||||||
@@ -349,15 +454,25 @@ class AxiStreamSource(object):
|
|||||||
self.bus.tdest <= 0
|
self.bus.tdest <= 0
|
||||||
if hasattr(self.bus, "tuser"):
|
if hasattr(self.bus, "tuser"):
|
||||||
self.bus.tuser <= 0
|
self.bus.tuser <= 0
|
||||||
continue
|
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
frame = None
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
while True:
|
||||||
await RisingEdge(self.clock)
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
|
# read handshake signals
|
||||||
|
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||||
|
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||||
|
|
||||||
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
if (tready_sample and tvalid_sample) or not tvalid_sample:
|
||||||
if frame is None and self.queue:
|
if frame is None and not self.queue.empty():
|
||||||
frame = self.queue.popleft()
|
frame = self.queue.get_nowait()
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
self.queue_occupancy_frames -= 1
|
self.queue_occupancy_frames -= 1
|
||||||
|
frame.sim_time_start = get_sim_time()
|
||||||
|
frame.sim_time_end = None
|
||||||
self.log.info("TX frame: %s", frame)
|
self.log.info("TX frame: %s", frame)
|
||||||
frame.normalize()
|
frame.normalize()
|
||||||
self.active = True
|
self.active = True
|
||||||
@@ -370,7 +485,7 @@ class AxiStreamSource(object):
|
|||||||
tdest_val = 0
|
tdest_val = 0
|
||||||
tuser_val = 0
|
tuser_val = 0
|
||||||
|
|
||||||
for offset in range(self.byte_width):
|
for offset in range(self.byte_lanes):
|
||||||
tdata_val |= (frame.tdata.pop(0) & self.byte_mask) << (offset * self.byte_size)
|
tdata_val |= (frame.tdata.pop(0) & self.byte_mask) << (offset * self.byte_size)
|
||||||
tkeep_val |= (frame.tkeep.pop(0) & 1) << offset
|
tkeep_val |= (frame.tkeep.pop(0) & 1) << offset
|
||||||
tid_val = frame.tid.pop(0)
|
tid_val = frame.tid.pop(0)
|
||||||
@@ -379,6 +494,8 @@ class AxiStreamSource(object):
|
|||||||
|
|
||||||
if len(frame.tdata) == 0:
|
if len(frame.tdata) == 0:
|
||||||
tlast_val = 1
|
tlast_val = 1
|
||||||
|
frame.sim_time_end = get_sim_time()
|
||||||
|
frame.handle_tx_complete()
|
||||||
frame = None
|
frame = None
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -401,87 +518,41 @@ class AxiStreamSource(object):
|
|||||||
if hasattr(self.bus, "tlast"):
|
if hasattr(self.bus, "tlast"):
|
||||||
self.bus.tlast <= 0
|
self.bus.tlast <= 0
|
||||||
self.active = bool(frame)
|
self.active = bool(frame)
|
||||||
|
if not frame and self.queue.empty():
|
||||||
async def _run_pause(self):
|
self.idle_event.set()
|
||||||
for val in self._pause_generator:
|
|
||||||
self.pause = val
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
|
||||||
|
|
||||||
class AxiStreamSink(object):
|
class AxiStreamMonitor(AxiStreamBase):
|
||||||
|
|
||||||
_signals = ["tdata"]
|
_type = "monitor"
|
||||||
_optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"]
|
|
||||||
|
|
||||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
_init_x = False
|
||||||
self.log = SimLog("cocotb.%s.%s" % (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 sink")
|
_valid_init = None
|
||||||
self.log.info("cocotbext-axi version %s", __version__)
|
_ready_init = None
|
||||||
self.log.info("Copyright (c) 2020 Alex Forencich")
|
|
||||||
self.log.info("https://github.com/alexforencich/cocotbext-axi")
|
|
||||||
|
|
||||||
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.read_queue = []
|
||||||
|
|
||||||
self.pause = False
|
async def recv(self, compact=True):
|
||||||
self._pause_generator = None
|
frame = await self.queue.get()
|
||||||
self._pause_cr = None
|
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
|
||||||
|
|
||||||
self.queue_occupancy_bytes = 0
|
def recv_nowait(self, compact=True):
|
||||||
self.queue_occupancy_frames = 0
|
if not self.queue.empty():
|
||||||
self.queue_occupancy_limit_bytes = None
|
frame = self.queue.get_nowait()
|
||||||
self.queue_occupancy_limit_frames = None
|
if self.queue.empty():
|
||||||
|
self.active_event.clear()
|
||||||
self.width = len(self.bus.tdata)
|
|
||||||
self.byte_width = 1
|
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
if hasattr(self.bus, "tready"):
|
|
||||||
self.bus.tready.setimmediatevalue(0)
|
|
||||||
if hasattr(self.bus, "tkeep"):
|
|
||||||
self.byte_width = len(self.bus.tkeep)
|
|
||||||
|
|
||||||
self.byte_size = self.width // self.byte_width
|
|
||||||
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_width)
|
|
||||||
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")
|
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
|
||||||
|
|
||||||
def recv(self, compact=True):
|
|
||||||
if self.queue:
|
|
||||||
frame = self.queue.popleft()
|
|
||||||
self.queue_occupancy_bytes -= len(frame)
|
self.queue_occupancy_bytes -= len(frame)
|
||||||
self.queue_occupancy_frames -= 1
|
self.queue_occupancy_frames -= 1
|
||||||
if compact:
|
if compact:
|
||||||
@@ -489,11 +560,15 @@ class AxiStreamSink(object):
|
|||||||
return frame
|
return frame
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def read(self, count=-1):
|
async def read(self, count=-1):
|
||||||
while True:
|
while not self.read_queue:
|
||||||
frame = self.recv(compact=True)
|
frame = await self.recv(compact=True)
|
||||||
if frame is None:
|
self.read_queue.extend(frame.tdata)
|
||||||
break
|
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)
|
self.read_queue.extend(frame.tdata)
|
||||||
if count < 0:
|
if count < 0:
|
||||||
count = len(self.read_queue)
|
count = len(self.read_queue)
|
||||||
@@ -501,66 +576,37 @@ class AxiStreamSink(object):
|
|||||||
del self.read_queue[:count]
|
del self.read_queue[:count]
|
||||||
return data
|
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):
|
def idle(self):
|
||||||
return not self.active
|
return not self.active
|
||||||
|
|
||||||
async def wait(self, timeout=0, timeout_unit='ns'):
|
async def wait(self, timeout=0, timeout_unit='ns'):
|
||||||
if not self.empty():
|
if not self.empty():
|
||||||
return
|
return
|
||||||
self.sync.clear()
|
|
||||||
if timeout:
|
if timeout:
|
||||||
await First(self.sync.wait(), Timer(timeout, timeout_unit))
|
await First(self.active_event.wait(), Timer(timeout, timeout_unit))
|
||||||
else:
|
else:
|
||||||
await self.sync.wait()
|
await self.active_event.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)
|
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
|
||||||
self.active = False
|
|
||||||
if hasattr(self.bus, "tready"):
|
|
||||||
self.bus.tready <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
if tready_sample and tvalid_sample:
|
if tready_sample and tvalid_sample:
|
||||||
for offset in range(self.byte_width):
|
if frame is None:
|
||||||
|
if self.byte_size == 8:
|
||||||
|
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||||
|
else:
|
||||||
|
frame = AxiStreamFrame([], [], [], [], [])
|
||||||
|
frame.sim_time_start = get_sim_time()
|
||||||
|
|
||||||
|
for offset in range(self.byte_lanes):
|
||||||
|
|
||||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||||
if hasattr(self.bus, "tkeep"):
|
if hasattr(self.bus, "tkeep"):
|
||||||
@@ -573,152 +619,69 @@ class AxiStreamSink(object):
|
|||||||
frame.tuser.append(self.bus.tuser.value.integer)
|
frame.tuser.append(self.bus.tuser.value.integer)
|
||||||
|
|
||||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
||||||
if self.byte_size == 8:
|
frame.sim_time_end = get_sim_time()
|
||||||
frame.tdata = bytearray(frame.tdata)
|
|
||||||
|
|
||||||
self.log.info("RX frame: %s", frame)
|
self.log.info("RX frame: %s", frame)
|
||||||
|
|
||||||
self.queue_occupancy_bytes += len(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.queue_occupancy_frames += 1
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
self.queue.append(frame)
|
self.queue.put_nowait(frame)
|
||||||
self.sync.set()
|
self.active_event.set()
|
||||||
|
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
frame = None
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
class AxiStreamSink(AxiStreamMonitor, AxiStreamPause):
|
||||||
|
|
||||||
|
_type = "sink"
|
||||||
|
|
||||||
|
_init_x = False
|
||||||
|
|
||||||
|
_valid_init = None
|
||||||
|
_ready_init = 0
|
||||||
|
|
||||||
|
def __init__(self, bus, clock, reset=None, reset_active_level=True,
|
||||||
|
byte_size=None, byte_lanes=None, *args, **kwargs):
|
||||||
|
|
||||||
|
super().__init__(bus, clock, reset, reset_active_level, byte_size, byte_lanes, *args, **kwargs)
|
||||||
|
|
||||||
|
self.queue_occupancy_limit_bytes = -1
|
||||||
|
self.queue_occupancy_limit_frames = -1
|
||||||
|
|
||||||
|
def full(self):
|
||||||
|
if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
|
||||||
|
return True
|
||||||
|
elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_reset(self, state):
|
||||||
|
super()._handle_reset(state)
|
||||||
|
|
||||||
if hasattr(self.bus, "tready"):
|
if hasattr(self.bus, "tready"):
|
||||||
self.bus.tready <= (not self.full() and not self.pause)
|
self.bus.tready <= 0
|
||||||
|
|
||||||
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, *args, **kwargs):
|
|
||||||
self.log = SimLog("cocotb.%s.%s" % (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_width = 1
|
|
||||||
|
|
||||||
self.reset = reset
|
|
||||||
|
|
||||||
if hasattr(self.bus, "tkeep"):
|
|
||||||
self.byte_width = len(self.bus.tkeep)
|
|
||||||
|
|
||||||
self.byte_size = self.width // self.byte_width
|
|
||||||
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_width)
|
|
||||||
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")
|
|
||||||
|
|
||||||
cocotb.fork(self._run())
|
|
||||||
|
|
||||||
def recv(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
|
|
||||||
|
|
||||||
def read(self, count=-1):
|
|
||||||
while True:
|
|
||||||
frame = self.recv(compact=True)
|
|
||||||
if frame is None:
|
|
||||||
break
|
|
||||||
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()
|
|
||||||
|
|
||||||
async def _run(self):
|
async def _run(self):
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
frame = None
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
tready_sample = (not hasattr(self.bus, "tready")) or self.bus.tready.value
|
||||||
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
tvalid_sample = (not hasattr(self.bus, "tvalid")) or self.bus.tvalid.value
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
|
||||||
self.active = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
if tready_sample and tvalid_sample:
|
if tready_sample and tvalid_sample:
|
||||||
for offset in range(self.byte_width):
|
if frame is None:
|
||||||
|
if self.byte_size == 8:
|
||||||
|
frame = AxiStreamFrame(bytearray(), [], [], [], [])
|
||||||
|
else:
|
||||||
|
frame = AxiStreamFrame([], [], [], [], [])
|
||||||
|
frame.sim_time_start = get_sim_time()
|
||||||
|
|
||||||
|
for offset in range(self.byte_lanes):
|
||||||
|
|
||||||
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask)
|
||||||
if hasattr(self.bus, "tkeep"):
|
if hasattr(self.bus, "tkeep"):
|
||||||
@@ -731,14 +694,16 @@ class AxiStreamMonitor(object):
|
|||||||
frame.tuser.append(self.bus.tuser.value.integer)
|
frame.tuser.append(self.bus.tuser.value.integer)
|
||||||
|
|
||||||
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
if not hasattr(self.bus, "tlast") or self.bus.tlast.value:
|
||||||
if self.byte_size == 8:
|
frame.sim_time_end = get_sim_time()
|
||||||
frame.tdata = bytearray(frame.tdata)
|
|
||||||
|
|
||||||
self.log.info("RX frame: %s", frame)
|
self.log.info("RX frame: %s", frame)
|
||||||
|
|
||||||
self.queue.append(frame)
|
self.queue_occupancy_bytes += len(frame)
|
||||||
self.sync.set()
|
self.queue_occupancy_frames += 1
|
||||||
|
|
||||||
frame = AxiStreamFrame([], [], [], [], [])
|
self.queue.put_nowait(frame)
|
||||||
|
self.active_event.set()
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
frame = None
|
||||||
|
|
||||||
|
if hasattr(self.bus, "tready"):
|
||||||
|
self.bus.tready <= (not self.full() and not self.pause)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import mmap
|
|||||||
from .utils import hexdump, hexdump_lines, hexdump_str
|
from .utils import hexdump, hexdump_lines, hexdump_str
|
||||||
|
|
||||||
|
|
||||||
class Memory(object):
|
class Memory:
|
||||||
def __init__(self, size=1024, mem=None, *args, **kwargs):
|
def __init__(self, size=1024, mem=None, *args, **kwargs):
|
||||||
if mem is not None:
|
if mem is not None:
|
||||||
self.mem = mem
|
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()
|
||||||
@@ -22,15 +22,34 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import cocotb
|
import cocotb
|
||||||
from cocotb.triggers import RisingEdge, ReadOnly, Event, First, Timer
|
from cocotb.queue import Queue
|
||||||
from cocotb.bus import Bus
|
from cocotb.triggers import RisingEdge, Event, First, Timer
|
||||||
from cocotb.log import SimLog
|
from cocotb_bus.bus import Bus
|
||||||
|
|
||||||
from collections import deque
|
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"]
|
_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)})"
|
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"]
|
_signals = ["data", "valid", "ready"]
|
||||||
_optional_signals = []
|
_optional_signals = []
|
||||||
@@ -63,18 +82,22 @@ class StreamBase(object):
|
|||||||
_ready_init = None
|
_ready_init = None
|
||||||
|
|
||||||
_transaction_obj = StreamTransaction
|
_transaction_obj = StreamTransaction
|
||||||
|
_bus_obj = StreamBus
|
||||||
|
|
||||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
def __init__(self, bus, clock, reset=None, reset_active_level=True, *args, **kwargs):
|
||||||
self.log = SimLog("cocotb.%s.%s" % (entity._name, name))
|
self.bus = bus
|
||||||
self.entity = entity
|
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.reset = reset
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.queue = deque()
|
self.active = False
|
||||||
self.queue_sync = Event()
|
|
||||||
|
self.queue = Queue()
|
||||||
|
self.idle_event = Event()
|
||||||
|
self.idle_event.set()
|
||||||
|
self.active_event = Event()
|
||||||
|
|
||||||
self.ready = None
|
self.ready = None
|
||||||
self.valid = None
|
self.valid = None
|
||||||
@@ -98,17 +121,40 @@ class StreamBase(object):
|
|||||||
v.binstr = 'x'*len(v)
|
v.binstr = 'x'*len(v)
|
||||||
getattr(self.bus, sig).setimmediatevalue(v)
|
getattr(self.bus, sig).setimmediatevalue(v)
|
||||||
|
|
||||||
|
self._run_cr = None
|
||||||
|
|
||||||
|
self._init_reset(reset, reset_active_level)
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
return len(self.queue)
|
return self.queue.qsize()
|
||||||
|
|
||||||
def empty(self):
|
def empty(self):
|
||||||
return not self.queue
|
return self.queue.empty()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.queue.clear()
|
while not self.queue.empty():
|
||||||
|
self.queue.get_nowait()
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
self.log.info("Reset de-asserted")
|
||||||
|
if self._run_cr is None:
|
||||||
|
self._run_cr = cocotb.fork(self._run())
|
||||||
|
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class StreamPause(object):
|
class StreamPause:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@@ -137,222 +183,137 @@ class StreamPause(object):
|
|||||||
|
|
||||||
class StreamSource(StreamBase, StreamPause):
|
class StreamSource(StreamBase, StreamPause):
|
||||||
|
|
||||||
_signals = ["data", "valid", "ready"]
|
|
||||||
_optional_signals = []
|
|
||||||
|
|
||||||
_signal_widths = {"valid": 1, "ready": 1}
|
|
||||||
|
|
||||||
_init_x = True
|
_init_x = True
|
||||||
|
|
||||||
_valid_signal = "valid"
|
|
||||||
_valid_init = 0
|
_valid_init = 0
|
||||||
_ready_signal = "ready"
|
|
||||||
_ready_init = None
|
_ready_init = None
|
||||||
|
|
||||||
_transaction_obj = StreamTransaction
|
async def send(self, obj):
|
||||||
|
await self.queue.put(obj)
|
||||||
|
self.idle_event.clear()
|
||||||
|
|
||||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
def send_nowait(self, obj):
|
||||||
super().__init__(entity, name, clock, reset, *args, **kwargs)
|
self.queue.put_nowait(obj)
|
||||||
|
self.idle_event.clear()
|
||||||
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
|
|
||||||
|
|
||||||
def send(self, obj):
|
|
||||||
self.queue.append(obj)
|
|
||||||
self.queue_sync.set()
|
|
||||||
|
|
||||||
def idle(self):
|
def idle(self):
|
||||||
return self.empty() and not self.active
|
return self.empty() and not self.active
|
||||||
|
|
||||||
async def wait(self):
|
async def wait(self):
|
||||||
while not self.idle():
|
await self.idle_event.wait()
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
|
||||||
def clear(self):
|
def _handle_reset(self, state):
|
||||||
self.queue.clear()
|
super()._handle_reset(state)
|
||||||
self.drive_obj = None
|
|
||||||
self.drive_sync.set()
|
|
||||||
|
|
||||||
async def _run_source(self):
|
if self.valid is not None:
|
||||||
|
self.valid <= 0
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
ready_sample = self.ready is None or self.ready.value
|
ready_sample = self.ready is None or self.ready.value
|
||||||
valid_sample = self.valid is None or self.valid.value
|
valid_sample = self.valid is None or self.valid.value
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
self.clear()
|
|
||||||
if self.valid is not None:
|
|
||||||
self.valid <= 0
|
|
||||||
self.active = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
|
|
||||||
if (ready_sample and valid_sample) or (not valid_sample):
|
if (ready_sample and valid_sample) or (not valid_sample):
|
||||||
if self.drive_obj and not self.pause:
|
if not self.queue.empty() and not self.pause:
|
||||||
self.bus.drive(self.drive_obj)
|
self.bus.drive(self.queue.get_nowait())
|
||||||
self.drive_obj = None
|
|
||||||
self.drive_sync.set()
|
|
||||||
if self.valid is not None:
|
if self.valid is not None:
|
||||||
self.valid <= 1
|
self.valid <= 1
|
||||||
self.active = True
|
self.active = True
|
||||||
else:
|
else:
|
||||||
if self.valid is not None:
|
if self.valid is not None:
|
||||||
self.valid <= 0
|
self.valid <= 0
|
||||||
self.active = bool(self.drive_obj)
|
self.active = not self.queue.empty()
|
||||||
|
if self.queue.empty():
|
||||||
async def _run(self):
|
self.idle_event.set()
|
||||||
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())
|
|
||||||
|
|
||||||
def recv(self):
|
|
||||||
if self.queue:
|
|
||||||
return self.queue.popleft()
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def wait(self, timeout=0, timeout_unit=None):
|
|
||||||
if not self.empty():
|
|
||||||
return
|
|
||||||
self.queue_sync.clear()
|
|
||||||
if timeout:
|
|
||||||
await First(self.queue_sync.wait(), Timer(timeout, timeout_unit))
|
|
||||||
else:
|
|
||||||
await self.queue_sync.wait()
|
|
||||||
|
|
||||||
def callback(self, obj):
|
|
||||||
self.queue.append(obj)
|
|
||||||
self.queue_sync.set()
|
|
||||||
|
|
||||||
async def _run_sink(self):
|
|
||||||
while True:
|
|
||||||
await ReadOnly()
|
|
||||||
|
|
||||||
# read handshake signals
|
|
||||||
ready_sample = self.ready is None or self.ready.value
|
|
||||||
valid_sample = self.valid is None or self.valid.value
|
|
||||||
|
|
||||||
if self.reset is not None and self.reset.value:
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
self.clear()
|
|
||||||
if self.ready is not None:
|
|
||||||
self.ready <= 0
|
|
||||||
continue
|
|
||||||
|
|
||||||
if ready_sample and valid_sample:
|
|
||||||
obj = self._transaction_obj()
|
|
||||||
self.bus.sample(obj)
|
|
||||||
self.callback(obj)
|
|
||||||
|
|
||||||
await RisingEdge(self.clock)
|
|
||||||
if self.ready is not None:
|
|
||||||
self.ready <= (not self.pause)
|
|
||||||
|
|
||||||
|
|
||||||
class StreamMonitor(StreamBase):
|
class StreamMonitor(StreamBase):
|
||||||
|
|
||||||
_signals = ["data", "valid", "ready"]
|
|
||||||
_optional_signals = []
|
|
||||||
|
|
||||||
_signal_widths = {"valid": 1, "ready": 1}
|
|
||||||
|
|
||||||
_init_x = False
|
_init_x = False
|
||||||
|
|
||||||
_valid_signal = "valid"
|
|
||||||
_valid_init = None
|
_valid_init = None
|
||||||
_ready_signal = "ready"
|
|
||||||
_ready_init = None
|
_ready_init = None
|
||||||
|
|
||||||
_transaction_obj = StreamTransaction
|
async def recv(self):
|
||||||
|
item = await self.queue.get()
|
||||||
|
if self.queue.empty():
|
||||||
|
self.active_event.clear()
|
||||||
|
return item
|
||||||
|
|
||||||
def __init__(self, entity, name, clock, reset=None, *args, **kwargs):
|
def recv_nowait(self):
|
||||||
super().__init__(entity, name, clock, reset, *args, **kwargs)
|
if not self.queue.empty():
|
||||||
|
item = self.queue.get_nowait()
|
||||||
cocotb.fork(self._run_monitor())
|
if self.queue.empty():
|
||||||
|
self.active_event.clear()
|
||||||
def recv(self):
|
return item
|
||||||
if self.queue:
|
|
||||||
return self.queue.popleft()
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def count(self):
|
|
||||||
return len(self.queue)
|
|
||||||
|
|
||||||
def empty(self):
|
|
||||||
return not self.queue
|
|
||||||
|
|
||||||
async def wait(self, timeout=0, timeout_unit=None):
|
async def wait(self, timeout=0, timeout_unit=None):
|
||||||
if not self.empty():
|
if not self.empty():
|
||||||
return
|
return
|
||||||
self.queue_sync.clear()
|
|
||||||
if timeout:
|
if timeout:
|
||||||
await First(self.queue_sync.wait(), Timer(timeout, timeout_unit))
|
await First(self.active_event.wait(), Timer(timeout, timeout_unit))
|
||||||
else:
|
else:
|
||||||
await self.queue_sync.wait()
|
await self.active_event.wait()
|
||||||
|
|
||||||
def callback(self, obj):
|
async def _run(self):
|
||||||
self.queue.append(obj)
|
|
||||||
self.queue_sync.set()
|
|
||||||
|
|
||||||
async def _run_monitor(self):
|
|
||||||
while True:
|
while True:
|
||||||
await ReadOnly()
|
await RisingEdge(self.clock)
|
||||||
|
|
||||||
# read handshake signals
|
# read handshake signals
|
||||||
ready_sample = self.ready is None or self.ready.value
|
ready_sample = self.ready is None or self.ready.value
|
||||||
valid_sample = self.valid is None or self.valid.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 self.ready is not None:
|
||||||
|
self.ready <= 0
|
||||||
|
|
||||||
|
async def _run(self):
|
||||||
|
while True:
|
||||||
await RisingEdge(self.clock)
|
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:
|
if ready_sample and valid_sample:
|
||||||
obj = self._transaction_obj()
|
obj = self._transaction_obj()
|
||||||
self.bus.sample(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 <= (not self.full() and not self.pause)
|
||||||
|
|
||||||
|
|
||||||
def define_stream(name, signals, optional_signals=None, valid_signal=None, ready_signal=None, signal_widths=None):
|
def define_stream(name, signals, optional_signals=None, valid_signal=None, ready_signal=None, signal_widths=None):
|
||||||
@@ -393,6 +354,11 @@ def define_stream(name, signals, optional_signals=None, valid_signal=None, ready
|
|||||||
if s not in (ready_signal, valid_signal):
|
if s not in (ready_signal, valid_signal):
|
||||||
filtered_signals.append(s)
|
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 = {s: 0 for s in filtered_signals}
|
||||||
attrib['_signals'] = filtered_signals
|
attrib['_signals'] = filtered_signals
|
||||||
|
|
||||||
@@ -405,9 +371,10 @@ def define_stream(name, signals, optional_signals=None, valid_signal=None, ready
|
|||||||
attrib['_ready_signal'] = ready_signal
|
attrib['_ready_signal'] = ready_signal
|
||||||
attrib['_valid_signal'] = valid_signal
|
attrib['_valid_signal'] = valid_signal
|
||||||
attrib['_transaction_obj'] = transaction
|
attrib['_transaction_obj'] = transaction
|
||||||
|
attrib['_bus_obj'] = bus
|
||||||
|
|
||||||
source = type(name+"Source", (StreamSource,), attrib)
|
source = type(name+"Source", (StreamSource,), attrib)
|
||||||
sink = type(name+"Sink", (StreamSink,), attrib)
|
sink = type(name+"Sink", (StreamSink,), attrib)
|
||||||
monitor = type(name+"Monitor", (StreamMonitor,), 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 = ""
|
h = ""
|
||||||
c = ""
|
c = ""
|
||||||
for ch in data[0:16]:
|
for ch in data[0:row_size]:
|
||||||
h += f"{ch:02x} "
|
h += f"{ch:02x} "
|
||||||
c += chr(ch) if 32 < ch < 127 else "."
|
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)
|
stop = min(start+length, len(data)) if length else len(data)
|
||||||
for k in range(start, stop, 16):
|
for k in range(start, stop, row_size):
|
||||||
print(prefix+hexdump_line(data[k:min(k+16, stop)], k+offset))
|
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 = []
|
lines = []
|
||||||
stop = min(start+length, len(data)) if length else len(data)
|
stop = min(start+length, len(data)) if length else len(data)
|
||||||
for k in range(start, stop, 16):
|
for k in range(start, stop, row_size):
|
||||||
lines.append(prefix+hexdump_line(data[k:min(k+16, stop)], k+offset))
|
lines.append(prefix+hexdump_line(data[k:min(k+row_size, stop)], k+offset, row_size))
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def hexdump_str(data, start=0, length=None, prefix="", offset=0):
|
def hexdump_str(data, start=0, length=None, row_size=16, prefix="", offset=0):
|
||||||
return "\n".join(hexdump_lines(data, start, length, prefix, offset))
|
return "\n".join(hexdump_lines(data, start, length, row_size, prefix, offset))
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.2"
|
__version__ = "0.1.8"
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ packages = find_namespace:
|
|||||||
python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
install_requires =
|
install_requires =
|
||||||
cocotb
|
cocotb
|
||||||
|
cocotb-bus
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
test =
|
test =
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ export PARAM_BUSER_WIDTH ?= 1
|
|||||||
export PARAM_ARUSER_WIDTH ?= 1
|
export PARAM_ARUSER_WIDTH ?= 1
|
||||||
export PARAM_RUSER_WIDTH ?= 1
|
export PARAM_RUSER_WIDTH ?= 1
|
||||||
|
|
||||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
@@ -79,6 +77,8 @@ else ifeq ($(SIM), verilator)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
iverilog_dump.v:
|
||||||
echo 'module iverilog_dump();' > $@
|
echo 'module iverilog_dump();' > $@
|
||||||
echo 'initial begin' >> $@
|
echo 'initial begin' >> $@
|
||||||
@@ -88,9 +88,5 @@ iverilog_dump.v:
|
|||||||
echo 'endmodule' >> $@
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
@rm -rf sim_build_*
|
|
||||||
@rm -rf iverilog_dump.v
|
@rm -rf iverilog_dump.v
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
@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.triggers import RisingEdge, Timer
|
||||||
from cocotb.regression import TestFactory
|
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):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ class TB(object):
|
|||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.axi_master = AxiMaster(dut, "axi", dut.clk, dut.rst)
|
self.axi_master = AxiMaster(AxiBus.from_prefix(dut, "axi"), dut.clk, dut.rst)
|
||||||
self.axi_ram = AxiRam(dut, "axi", dut.clk, dut.rst, size=2**16)
|
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.write_if.log.setLevel(logging.DEBUG)
|
||||||
self.axi_ram.read_if.log.setLevel(logging.DEBUG)
|
self.axi_ram.read_if.log.setLevel(logging.DEBUG)
|
||||||
@@ -311,7 +311,6 @@ if cocotb.SIM_NAME:
|
|||||||
# cocotb-test
|
# cocotb-test
|
||||||
|
|
||||||
tests_dir = os.path.dirname(__file__)
|
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])
|
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||||
@@ -338,8 +337,8 @@ def test_axi(request, data_width):
|
|||||||
|
|
||||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
sim_build = os.path.join(tests_dir,
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
cocotb_test.simulator.run(
|
cocotb_test.simulator.run(
|
||||||
python_search=[tests_dir],
|
python_search=[tests_dir],
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ export PARAM_DATA_WIDTH ?= 32
|
|||||||
export PARAM_ADDR_WIDTH ?= 32
|
export PARAM_ADDR_WIDTH ?= 32
|
||||||
export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
export PARAM_STRB_WIDTH ?= $(shell expr $(PARAM_DATA_WIDTH) / 8 )
|
||||||
|
|
||||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
@@ -61,6 +59,8 @@ else ifeq ($(SIM), verilator)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
iverilog_dump.v:
|
||||||
echo 'module iverilog_dump();' > $@
|
echo 'module iverilog_dump();' > $@
|
||||||
echo 'initial begin' >> $@
|
echo 'initial begin' >> $@
|
||||||
@@ -70,9 +70,5 @@ iverilog_dump.v:
|
|||||||
echo 'endmodule' >> $@
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
@rm -rf sim_build_*
|
|
||||||
@rm -rf iverilog_dump.v
|
@rm -rf iverilog_dump.v
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
@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.triggers import RisingEdge, Timer
|
||||||
from cocotb.regression import TestFactory
|
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):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ class TB(object):
|
|||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.axil_master = AxiLiteMaster(dut, "axil", dut.clk, dut.rst)
|
self.axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst)
|
||||||
self.axil_ram = AxiLiteRam(dut, "axil", dut.clk, dut.rst, size=2**16)
|
self.axil_ram = AxiLiteRam(AxiLiteBus.from_prefix(dut, "axil"), dut.clk, dut.rst, size=2**16)
|
||||||
|
|
||||||
def set_idle_generator(self, generator=None):
|
def set_idle_generator(self, generator=None):
|
||||||
if generator:
|
if generator:
|
||||||
@@ -295,7 +295,6 @@ if cocotb.SIM_NAME:
|
|||||||
# cocotb-test
|
# cocotb-test
|
||||||
|
|
||||||
tests_dir = os.path.dirname(__file__)
|
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])
|
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||||
@@ -316,8 +315,8 @@ def test_axil(request, data_width):
|
|||||||
|
|
||||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
sim_build = os.path.join(tests_dir,
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
cocotb_test.simulator.run(
|
cocotb_test.simulator.run(
|
||||||
python_search=[tests_dir],
|
python_search=[tests_dir],
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ export PARAM_ID_WIDTH ?= 8
|
|||||||
export PARAM_DEST_WIDTH ?= 8
|
export PARAM_DEST_WIDTH ?= 8
|
||||||
export PARAM_USER_WIDTH ?= 1
|
export PARAM_USER_WIDTH ?= 1
|
||||||
|
|
||||||
SIM_BUILD ?= sim_build_$(MODULE)-$(PARAM_DATA_WIDTH)
|
|
||||||
|
|
||||||
ifeq ($(SIM), icarus)
|
ifeq ($(SIM), icarus)
|
||||||
PLUSARGS += -fst
|
PLUSARGS += -fst
|
||||||
|
|
||||||
@@ -67,6 +65,8 @@ else ifeq ($(SIM), verilator)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
include $(shell cocotb-config --makefiles)/Makefile.sim
|
||||||
|
|
||||||
iverilog_dump.v:
|
iverilog_dump.v:
|
||||||
echo 'module iverilog_dump();' > $@
|
echo 'module iverilog_dump();' > $@
|
||||||
echo 'initial begin' >> $@
|
echo 'initial begin' >> $@
|
||||||
@@ -76,9 +76,5 @@ iverilog_dump.v:
|
|||||||
echo 'endmodule' >> $@
|
echo 'endmodule' >> $@
|
||||||
|
|
||||||
clean::
|
clean::
|
||||||
@rm -rf sim_build_*
|
|
||||||
@rm -rf iverilog_dump.v
|
@rm -rf iverilog_dump.v
|
||||||
@rm -rf dump.fst $(TOPLEVEL).fst
|
@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.triggers import RisingEdge
|
||||||
from cocotb.regression import TestFactory
|
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):
|
def __init__(self, dut):
|
||||||
self.dut = dut
|
self.dut = dut
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@ class TB(object):
|
|||||||
|
|
||||||
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
cocotb.fork(Clock(dut.clk, 2, units="ns").start())
|
||||||
|
|
||||||
self.source = AxiStreamSource(dut, "axis", dut.clk, dut.rst)
|
self.source = AxiStreamSource(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||||
self.sink = AxiStreamSink(dut, "axis", dut.clk, dut.rst)
|
self.sink = AxiStreamSink(AxiStreamBus.from_prefix(dut, "axis"), dut.clk, dut.rst)
|
||||||
self.monitor = AxiStreamMonitor(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):
|
def set_idle_generator(self, generator=None):
|
||||||
if generator:
|
if generator:
|
||||||
@@ -90,28 +90,29 @@ async def run_test(dut, payload_lengths=None, payload_data=None, idle_inserter=N
|
|||||||
test_frame = AxiStreamFrame(test_data)
|
test_frame = AxiStreamFrame(test_data)
|
||||||
test_frame.tid = cur_id
|
test_frame.tid = cur_id
|
||||||
test_frame.tdest = cur_id
|
test_frame.tdest = cur_id
|
||||||
tb.source.send(test_frame)
|
await tb.source.send(test_frame)
|
||||||
|
|
||||||
test_frames.append(test_frame)
|
test_frames.append(test_frame)
|
||||||
|
|
||||||
cur_id = (cur_id + 1) % id_count
|
cur_id = (cur_id + 1) % id_count
|
||||||
|
|
||||||
for test_frame in test_frames:
|
for test_frame in test_frames:
|
||||||
await tb.sink.wait()
|
rx_frame = await tb.sink.recv()
|
||||||
rx_frame = tb.sink.recv()
|
|
||||||
|
|
||||||
assert rx_frame.tdata == test_frame.tdata
|
assert rx_frame.tdata == test_frame.tdata
|
||||||
assert rx_frame.tid == test_frame.tid
|
assert rx_frame.tid == test_frame.tid
|
||||||
assert rx_frame.tdest == test_frame.tdest
|
assert rx_frame.tdest == test_frame.tdest
|
||||||
assert not rx_frame.tuser
|
assert not rx_frame.tuser
|
||||||
|
|
||||||
await tb.monitor.wait()
|
mon_rx_frame = await tb.monitor.recv()
|
||||||
rx_frame = tb.monitor.recv()
|
|
||||||
|
|
||||||
assert rx_frame.tdata == test_frame.tdata
|
assert mon_rx_frame.tdata == test_frame.tdata
|
||||||
assert rx_frame.tid == test_frame.tid
|
assert mon_rx_frame.tid == test_frame.tid
|
||||||
assert rx_frame.tdest == test_frame.tdest
|
assert mon_rx_frame.tdest == test_frame.tdest
|
||||||
assert not rx_frame.tuser
|
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.sink.empty()
|
||||||
assert tb.monitor.empty()
|
assert tb.monitor.empty()
|
||||||
@@ -147,7 +148,6 @@ if cocotb.SIM_NAME:
|
|||||||
# cocotb-test
|
# cocotb-test
|
||||||
|
|
||||||
tests_dir = os.path.dirname(__file__)
|
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])
|
@pytest.mark.parametrize("data_width", [8, 16, 32])
|
||||||
@@ -170,8 +170,8 @@ def test_axis(request, data_width):
|
|||||||
|
|
||||||
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
|
||||||
|
|
||||||
sim_build = os.path.join(tests_dir,
|
sim_build = os.path.join(tests_dir, "sim_build",
|
||||||
"sim_build_"+request.node.name.replace('[', '-').replace(']', ''))
|
request.node.name.replace('[', '-').replace(']', ''))
|
||||||
|
|
||||||
cocotb_test.simulator.run(
|
cocotb_test.simulator.run(
|
||||||
python_search=[tests_dir],
|
python_search=[tests_dir],
|
||||||
|
|||||||
Reference in New Issue
Block a user