Skip to content

Commit e4bcdea

Browse files
authored
Fix bugs in LogicStructure instrumentation calls to packed and changed issues across Simulator.reset (#458)
1 parent 395921c commit e4bcdea

File tree

6 files changed

+240
-25
lines changed

6 files changed

+240
-25
lines changed

lib/src/signals/logic_structure.dart

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,12 @@ class LogicStructure implements Logic {
281281
Logic? get srcConnection => null;
282282

283283
@override
284-
LogicValue get value => packed.value;
284+
LogicValue get value =>
285+
elements.map((e) => e.value).toList(growable: false).rswizzle();
285286

286287
@override
287-
LogicValue? get previousValue => packed.previousValue;
288+
LogicValue? get previousValue =>
289+
elements.map((e) => e.value).toList(growable: false).rswizzle();
288290

289291
@override
290292
late final int width = elements.map((e) => e.width).sum;
@@ -396,10 +398,32 @@ class LogicStructure implements Logic {
396398
@Deprecated('Use `value` instead.'
397399
' Check `width` separately to confirm single-bit.')
398400
@override
401+
// Can rely on `packed` here because it must be 1 bit.
399402
LogicValue get bit => packed.bit;
400403

401404
@override
402-
Stream<LogicValueChanged> get changed => packed.changed;
405+
late final Stream<LogicValueChanged> changed = _internalPacked.changed;
406+
407+
/// An internal version of [packed] for instrumentation operations on this
408+
/// [LogicStructure].
409+
late final _internalPacked = _generateInternalPacked();
410+
411+
/// Generates and subscribes to be stored lazily into [_internalPacked].
412+
Logic _generateInternalPacked() {
413+
final internalPacked = Logic(width: width)..put(value);
414+
415+
void updateInternalPacked() {
416+
internalPacked.put(value);
417+
}
418+
419+
for (final element in elements) {
420+
element.glitch.listen((args) {
421+
updateInternalPacked();
422+
});
423+
}
424+
425+
return internalPacked;
426+
}
403427

404428
@override
405429
Logic eq(dynamic other) => packed.eq(other);
@@ -408,7 +432,8 @@ class LogicStructure implements Logic {
408432
Logic neq(dynamic other) => packed.neq(other);
409433

410434
@override
411-
SynchronousEmitter<LogicValueChanged> get glitch => packed.glitch;
435+
late final SynchronousEmitter<LogicValueChanged> glitch =
436+
_internalPacked.glitch;
412437

413438
@override
414439
Logic gt(dynamic other) => packed.gt(other);
@@ -424,28 +449,32 @@ class LogicStructure implements Logic {
424449

425450
@Deprecated('Use value.isValid instead.')
426451
@override
427-
bool hasValidValue() => packed.hasValidValue();
452+
bool hasValidValue() => value.isValid;
428453

429454
@Deprecated('Use value.isFloating instead.')
430455
@override
431-
bool isFloating() => packed.isFloating();
456+
bool isFloating() => value.isFloating;
432457

433458
@override
434459
Logic isIn(List<dynamic> list) => packed.isIn(list);
435460

436461
@override
462+
// Can rely on `packed` here because it must be 1 bit.
437463
Stream<LogicValueChanged> get negedge => packed.negedge;
438464

439465
@override
466+
// Can rely on `packed` here because it must be 1 bit.
440467
Stream<LogicValueChanged> get posedge => packed.posedge;
441468

442469
@override
443-
Future<LogicValueChanged> get nextChanged => packed.nextChanged;
470+
Future<LogicValueChanged> get nextChanged => changed.first;
444471

445472
@override
473+
// Can rely on `packed` here because it must be 1 bit.
446474
Future<LogicValueChanged> get nextNegedge => packed.nextNegedge;
447475

448476
@override
477+
// Can rely on `packed` here because it must be 1 bit.
449478
Future<LogicValueChanged> get nextPosedge => packed.nextPosedge;
450479

451480
@override
@@ -460,13 +489,14 @@ class LogicStructure implements Logic {
460489
@override
461490
Logic zeroExtend(int newWidth) => packed.zeroExtend(newWidth);
462491

492+
@Deprecated('Use `value` instead.'
493+
' Check `width` separately to confirm single-bit.')
463494
@override
464-
// ignore: deprecated_member_use_from_same_package
465-
BigInt get valueBigInt => packed.valueBigInt;
495+
BigInt get valueBigInt => value.toBigInt();
466496

497+
@Deprecated('Use value.toInt() instead.')
467498
@override
468-
// ignore: deprecated_member_use_from_same_package
469-
int get valueInt => packed.valueInt;
499+
int get valueInt => value.toInt();
470500

471501
@override
472502
Logic? get _srcConnection => throw UnsupportedError('Delegated to elements');

lib/src/signals/wire.dart

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,26 @@ class _Wire {
5555
// them! saves performance!
5656
_changedBeingWatched = true;
5757

58-
_postTickSubscription ??= Simulator.postTick.listen((event) {
59-
if (value != _preTickValue && _preTickValue != null) {
60-
_changedController.add(LogicValueChanged(value, _preTickValue!));
61-
}
62-
});
58+
StreamSubscription<void> subscribe() {
59+
unawaited(Simulator.resetRequested.then((_) async {
60+
final oldPostTickSubscription = _postTickSubscription;
61+
62+
_postTickSubscription = subscribe();
63+
64+
unawaited(oldPostTickSubscription?.cancel());
65+
}));
66+
67+
return Simulator.postTick.listen((event) {
68+
if (value != _preTickValue && _preTickValue != null) {
69+
_changedController.add(LogicValueChanged(value, _preTickValue!));
70+
}
71+
});
72+
}
73+
74+
assert(_postTickSubscription == null,
75+
'Should not be creating a new subscription if one exists.');
76+
77+
_postTickSubscription = subscribe();
6378
}
6479
return _changedController.stream;
6580
}
@@ -68,9 +83,22 @@ class _Wire {
6883
///
6984
/// If one already exists, it will not create a new one.
7085
void _setupPreTickListener() {
71-
_preTickSubscription ??= Simulator.preTick.listen((event) {
86+
_preTickSubscription = Simulator.preTick.listen((event) {
7287
_preTickValue = value;
7388
});
89+
90+
unawaited(Simulator.resetRequested.then((_) async {
91+
assert(_preTickSubscription != null,
92+
'Should not be null if we are setting up a new one.');
93+
94+
_preTickValue = value;
95+
96+
final oldPreTickSubscription = _preTickSubscription;
97+
98+
_setupPreTickListener();
99+
100+
unawaited(oldPreTickSubscription?.cancel());
101+
}));
74102
}
75103

76104
/// The [value] of this signal before the most recent [Simulator.tick] had

lib/src/simulator.dart

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,25 +89,29 @@ abstract class Simulator {
8989

9090
/// Emits an event before any other actions take place on the tick.
9191
static Stream<void> get preTick => _preTickController.stream;
92+
static StreamController<void> _preTickController =
93+
StreamController.broadcast(sync: true);
9294

9395
/// Emits an event at the start of actions within a tick.
9496
static Stream<void> get startTick => _startTickController.stream;
97+
static StreamController<void> _startTickController =
98+
StreamController.broadcast(sync: true);
9599

96100
/// Emits an event when most events are complete, and clocks are stable.
97101
static Stream<void> get clkStable => _clkStableController.stream;
102+
static StreamController<void> _clkStableController =
103+
StreamController.broadcast(sync: true);
98104

99105
/// Emits an event after all events are completed.
100106
static Stream<void> get postTick => _postTickController.stream;
101-
102-
static StreamController<void> _preTickController =
103-
StreamController.broadcast(sync: true);
104-
static StreamController<void> _startTickController =
105-
StreamController.broadcast(sync: true);
106-
static StreamController<void> _clkStableController =
107-
StreamController.broadcast(sync: true);
108107
static StreamController<void> _postTickController =
109108
StreamController.broadcast(sync: true);
110109

110+
/// Completes when [reset] is called after the [Simulator] has completed any
111+
/// actions it needs to perform to prepare for the next simulation.
112+
static Future<void> get resetRequested => _resetCompleter.future;
113+
static Completer<void> _resetCompleter = Completer<void>();
114+
111115
/// Completes when the simulation has completed.
112116
static Future<void> get simulationEnded => _simulationEndedCompleter.future;
113117
static Completer<void> _simulationEndedCompleter = Completer<void>();
@@ -156,6 +160,13 @@ abstract class Simulator {
156160
_pendingTimestamps.clear();
157161
_phase = SimulatorPhase.outOfTick;
158162
_injectedActions.clear();
163+
164+
// make sure we've already passed the new completer so that listeners can
165+
// get the latest
166+
final oldResetCompleter = _resetCompleter;
167+
_resetCompleter = Completer();
168+
oldResetCompleter.complete();
169+
await oldResetCompleter.future;
159170
}
160171

161172
/// Sets a time, after which, the [Simulator] will halt processing of new

test/changed_test.dart

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,79 @@ void main() {
3939
expect(numChangesDetected, equals(1));
4040
});
4141

42+
group('across simulator reset', () {
43+
Future<void> testAcrossSimulatorResets(Logic a) async {
44+
var numChangesDetected = 0;
45+
46+
a.changed.listen((event) {
47+
numChangesDetected++;
48+
expect(event.newValue, event.previousValue + 1);
49+
});
50+
51+
a.glitch.listen((args) {});
52+
53+
Simulator.registerAction(10, () => a.put(1));
54+
55+
await Simulator.run();
56+
57+
expect(numChangesDetected, 1);
58+
59+
await Simulator.reset();
60+
61+
expect(a.value.toInt(), 1);
62+
63+
// should *not* trigger a change, didn't change!
64+
Simulator.registerAction(20, () => a.put(1));
65+
66+
await Simulator.run();
67+
68+
expect(numChangesDetected, 1);
69+
70+
await Simulator.reset();
71+
72+
expect(a.value.toInt(), 1);
73+
74+
// *should* trigger a change
75+
Simulator.registerAction(30, () => a.put(2));
76+
Simulator.registerAction(40, () => a.put(3));
77+
78+
await Simulator.run();
79+
80+
expect(a.value.toInt(), 3);
81+
82+
expect(numChangesDetected, 3);
83+
}
84+
85+
test('normal logic', () async {
86+
final a = Logic(width: 8)..put(0);
87+
88+
await testAcrossSimulatorResets(a);
89+
});
90+
91+
test('logic structure', () async {
92+
final a = LogicStructure([Logic(), Logic()])..put(0);
93+
94+
await testAcrossSimulatorResets(a);
95+
});
96+
});
97+
98+
test('changed does not trigger first time if no change', () async {
99+
final a = Logic()..put(1);
100+
101+
var numChangesDetected = 0;
102+
103+
a.changed.listen((event) {
104+
numChangesDetected++;
105+
});
106+
107+
Simulator.registerAction(10, () => a.put(1));
108+
Simulator.registerAction(20, () => a.put(1));
109+
110+
await Simulator.run();
111+
112+
expect(numChangesDetected, 0);
113+
});
114+
42115
test('clk edge counter', () async {
43116
final clk = SimpleClockGenerator(10).clk;
44117
final b = Logic();

test/logic_structure_test.dart

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// 2023 May 5
88
// Author: Max Korbel <[email protected]>
99

10+
import 'dart:async';
11+
1012
import 'package:rohd/rohd.dart';
1113
import 'package:rohd/src/utilities/simcompare.dart';
1214
import 'package:test/test.dart';
@@ -70,11 +72,81 @@ class FancyStructInverter extends Module {
7072
}
7173
}
7274

75+
class StructModuleWithInstrumentation extends Module {
76+
StructModuleWithInstrumentation(Logic a) {
77+
a = addInput('a', a, width: 2);
78+
79+
MyStruct()
80+
..gets(a)
81+
..value
82+
..previousValue
83+
..width
84+
..srcConnection
85+
..dstConnections
86+
..parentModule
87+
..parentStructure
88+
..naming
89+
..arrayIndex
90+
..isArrayMember
91+
..leafElements
92+
..isInput
93+
..isOutput
94+
..changed
95+
..glitch
96+
..nextChanged
97+
// ignore: deprecated_member_use_from_same_package
98+
..hasValidValue()
99+
// ignore: deprecated_member_use_from_same_package
100+
..isFloating()
101+
// ignore: deprecated_member_use_from_same_package
102+
..valueBigInt
103+
// ignore: deprecated_member_use_from_same_package
104+
..valueInt;
105+
}
106+
}
107+
73108
void main() {
74109
tearDown(() async {
75110
await Simulator.reset();
76111
});
77112

113+
test('late previousValue', () async {
114+
final s = MyStruct();
115+
final clk = SimpleClockGenerator(10).clk;
116+
Simulator.setMaxSimTime(200);
117+
118+
var i = 0;
119+
clk.posedge.listen((_) => s.inject(i++));
120+
121+
unawaited(Simulator.run());
122+
123+
await clk.nextPosedge;
124+
await clk.nextPosedge;
125+
await clk.nextPosedge;
126+
127+
expect(s.previousValue, isNotNull);
128+
expect(s.previousValue!.toInt(), 1);
129+
130+
s.packed;
131+
await clk.nextPosedge;
132+
await clk.nextPosedge;
133+
expect(s.packed.previousValue, s.previousValue);
134+
await clk.nextPosedge;
135+
expect(s.packed.previousValue, s.previousValue);
136+
137+
await Simulator.endSimulation();
138+
});
139+
140+
test('instrumentation on struct does not make hardware', () async {
141+
final mod = StructModuleWithInstrumentation(Const(0, width: 2));
142+
await mod.build();
143+
144+
final sv = mod.generateSynth();
145+
146+
expect(sv.contains('swizzle'), isFalse,
147+
reason: 'Should not pack from instrumentation!');
148+
});
149+
78150
group('LogicStructure construction', () {
79151
test('simple construction', () {
80152
final s = LogicStructure([

0 commit comments

Comments
 (0)