From 1618220a30202046bb525c185c721364bb7b0340 Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Thu, 24 Dec 2020 23:19:58 -0800 Subject: [PATCH] Rework AXI stream resets --- cocotbext/axi/axis.py | 109 +++++++++++++++++++++++++---------------- cocotbext/axi/reset.py | 66 +++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 42 deletions(-) create mode 100644 cocotbext/axi/reset.py diff --git a/cocotbext/axi/axis.py b/cocotbext/axi/axis.py index f78bc1f..0e26d73 100644 --- a/cocotbext/axi/axis.py +++ b/cocotbext/axi/axis.py @@ -31,6 +31,7 @@ from cocotb.utils import get_sim_time from cocotb.bus import Bus from .version import __version__ +from .reset import Reset class AxiStreamFrame: @@ -216,7 +217,7 @@ class AxiStreamFrame: return bytes(self.tdata) -class AxiStreamSource: +class AxiStreamSource(Reset): _signals = ["tdata"] _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"] @@ -248,8 +249,6 @@ class AxiStreamSource: self.width = len(self.bus.tdata) self.byte_lanes = 1 - self.reset = reset - self.bus.tdata.setimmediatevalue(0) if hasattr(self.bus, "tvalid"): self.bus.tvalid.setimmediatevalue(0) @@ -306,7 +305,9 @@ class AxiStreamSource: raise ValueError(f"Bus does not evenly divide into byte lanes " f"({self.byte_lanes} * {self.byte_size} != {self.width})") - cocotb.fork(self._run()) + self._run_cr = None + + self._init_reset(reset) async def send(self, frame): self.send_nowait(frame) @@ -349,6 +350,32 @@ class AxiStreamSource: def clear_pause_generator(self): self.set_pause_generator(None) + 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 + self.bus.tdata <= 0 + if hasattr(self.bus, "tvalid"): + self.bus.tvalid <= 0 + if hasattr(self.bus, "tlast"): + self.bus.tlast <= 0 + if hasattr(self.bus, "tkeep"): + self.bus.tkeep <= 0 + if hasattr(self.bus, "tid"): + self.bus.tid <= 0 + if hasattr(self.bus, "tdest"): + self.bus.tdest <= 0 + if hasattr(self.bus, "tuser"): + self.bus.tuser <= 0 + async def _run(self): frame = None self.active = False @@ -360,24 +387,6 @@ class AxiStreamSource: 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: - frame = None - self.active = False - self.bus.tdata <= 0 - if hasattr(self.bus, "tvalid"): - self.bus.tvalid <= 0 - if hasattr(self.bus, "tlast"): - self.bus.tlast <= 0 - if hasattr(self.bus, "tkeep"): - self.bus.tkeep <= 0 - if hasattr(self.bus, "tid"): - self.bus.tid <= 0 - if hasattr(self.bus, "tdest"): - self.bus.tdest <= 0 - if hasattr(self.bus, "tuser"): - self.bus.tuser <= 0 - continue - if (tready_sample and tvalid_sample) or not tvalid_sample: if frame is None and self.queue: frame = self.queue.popleft() @@ -433,7 +442,7 @@ class AxiStreamSource: await RisingEdge(self.clock) -class AxiStreamSink: +class AxiStreamSink(Reset): _signals = ["tdata"] _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"] @@ -469,8 +478,6 @@ class AxiStreamSink: self.width = len(self.bus.tdata) self.byte_lanes = 1 - self.reset = reset - if hasattr(self.bus, "tready"): self.bus.tready.setimmediatevalue(0) @@ -516,7 +523,9 @@ class AxiStreamSink: raise ValueError(f"Bus does not evenly divide into byte lanes " f"({self.byte_lanes} * {self.byte_size} != {self.width})") - cocotb.fork(self._run()) + self._run_cr = None + + self._init_reset(reset) async def recv(self, compact=True): while self.empty(): @@ -589,6 +598,21 @@ class AxiStreamSink: def clear_pause_generator(self): self.set_pause_generator(None) + 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 + if hasattr(self.bus, "tready"): + self.bus.tready <= 0 + async def _run(self): frame = None self.active = False @@ -600,13 +624,6 @@ class AxiStreamSink: 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: - frame = None - self.active = False - if hasattr(self.bus, "tready"): - self.bus.tready <= 0 - continue - if tready_sample and tvalid_sample: if frame is None: if self.byte_size == 8: @@ -647,7 +664,7 @@ class AxiStreamSink: await RisingEdge(self.clock) -class AxiStreamMonitor: +class AxiStreamMonitor(Reset): _signals = ["tdata"] _optional_signals = ["tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"] @@ -677,8 +694,6 @@ class AxiStreamMonitor: self.width = len(self.bus.tdata) self.byte_lanes = 1 - self.reset = reset - if hasattr(self.bus, "tkeep"): self.byte_lanes = len(self.bus.tkeep) if byte_size is not None or byte_lanes is not None: @@ -721,7 +736,9 @@ class AxiStreamMonitor: raise ValueError(f"Bus does not evenly divide into byte lanes " f"({self.byte_lanes} * {self.byte_size} != {self.width})") - cocotb.fork(self._run()) + self._run_cr = None + + self._init_reset(reset) async def recv(self, compact=True): while self.empty(): @@ -773,6 +790,19 @@ class AxiStreamMonitor: else: await self.sync.wait() + 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): frame = None self.active = False @@ -784,11 +814,6 @@ class AxiStreamMonitor: 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: - frame = None - self.active = False - continue - if tready_sample and tvalid_sample: if frame is None: if self.byte_size == 8: diff --git a/cocotbext/axi/reset.py b/cocotbext/axi/reset.py new file mode 100644 index 0000000..34eac68 --- /dev/null +++ b/cocotbext/axi/reset.py @@ -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_high=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, active_high)) + + 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 = 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_high): + while True: + if bool(reset_signal.value): + await FallingEdge(reset_signal) + self._ext_reset = not active_high + self._update_reset() + else: + await RisingEdge(reset_signal) + self._ext_reset = active_high + self._update_reset()