Skip to content

Commit

Permalink
Add previousValue to Logic (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkorbel1 authored Jun 20, 2023
1 parent 5192eca commit ad73088
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 10 deletions.
16 changes: 16 additions & 0 deletions lib/src/signals/logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ class Logic {
/// Throws an exception if [width] is not `1`.
Future<LogicValueChanged> 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],
Expand Down
3 changes: 3 additions & 0 deletions lib/src/signals/logic_structure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 35 additions & 10 deletions lib/src/signals/wire.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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!));
}
Expand All @@ -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<void> _preTickSubscription;
/// Non-null after the first tick has occurred after creation of `this`.
StreamSubscription<void>? _preTickSubscription;

/// The subscription to the [Simulator]'s `postTick`.
///
/// Only non-null if [_changedBeingWatched] is true.
late final StreamSubscription<void> _postTickSubscription;
StreamSubscription<void>? _postTickSubscription;

/// Cancels all [Simulator] subscriptions and uses [newChanged] as the
/// source to replace all [changed] events for this [_Wire].
void _migrateChangedTriggers(Stream<LogicValueChanged> newChanged) {
unawaited(_preTickSubscription?.cancel());

if (_changedBeingWatched) {
unawaited(_preTickSubscription.cancel());
unawaited(_postTickSubscription.cancel());
unawaited(_postTickSubscription?.cancel());
newChanged.listen(_changedController.add);
_changedBeingWatched = false;
}
Expand Down
34 changes: 34 additions & 0 deletions test/changed_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
66 changes: 66 additions & 0 deletions test/previous_value_test.dart
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>

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<void> 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();
});
}

0 comments on commit ad73088

Please sign in to comment.