diff --git a/README.md b/README.md index e6ccd51..8f66216 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,7 @@ To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()`/`re * `tready`: indicates sink is ready for data; optional, assumed `1` when absent * `tlast`: marks the last cycle of a frame; optional, assumed `1` when absent * `tkeep`: qualifies data byte, data bus width must be evenly divisible by `tkeep` signal width; optional, assumed `1` when absent +* `tstrb`: qualifies data byte, data bus width must be evenly divisible by `tstrb` signal width; optional, assumed equal to `tkeep` when absent * `tid`: ID signal, can be used for routing; optional, assumed `0` when absent * `tdest`: destination signal, can be used for routing; optional, assumed `0` when absent * `tuser`: additional user data; optional, assumed `0` when absent @@ -266,7 +267,7 @@ To receive data with an `AxiStreamSink` or `AxiStreamMonitor`, call `recv()`/`re * _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)`. +Note: _byte_size_, _byte_lanes_, `len(tdata)`, `len(tkeep)`, and `len(tstrb)` are all related, in that _byte_lanes_ is set from `tkeep` or `tstrb` if either is connected, and `byte_size*byte_lanes == len(tdata)`. So, if either `tkeep` or `tstrb` is connected, both _byte_size_ and _byte_lanes_ will be computed internally and cannot be overridden. If `tkeep` and `tstrb` 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: @@ -302,12 +303,13 @@ The `AxiStreamBus` object is a container for the interface signals. Currently, #### `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. +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`, `tstrb`, `tid`, `tdest`, and `tuser` can either be `None`, an `int`, or a `list` of `int`s. Attributes: * `tdata`: bytes, bytearray, or list * `tkeep`: tkeep field, optional; list, each entry qualifies the corresponding entry in `tdata`. Can be used to insert gaps on the source side. +* `tstrb`: tstrb field, optional; list, each entry qualifies the corresponding entry in `tdata`. Used to signal padding bytes. * `tid`: tid field, optional; int or list with one entry per `tdata`, last value used per cycle when sending. * `tdest`: tdest field, optional; int or list with one entry per `tdata`, last value used per cycle when sending. * `tuser`: tuser field, optional; int or list with one entry per `tdata`, last value used per cycle when sending. @@ -317,8 +319,8 @@ Attributes: Methods: -* `normalize()`: pack `tkeep`, `tid`, `tdest`, and `tuser` to the same length as `tdata`, replicating last element if necessary, initialize `tkeep` to list of `1` and `tid`, `tdest`, and `tuser` to list of `0` if not specified. -* `compact()`: remove `tdata`, `tid`, `tdest`, and `tuser` values based on `tkeep`, remove `tkeep`, compact `tid`, `tdest`, and `tuser` to an int if all values are identical. +* `normalize()`: pack `tkeep`, `tstrb`, `tid`, `tdest`, and `tuser` to the same length as `tdata`, replicating last element if necessary, initialize `tkeep` to list of `1`, `tstrb` == `tkeep`, and `tid`, `tdest`, and `tuser` to list of `0` if not specified. +* `compact()`: remove `tdata`, `tstrb`, `tid`, `tdest`, and `tuser` values based on `tkeep`, remove `tkeep`, compact `tid`, `tdest`, and `tuser` to an int if all values are identical. ### Address space abstraction diff --git a/cocotbext/axi/axis.py b/cocotbext/axi/axis.py index aa83d1d..9fc5911 100644 --- a/cocotbext/axi/axis.py +++ b/cocotbext/axi/axis.py @@ -35,9 +35,10 @@ class AxiStreamFrame: - def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None, tx_complete=None): + def __init__(self, tdata=b'', tkeep=None, tstrb=None, tid=None, tdest=None, tuser=None, tx_complete=None): self.tdata = bytearray() self.tkeep = None + self.tstrb = None self.tid = None self.tdest = None self.tuser = None @@ -52,6 +53,8 @@ def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None, tx_c self.tdata = list(tdata.tdata) if tdata.tkeep is not None: self.tkeep = list(tdata.tkeep) + if tdata.tstrb is not None: + self.tstrb = list(tdata.tstrb) if tdata.tid is not None: if type(tdata.tid) in (int, bool): self.tid = tdata.tid @@ -73,12 +76,14 @@ def __init__(self, tdata=b'', tkeep=None, tid=None, tdest=None, tuser=None, tx_c elif type(tdata) in (bytes, bytearray): self.tdata = bytearray(tdata) self.tkeep = tkeep + self.tstrb = tstrb self.tid = tid self.tdest = tdest self.tuser = tuser else: self.tdata = list(tdata) self.tkeep = tkeep + self.tstrb = tstrb self.tid = tid self.tdest = tdest self.tuser = tuser @@ -95,6 +100,11 @@ def normalize(self): else: self.tkeep = [1]*n + if self.tstrb is not None: + self.tstrb = self.tstrb[:n] + [self.tstrb[-1]]*(n-len(self.tstrb)) + else: + self.tstrb = self.tkeep + if self.tid is not None: if type(self.tid) in (int, bool): self.tid = [self.tid]*n @@ -128,6 +138,8 @@ def compact(self): del self.tdata[k] if k < len(self.tkeep): del self.tkeep[k] + if k < len(self.tstrb): + del self.tstrb[k] if k < len(self.tid): del self.tid[k] if k < len(self.tdest): @@ -172,6 +184,10 @@ def __eq__(self, other): if self.tkeep != other.tkeep: return False + if self.tstrb is not None and other.tstrb is not None: + if self.tstrb != other.tstrb: + return False + if self.tid is not None and other.tid is not None: if type(self.tid) in (int, bool) and type(other.tid) is list: for k in other.tid: @@ -214,6 +230,7 @@ def __repr__(self): return ( f"{type(self).__name__}(tdata={self.tdata!r}, " f"tkeep={self.tkeep!r}, " + f"tstrb={self.tstrb!r}, " f"tid={self.tid!r}, " f"tdest={self.tdest!r}, " f"tuser={self.tuser!r}, " @@ -234,7 +251,7 @@ def __bytes__(self): class AxiStreamBus(Bus): _signals = ["tdata"] - _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"] + _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tstrb", "tid", "tdest", "tuser"] def __init__(self, entity=None, prefix=None, **kwargs): super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs) @@ -251,7 +268,7 @@ def from_prefix(cls, entity, prefix, **kwargs): class AxiStreamBase(Reset): _signals = ["tdata"] - _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"] + _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tstrb", "tid", "tdest", "tuser"] _type = "base" @@ -306,6 +323,14 @@ def __init__(self, bus, clock, reset=None, reset_active_level=True, self.byte_lanes = len(self.bus.tkeep) if byte_size is not None or byte_lanes is not None: raise ValueError("Cannot specify byte_size or byte_lanes if tkeep is connected") + if hasattr(self.bus, "tstrb") and (len(self.bus.tstrb) != self.byte_lanes): + raise ValueError("tstrb must be the same width as tkeep") + + elif hasattr(self.bus, "tstrb"): + self.byte_lanes = len(self.bus.tstrb) + if byte_size is not None or byte_lanes is not None: + raise ValueError("Cannot specify byte_size or byte_lanes if tstrb is connected") + else: if byte_lanes is not None: self.byte_lanes = byte_lanes @@ -484,6 +509,8 @@ def _handle_reset(self, state): self.bus.tlast.value = 0 if hasattr(self.bus, "tkeep"): self.bus.tkeep.value = 0 + if hasattr(self.bus, "tstrb"): + self.bus.tstrb.value = 0 if hasattr(self.bus, "tid"): self.bus.tid.value = 0 if hasattr(self.bus, "tdest"): @@ -505,6 +532,7 @@ async def _run(self): has_tvalid = hasattr(self.bus, "tvalid") has_tlast = hasattr(self.bus, "tlast") has_tkeep = hasattr(self.bus, "tkeep") + has_tstrb = hasattr(self.bus, "tstrb") has_tid = hasattr(self.bus, "tid") has_tdest = hasattr(self.bus, "tdest") has_tuser = hasattr(self.bus, "tuser") @@ -536,6 +564,7 @@ async def _run(self): tdata_val = 0 tlast_val = 0 tkeep_val = 0 + tstrb_val = 0 tid_val = 0 tdest_val = 0 tuser_val = 0 @@ -543,6 +572,7 @@ async def _run(self): for offset in range(self.byte_lanes): tdata_val |= (frame.tdata[frame_offset] & self.byte_mask) << (offset * self.byte_size) tkeep_val |= (frame.tkeep[frame_offset] & 1) << offset + tstrb_val |= (frame.tstrb[frame_offset] & 1) << offset tid_val = frame.tid[frame_offset] tdest_val = frame.tdest[frame_offset] tuser_val = frame.tuser[frame_offset] @@ -563,6 +593,8 @@ async def _run(self): self.bus.tlast.value = tlast_val if has_tkeep: self.bus.tkeep.value = tkeep_val + if has_tstrb: + self.bus.tstrb.value = tstrb_val if has_tid: self.bus.tid.value = tid_val if has_tdest: @@ -673,6 +705,7 @@ async def _run(self): has_tvalid = hasattr(self.bus, "tvalid") has_tlast = hasattr(self.bus, "tlast") has_tkeep = hasattr(self.bus, "tkeep") + has_tstrb = hasattr(self.bus, "tstrb") has_tid = hasattr(self.bus, "tid") has_tdest = hasattr(self.bus, "tdest") has_tuser = hasattr(self.bus, "tuser") @@ -691,9 +724,9 @@ async def _run(self): if tready_sample and tvalid_sample: if not frame: if self.byte_size == 8: - frame = AxiStreamFrame(bytearray(), [], [], [], []) + frame = AxiStreamFrame(bytearray(), [], [], [], [], []) else: - frame = AxiStreamFrame([], [], [], [], []) + frame = AxiStreamFrame([], [], [], [], [], []) frame.sim_time_start = get_sim_time() self.active = True @@ -701,6 +734,8 @@ async def _run(self): frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask) if has_tkeep: frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1) + if has_tstrb: + frame.tstrb.append((self.bus.tstrb.value.integer >> offset) & 1) if has_tid: frame.tid.append(self.bus.tid.value.integer) if has_tdest: @@ -772,6 +807,7 @@ async def _run(self): has_tvalid = hasattr(self.bus, "tvalid") has_tlast = hasattr(self.bus, "tlast") has_tkeep = hasattr(self.bus, "tkeep") + has_tstrb = hasattr(self.bus, "tstrb") has_tid = hasattr(self.bus, "tid") has_tdest = hasattr(self.bus, "tdest") has_tuser = hasattr(self.bus, "tuser") @@ -792,9 +828,9 @@ async def _run(self): if tready_sample and tvalid_sample: if not frame: if self.byte_size == 8: - frame = AxiStreamFrame(bytearray(), [], [], [], []) + frame = AxiStreamFrame(bytearray(), [], [], [], [], []) else: - frame = AxiStreamFrame([], [], [], [], []) + frame = AxiStreamFrame([], [], [], [], [], []) frame.sim_time_start = get_sim_time() self.active = True @@ -802,6 +838,8 @@ async def _run(self): frame.tdata.append((self.bus.tdata.value.integer >> (offset * self.byte_size)) & self.byte_mask) if has_tkeep: frame.tkeep.append((self.bus.tkeep.value.integer >> offset) & 1) + if has_tstrb: + frame.tstrb.append((self.bus.tstrb.value.integer >> offset) & 1) if has_tid: frame.tid.append(self.bus.tid.value.integer) if has_tdest: