From ad73088e39dd5b8015047fc55b1bd3f9246b734a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 20 Jun 2023 13:10:01 -0700 Subject: [PATCH] Add `previousValue` to `Logic` (#385) --- lib/src/signals/logic.dart | 16 +++++++ lib/src/signals/logic_structure.dart | 3 ++ lib/src/signals/wire.dart | 45 ++++++++++++++----- test/changed_test.dart | 34 ++++++++++++++ test/previous_value_test.dart | 66 ++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 test/previous_value_test.dart diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index 8f2b79127..234fe41c9 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -119,6 +119,22 @@ class Logic { /// Throws an exception if [width] is not `1`. Future get nextNegedge => _wire.nextNegedge; + /// The [value] of this signal before the most recent [Simulator.tick] had + /// completed. It will be `null` before the first tick after this signal is + /// created. + /// + /// If this is called mid-tick, it will be the value from before the tick + /// started. If this is called post-tick, it will be the value from before + /// that last tick started. + /// + /// This is useful for querying the value of a signal in a testbench before + /// some change event occurred, for example sampling a signal before a clock + /// edge for code that was triggered on that edge. + /// + /// Note that if a signal is connected to another signal, the listener may + /// be reset. + LogicValue? get previousValue => _wire.previousValue; + /// The [Module] that this [Logic] exists within. /// /// For internal signals, this only gets populated after its parent [Module], diff --git a/lib/src/signals/logic_structure.dart b/lib/src/signals/logic_structure.dart index 1879d535c..f5a9beada 100644 --- a/lib/src/signals/logic_structure.dart +++ b/lib/src/signals/logic_structure.dart @@ -285,6 +285,9 @@ class LogicStructure implements Logic { @override LogicValue get value => packed.value; + @override + LogicValue? get previousValue => packed.previousValue; + @override late final int width = elements.isEmpty ? 0 diff --git a/lib/src/signals/wire.dart b/lib/src/signals/wire.dart index dd4b4e31d..b2d5de2f4 100644 --- a/lib/src/signals/wire.dart +++ b/lib/src/signals/wire.dart @@ -13,7 +13,9 @@ part of signals; /// more [Logic]s. class _Wire { _Wire({required this.width}) - : _currentValue = LogicValue.filled(width, LogicValue.z); + : _currentValue = LogicValue.filled(width, LogicValue.z) { + _setupPreTickListener(); + } /// The current active value of this signal. LogicValue get value => _currentValue; @@ -53,10 +55,7 @@ class _Wire { // them! saves performance! _changedBeingWatched = true; - _preTickSubscription = Simulator.preTick.listen((event) { - _preTickValue = value; - }); - _postTickSubscription = Simulator.postTick.listen((event) { + _postTickSubscription ??= Simulator.postTick.listen((event) { if (value != _preTickValue && _preTickValue != null) { _changedController.add(LogicValueChanged(value, _preTickValue!)); } @@ -65,22 +64,48 @@ class _Wire { return _changedController.stream; } + /// Sets up the pre-tick listener for [_preTickValue]. + /// + /// If one already exists, it will not create a new one. + void _setupPreTickListener() { + _preTickSubscription ??= Simulator.preTick.listen((event) { + _preTickValue = value; + }); + } + + /// The [value] of this signal before the most recent [Simulator.tick] had + /// completed. It will be `null` before the first tick after this signal is + /// created. + /// + /// If this is called mid-tick, it will be the value from before the tick + /// started. If this is called post-tick, it will be the value from before + /// that last tick started. + /// + /// This is useful for querying the value of a signal in a testbench before + /// some change event occurred, for example sampling a signal before a clock + /// edge for code that was triggered on that edge. + /// + /// Note that if a signal is connected to another signal, the listener may + /// be reset. + LogicValue? get previousValue => _preTickValue; + /// The subscription to the [Simulator]'s `preTick`. /// - /// Only non-null if [_changedBeingWatched] is true. - late final StreamSubscription _preTickSubscription; + /// Non-null after the first tick has occurred after creation of `this`. + StreamSubscription? _preTickSubscription; /// The subscription to the [Simulator]'s `postTick`. /// /// Only non-null if [_changedBeingWatched] is true. - late final StreamSubscription _postTickSubscription; + StreamSubscription? _postTickSubscription; /// Cancels all [Simulator] subscriptions and uses [newChanged] as the /// source to replace all [changed] events for this [_Wire]. void _migrateChangedTriggers(Stream newChanged) { + unawaited(_preTickSubscription?.cancel()); + if (_changedBeingWatched) { - unawaited(_preTickSubscription.cancel()); - unawaited(_postTickSubscription.cancel()); + unawaited(_postTickSubscription?.cancel()); newChanged.listen(_changedController.add); _changedBeingWatched = false; } diff --git a/test/changed_test.dart b/test/changed_test.dart index 4c1ffc58a..69830707f 100644 --- a/test/changed_test.dart +++ b/test/changed_test.dart @@ -315,4 +315,38 @@ void main() { Simulator.setMaxSimTime(5000); await Simulator.run(); }); + + test('chain of changed and injects', () async { + final a = Logic()..put(0); + final b = Logic()..put(0); + final c = Logic()..put(0); + final d = Logic()..put(0); + + var changeCount = 0; + + a.changed.listen((event) { + b.inject(~b.value); + }); + + b.changed.listen((event) { + c.inject(~c.value); + }); + + c.changed.listen((event) { + d.inject(~d.value); + }); + + d.changed.listen((event) { + changeCount++; + }); + + Simulator.registerAction(10, () => a.put(~a.value)); + Simulator.registerAction(20, () => a.put(~a.value)); + + Simulator.setMaxSimTime(500); + + await Simulator.run(); + + expect(changeCount, 2); + }); } diff --git a/test/previous_value_test.dart b/test/previous_value_test.dart new file mode 100644 index 000000000..7de789ea9 --- /dev/null +++ b/test/previous_value_test.dart @@ -0,0 +1,66 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// previous_value_test.dart +// Tests for Logic.previousValue +// +// 2023 June 16 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:test/test.dart'; + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + test('sample on flop with listen', () async { + final clk = SimpleClockGenerator(10).clk; + + final a = Logic()..put(0); + + final b = flop(clk, a); + final c = flop(clk, b); + + Simulator.registerAction(11, () => a.put(1)); + + clk.posedge.listen((event) { + if (Simulator.time == 25) { + expect(c.previousValue!.toInt(), 0); + expect(c.value.toInt(), 1); + } + }); + + Simulator.setMaxSimTime(200); + await Simulator.run(); + }); + + test('sample on flop with await', () async { + final clk = SimpleClockGenerator(10).clk; + + final a = Logic()..put(0); + + final b = flop(clk, a); + final c = flop(clk, b); + + Simulator.registerAction(11, () => a.put(1)); + + Future clkLoop() async { + while (!Simulator.simulationHasEnded) { + await clk.nextPosedge; + if (Simulator.time == 25) { + expect(c.previousValue!.toInt(), 0); + expect(c.value.toInt(), 1); + } + } + } + + unawaited(clkLoop()); + + Simulator.setMaxSimTime(200); + await Simulator.run(); + }); +}