Repository: intel/rohd
Pull request #210 (2022-11-25 08:45:09)

[Issue-12] simpler constructor of simple control blocks
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

The default implementation for If, Iff, etc. take lists of Conditionals as inputs, which can be excessively verbose in cases where there is only one Conditional. Adding a special constructor (e.g. something like Iff.s) which takes a single Conditional could make simple code look cleaner.

## Related Issue(s)

Issue #12 

## Testing

Added a new unit test that should return ``true`` if single conditional constructor is used.

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible?  If yes, how so?

Nope.

## Documentation

> Does the change require any updates to documentation?  If so, where?  Are they included?

Yes. 


quekyj: If [condition] is 1, then [then] will be executed.

```
ElseIf.s(this.condition, this.then);
```
quekyj: If [condition] is 1, then [then] will be executed.

```
ElseIf.s(this. Condition, this.then);
```

Patch:
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 8e0e70e0..f0fb86f2 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -34,7 +34,7 @@ linter:
     - avoid_function_literals_in_foreach_calls
     - avoid_implementing_value_types
     - avoid_init_to_null
-    - avoid_js_rounded_ints
+    # - avoid_js_rounded_ints
     - avoid_multiple_declarations_per_line
     - avoid_null_checks_in_equality_operators
     - avoid_positional_boolean_parameters
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index 12ecc0df..f5b00436 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -26,7 +26,7 @@ class LogicValueChanged {
 
   /// Represents the event of a [Logic] changing value from [previousValue]
   /// to [newValue].
-  LogicValueChanged(this.newValue, this.previousValue);
+  const LogicValueChanged(this.newValue, this.previousValue);
 
   @override
   String toString() => '$previousValue  -->  $newValue';
@@ -49,12 +49,245 @@ class Const extends Logic {
   }
 }
 
+/// Represents a physical wire which shares a common value with one or
+/// more [Logic]s.
+class _Wire {
+  _Wire({required this.width})
+      : _currentValue = LogicValue.filled(width, LogicValue.z);
+
+  /// The current active value of this signal.
+  LogicValue get value => _currentValue;
+
+  /// The number of bits in this signal.
+  final int width;
+
+  /// The current active value of this signal.
+  LogicValue _currentValue;
+
+  /// The last value of this signal before the [Simulator] tick.
+  ///
+  /// This is useful for detecting when to trigger an edge.
+  LogicValue? _preTickValue;
+
+  /// A stream of [LogicValueChanged] events for every time the signal
+  /// transitions at any time during a [Simulator] tick.
+  ///
+  /// This event can occur more than once per edge, or even if there is no edge.
+  SynchronousEmitter<LogicValueChanged> get glitch => _glitchController.emitter;
+  final SynchronousPropagator<LogicValueChanged> _glitchController =
+      SynchronousPropagator<LogicValueChanged>();
+
+  /// Controller for stable events that can be safely consumed at the
+  /// end of a [Simulator] tick.
+  final StreamController<LogicValueChanged> _changedController =
+      StreamController<LogicValueChanged>.broadcast(sync: true);
+
+  /// Tracks whether is being subscribed to by anything/anyone.
+  bool _changedBeingWatched = false;
+
+  /// A [Stream] of [LogicValueChanged] events which triggers at most once
+  /// per [Simulator] tick, iff the value of the [Logic] has changed.
+  Stream<LogicValueChanged> get changed {
+    if (!_changedBeingWatched) {
+      // only do these simulator subscriptions if someone has asked for
+      // them! saves performance!
+      _changedBeingWatched = true;
+
+      _preTickSubscription = Simulator.preTick.listen((event) {
+        _preTickValue = value;
+      });
+      _postTickSubscription = Simulator.postTick.listen((event) {
+        if (value != _preTickValue && _preTickValue != null) {
+          _changedController.add(LogicValueChanged(value, _preTickValue!));
+        }
+      });
+    }
+    return _changedController.stream;
+  }
+
+  /// The subscription to the [Simulator]'s `preTick`.
+  ///
+  /// Only non-null if [_changedBeingWatched] is true.
+  late final StreamSubscription<void> _preTickSubscription;
+
+  /// The subscription to the [Simulator]'s `postTick`.
+  ///
+  /// Only non-null if [_changedBeingWatched] is true.
+  late final 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) {
+    if (_changedBeingWatched) {
+      unawaited(_preTickSubscription.cancel());
+      unawaited(_postTickSubscription.cancel());
+      newChanged.listen(_changedController.add);
+      _changedBeingWatched = false;
+    }
+  }
+
+  /// Tells this [_Wire] to adopt all the behavior of [other] so that
+  /// it can replace [other].
+  void _adopt(_Wire other) {
+    _glitchController.emitter.adopt(other._glitchController.emitter);
+    other._migrateChangedTriggers(changed);
+  }
+
+  /// Store the [negedge] stream to avoid creating multiple copies
+  /// of streams.
+  Stream<LogicValueChanged>? _negedge;
+
+  /// Store the [posedge] stream to avoid creating multiple copies
+  /// of streams.
+  Stream<LogicValueChanged>? _posedge;
+
+  /// A [Stream] of [LogicValueChanged] events which triggers at most once
+  /// per [Simulator] tick, iff the value of the [Logic] has changed
+  /// from `1` to `0`.
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Stream<LogicValueChanged> get negedge {
+    if (width != 1) {
+      throw Exception(
+          'Can only detect negedge when width is 1, but was $width');
+    }
+
+    _negedge ??= changed.where((args) => LogicValue.isNegedge(
+          args.previousValue,
+          args.newValue,
+          ignoreInvalid: true,
+        ));
+
+    return _negedge!;
+  }
+
+  /// A [Stream] of [LogicValueChanged] events which triggers at most once
+  /// per [Simulator] tick, iff the value of the [Logic] has changed
+  /// from `0` to `1`.
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Stream<LogicValueChanged> get posedge {
+    if (width != 1) {
+      throw Exception(
+          'Can only detect posedge when width is 1, but was $width');
+    }
+
+    _posedge ??= changed.where((args) => LogicValue.isPosedge(
+          args.previousValue,
+          args.newValue,
+          ignoreInvalid: true,
+        ));
+
+    return _posedge!;
+  }
+
+  /// Triggers at most once, the next time that this [Logic] changes
+  /// value at the end of a [Simulator] tick.
+  Future<LogicValueChanged> get nextChanged => changed.first;
+
+  /// Triggers at most once, the next time that this [Logic] changes
+  /// value at the end of a [Simulator] tick from `0` to `1`.
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Future<LogicValueChanged> get nextPosedge => posedge.first;
+
+  /// Triggers at most once, the next time that this [Logic] changes
+  /// value at the end of a [Simulator] tick from `1` to `0`.
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Future<LogicValueChanged> get nextNegedge => negedge.first;
+
+  /// Injects a value onto this signal in the current [Simulator] tick.
+  ///
+  /// This function calls [put()] in [Simulator.injectAction()].
+  void inject(dynamic val, {bool fill = false}) {
+    Simulator.injectAction(() => put(val, fill: fill));
+  }
+
+  /// Keeps track of whether there is an active put, to detect reentrance.
+  bool _isPutting = false;
+
+  /// Puts a value [val] onto this signal, which may or may not be picked up
+  /// for [changed] in this [Simulator] tick.
+  ///
+  /// The type of [val] should be an `int`, [BigInt], `bool`, or [LogicValue].
+  ///
+  /// This function is used for propogating glitches through connected signals.
+  /// Use this function for custom definitions of [Module] behavior.
+  ///
+  /// If [fill] is set, all bits of the signal gets set to [val], similar
+  /// to `'` in SystemVerilog.
+  void put(dynamic val, {bool fill = false}) {
+    LogicValue newValue;
+    if (val is int) {
+      if (fill) {
+        newValue = LogicValue.filled(
+            width,
+            val == 0
+                ? LogicValue.zero
+                : val == 1
+                    ? LogicValue.one
+                    : throw Exception('Only can fill 0 or 1, but saw $val.'));
+      } else {
+        newValue = LogicValue.ofInt(val, width);
+      }
+    } else if (val is BigInt) {
+      if (fill) {
+        newValue = LogicValue.filled(
+            width,
+            val == BigInt.zero
+                ? LogicValue.zero
+                : val == BigInt.one
+                    ? LogicValue.one
+                    : throw Exception('Only can fill 0 or 1, but saw $val.'));
+      } else {
+        newValue = LogicValue.ofBigInt(val, width);
+      }
+    } else if (val is bool) {
+      newValue = LogicValue.ofInt(val ? 1 : 0, width);
+    } else if (val is LogicValue) {
+      if (val.width == 1 &&
+          (val == LogicValue.x || val == LogicValue.z || fill)) {
+        newValue = LogicValue.filled(width, val);
+      } else if (fill) {
+        throw Exception(
+            'Failed to fill value with $val.  To fill, it should be 1 bit.');
+      } else {
+        newValue = val;
+      }
+    } else {
+      throw Exception('Unrecognized value "$val" to deposit on this signal. '
+          'Unknown type ${val.runtimeType} cannot be deposited.');
+    }
+
+    if (newValue.width != width) {
+      throw Exception(
+          'Updated value width mismatch.  The width of $val should be $width.');
+    }
+
+    if (_isPutting) {
+      // if this is the result of a cycle, then contention!
+      newValue = LogicValue.filled(width, LogicValue.x);
+    }
+
+    final prevValue = _currentValue;
+    _currentValue = newValue;
+
+    // sends out a glitch if the value deposited has changed
+    if (_currentValue != prevValue) {
+      _isPutting = true;
+      _glitchController.add(LogicValueChanged(_currentValue, prevValue));
+      _isPutting = false;
+    }
+  }
+}
+
 /// Represents a logical signal of any width which can change values.
 class Logic {
   /// An internal counter for encouraging unique naming of unnamed signals.
   static int _signalIdx = 0;
 
-  // special quiet flag to prevent <= and < where inappropriate
+  // A special quiet flag to prevent `<=` and `<` where inappropriate
   bool _unassignable = false;
 
   /// Makes it so that this signal cannot be assigned by any full (`<=`) or
@@ -64,19 +297,16 @@ class Logic {
   /// The name of this signal.
   final String name;
 
-  /// The current active value of this signal.
-  LogicValue _currentValue;
-
-  /// The last value of this signal before the [Simulator] tick.
+  /// The [_Wire] which holds the current value and listeners for this [Logic].
   ///
-  /// This is useful for detecting when to trigger an edge.
-  LogicValue? _preTickValue;
+  /// May be a shared object between multiple [Logic]s.
+  _Wire _wire;
 
   /// The number of bits in this signal.
-  final int width;
+  int get width => _wire.width;
 
   /// The current active value of this signal.
-  LogicValue get value => _currentValue;
+  LogicValue get value => _wire._currentValue;
 
   /// The current active value of this signal if it has width 1, as
   /// a [LogicValue].
@@ -84,7 +314,7 @@ class Logic {
   /// Throws an Exception if width is not 1.
   @Deprecated('Use `value` instead.'
       '  Check `width` separately to confirm single-bit.')
-  LogicValue get bit => _currentValue.bit;
+  LogicValue get bit => value.bit;
 
   /// The current valid active value of this signal as an [int].
   ///
@@ -100,7 +330,7 @@ class Logic {
   BigInt get valueBigInt => value.toBigInt();
 
   /// Returns `true` iff the value of this signal is valid (no `x` or `z`).
-  bool hasValidValue() => _currentValue.isValid;
+  bool hasValidValue() => value.isValid;
 
   /// Returns `true` iff *all* bits of the current value are floating (`z`).
   bool isFloating() => value.isFloating;
@@ -122,65 +352,41 @@ class Logic {
   /// transitions at any time during a [Simulator] tick.
   ///
   /// This event can occur more than once per edge, or even if there is no edge.
-  SynchronousEmitter<LogicValueChanged> get glitch => _glitchController.emitter;
-  final SynchronousPropagator<LogicValueChanged> _glitchController =
-      SynchronousPropagator<LogicValueChanged>();
-
-  /// Controller for stable events that can be safely consumed at the
-  /// end of a [Simulator] tick.
-  final StreamController<LogicValueChanged> _changedController =
-      StreamController<LogicValueChanged>.broadcast(sync: true);
-
-  /// Tracks whether is being subscribed to by anything/anyone.
-  bool _changedBeingWatched = false;
+  SynchronousEmitter<LogicValueChanged> get glitch => _wire.glitch;
 
   /// A [Stream] of [LogicValueChanged] events which triggers at most once
   /// per [Simulator] tick, iff the value of the [Logic] has changed.
-  Stream<LogicValueChanged> get changed {
-    if (!_changedBeingWatched) {
-      // only do these simulator subscriptions if someone has asked for
-      // them! saves performance!
-      _changedBeingWatched = true;
-
-      Simulator.preTick.listen((event) {
-        _preTickValue = value;
-      });
-      Simulator.postTick.listen((event) {
-        if (value != _preTickValue && _preTickValue != null) {
-          _changedController.add(LogicValueChanged(value, _preTickValue!));
-        }
-      });
-    }
-    return _changedController.stream;
-  }
+  Stream<LogicValueChanged> get changed => _wire.changed;
 
   /// A [Stream] of [LogicValueChanged] events which triggers at most once
   /// per [Simulator] tick, iff the value of the [Logic] has changed
   /// from `1` to `0`.
-  Stream<LogicValueChanged> get negedge => changed.where((args) =>
-      width == 1 &&
-      LogicValue.isNegedge(args.previousValue[0], args.newValue[0],
-          ignoreInvalid: true));
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Stream<LogicValueChanged> get negedge => _wire.negedge;
 
   /// A [Stream] of [LogicValueChanged] events which triggers at most once
   /// per [Simulator] tick, iff the value of the [Logic] has changed
   /// from `0` to `1`.
-  Stream<LogicValueChanged> get posedge => changed.where((args) =>
-      width == 1 &&
-      LogicValue.isPosedge(args.previousValue[0], args.newValue[0],
-          ignoreInvalid: true));
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Stream<LogicValueChanged> get posedge => _wire.posedge;
 
   /// Triggers at most once, the next time that this [Logic] changes
   /// value at the end of a [Simulator] tick.
-  Future<LogicValueChanged> get nextChanged => changed.first;
+  Future<LogicValueChanged> get nextChanged => _wire.changed.first;
 
   /// Triggers at most once, the next time that this [Logic] changes
   /// value at the end of a [Simulator] tick from `0` to `1`.
-  Future<LogicValueChanged> get nextPosedge => posedge.first;
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Future<LogicValueChanged> get nextPosedge => _wire.posedge.first;
 
   /// Triggers at most once, the next time that this [Logic] changes
   /// value at the end of a [Simulator] tick from `1` to `0`.
-  Future<LogicValueChanged> get nextNegedge => negedge.first;
+  ///
+  /// Throws an exception if [width] is not `1`.
+  Future<LogicValueChanged> get nextNegedge => _wire.negedge.first;
 
   /// The [Module] that this [Logic] exists within.
   ///
@@ -218,9 +424,9 @@ class Logic {
   ///
   /// The default value for [width] is 1.  The [name] should be synthesizable
   /// to the desired output (e.g. SystemVerilog).
-  Logic({String? name, this.width = 1})
+  Logic({String? name, int width = 1})
       : name = name == null ? 's${_signalIdx++}' : Sanitizer.sanitizeSV(name),
-        _currentValue = LogicValue.filled(width, LogicValue.z) {
+        _wire = _Wire(width: width) {
     if (width < 0) {
       throw Exception('Logic width must be greater than or equal to 0.');
     }
@@ -243,6 +449,24 @@ class Logic {
     }
   }
 
+  /// Injects a value onto this signal in the current [Simulator] tick.
+  ///
+  /// This function calls [put()] in [Simulator.injectAction()].
+  void inject(dynamic val, {bool fill = false}) =>
+      _wire.inject(val, fill: fill);
+
+  /// Puts a value [val] onto this signal, which may or may not be picked up
+  /// for [changed] in this [Simulator] tick.
+  ///
+  /// The type of [val] should be an `int`, [BigInt], `bool`, or [LogicValue].
+  ///
+  /// This function is used for propogating glitches through connected signals.
+  /// Use this function for custom definitions of [Module] behavior.
+  ///
+  /// If [fill] is set, all bits of the signal gets set to [val], similar
+  /// to `'` in SystemVerilog.
+  void put(dynamic val, {bool fill = false}) => _wire.put(val, fill: fill);
+
   /// Connects this [Logic] directly to [other].
   ///
   /// Every time [other] transitions (`glitch`es), this signal will transition
@@ -258,19 +482,19 @@ class Logic {
 
   /// Handles the actual connection of this [Logic] to [other].
   void _connect(Logic other) {
-    if (other.width != width) {
-      throw Exception('Bus widths must match.'
-          ' Cannot connect $this to $other which have different widths.');
-    }
-
     _unassignable = true;
+    _updateWire(other._wire);
+  }
 
-    if (value != other.value) {
-      put(other.value);
+  /// Updates the current active [_Wire] for this [Logic] and also
+  /// notifies all downstream [Logic]s of the new source [_Wire].
+  void _updateWire(_Wire newWire) {
+    newWire._adopt(_wire);
+    _wire = newWire;
+
+    for (final dstConnection in dstConnections) {
+      dstConnection._updateWire(newWire);
     }
-    other.glitch.listen((args) {
-      put(other.value);
-    });
   }
 
   /// Connects this [Logic] directly to another [Logic].
@@ -365,90 +589,6 @@ class Logic {
     }
   }
 
-  /// Injects a value onto this signal in the current [Simulator] tick.
-  ///
-  /// This function calls [put()] in [Simulator.injectAction()].
-  void inject(dynamic val, {bool fill = false}) {
-    Simulator.injectAction(() => put(val, fill: fill));
-  }
-
-  /// Keeps track of whether there is an active put, to detect reentrance.
-  bool _isPutting = false;
-
-  /// Puts a value [val] onto this signal, which may or may not be picked up
-  /// for [changed] in this [Simulator] tick.
-  ///
-  /// The type of [val] should be an `int`, [BigInt], `bool`, or [LogicValue].
-  ///
-  /// This function is used for propogating glitches through connected signals.
-  /// Use this function for custom definitions of [Module] behavior.
-  ///
-  /// If [fill] is set, all bits of the signal gets set to [val], similar
-  /// to `'` in SystemVerilog.
-  void put(dynamic val, {bool fill = false}) {
-    LogicValue newValue;
-    if (val is int) {
-      if (fill) {
-        newValue = LogicValue.filled(
-            width,
-            val == 0
-                ? LogicValue.zero
-                : val == 1
-                    ? LogicValue.one
-                    : throw Exception('Only can fill 0 or 1, but saw $val.'));
-      } else {
-        newValue = LogicValue.ofInt(val, width);
-      }
-    } else if (val is BigInt) {
-      if (fill) {
-        newValue = LogicValue.filled(
-            width,
-            val == BigInt.zero
-                ? LogicValue.zero
-                : val == BigInt.one
-                    ? LogicValue.one
-                    : throw Exception('Only can fill 0 or 1, but saw $val.'));
-      } else {
-        newValue = LogicValue.ofBigInt(val, width);
-      }
-    } else if (val is bool) {
-      newValue = LogicValue.ofInt(val ? 1 : 0, width);
-    } else if (val is LogicValue) {
-      if (val.width == 1 &&
-          (val == LogicValue.x || val == LogicValue.z || fill)) {
-        newValue = LogicValue.filled(width, val);
-      } else if (fill) {
-        throw Exception(
-            'Failed to fill value with $val.  To fill, it should be 1 bit.');
-      } else {
-        newValue = val;
-      }
-    } else {
-      throw Exception('Unrecognized value "$val" to deposit on this signal. '
-          'Unknown type ${val.runtimeType} cannot be deposited.');
-    }
-
-    if (newValue.width != width) {
-      throw Exception(
-          'Updated value width mismatch.  The width of $val should be $width.');
-    }
-
-    if (_isPutting) {
-      // if this is the result of a cycle, then contention!
-      newValue = LogicValue.filled(width, LogicValue.x);
-    }
-
-    final prevValue = _currentValue;
-    _currentValue = newValue;
-
-    // sends out a glitch if the value deposited has changed
-    if (_currentValue != prevValue) {
-      _isPutting = true;
-      _glitchController.add(LogicValueChanged(_currentValue, prevValue));
-      _isPutting = false;
-    }
-  }
-
   /// Accesses the [index]th bit of this signal.
   ///
   /// Accepts both [int] and [Logic] as [index].
diff --git a/lib/src/modules/conditional.dart b/lib/src/modules/conditional.dart
index 589a3d52..c49e8aab 100644
--- a/lib/src/modules/conditional.dart
+++ b/lib/src/modules/conditional.dart
@@ -351,6 +351,7 @@ class Sequential extends _Always {
   void _execute() {
     var anyClkInvalid = false;
     var anyClkPosedge = false;
+
     for (var i = 0; i < _clks.length; i++) {
       // if the pre-tick value is null, then it should have the same value as
       // it currently does
@@ -796,6 +797,11 @@ class ElseIf {
 
   /// If [condition] is 1, then [then] will be executed.
   ElseIf(this.condition, this.then);
+
+  /// If [condition] is 1, then [then] will be executed.
+  ///
+  /// Use this constructor when you only have a single [then] condition.
+  ElseIf.s(Logic condition, Conditional then) : this(condition, [then]);
 }
 
 /// A conditional block to execute only if `condition` is satisified.
@@ -928,6 +934,14 @@ class If extends Conditional {
   /// If [condition] is 1, then [then] executes, otherwise [orElse] is executed.
   If(this.condition, {this.then = const [], this.orElse = const []});
 
+  /// If [condition] is 1, then [then] is excutes,
+  /// otherwise [orElse] is executed.
+  ///
+  /// Use this constructor when you only have a single [then] condition.
+  /// An optional [orElse] condition can be passed.
+  If.s(Logic condition, Conditional then, [Conditional? orElse])
+      : this(condition, then: [then], orElse: orElse == null ? [] : [orElse]);
+
   @override
   List<Logic> getReceivers() {
     final allReceivers = <Logic>[];
diff --git a/lib/src/utilities/synchronous_propagator.dart b/lib/src/utilities/synchronous_propagator.dart
index b5b2a251..84425854 100644
--- a/lib/src/utilities/synchronous_propagator.dart
+++ b/lib/src/utilities/synchronous_propagator.dart
@@ -8,6 +8,8 @@
 /// Author: Max Korbel <max.korbel@intel.com>
 ///
 
+/***/
+
 /// A controller for a [SynchronousEmitter] that allows for
 /// adding of events of type [T] to be emitted.
 class SynchronousPropagator<T> {
@@ -51,4 +53,14 @@ class SynchronousEmitter<T> {
     }
     _isEmitting = false;
   }
+
+  /// Tells this emitter to adopt all behavior of [other].
+  ///
+  /// Tells this emitter to perform all the actions of [other] each
+  /// time this would propagate.  Also clears all actions from [other]
+  /// so that it will not execute anything in the future.
+  void adopt(SynchronousEmitter<T> other) {
+    _actions.addAll(other._actions);
+    other._actions.clear();
+  }
 }
diff --git a/lib/src/values/big_logic_value.dart b/lib/src/values/big_logic_value.dart
index 1984e0ca..2e89dc3a 100644
--- a/lib/src/values/big_logic_value.dart
+++ b/lib/src/values/big_logic_value.dart
@@ -10,6 +10,25 @@
 
 part of values;
 
+/// Extends [BigInt] with utility functions that are useful for dealing with
+/// large bit vectors and conversion between types.
+extension BigLogicValueBigIntUtilities on BigInt {
+  /// Returns this [BigInt] as an [int].
+  ///
+  /// Always interprets the number as unsigned, and thus never clamps to fit.
+  int toIntUnsigned(int width) {
+    if (width > LogicValue._INT_BITS) {
+      throw Exception('Cannot convert to BigInt when width $width'
+          ' is greater than ${LogicValue._INT_BITS}');
+    } else if (width == LogicValue._INT_BITS) {
+      // With `0x` in front of a hex literal, will be interpreted as unsigned.
+      return int.parse('0x${toRadixString(16)}');
+    } else {
+      return toInt();
+    }
+  }
+}
+
 /// A [LogicValue] whose number of bits is greater than the size of an [int].
 ///
 /// The implementation is similar to [_SmallLogicValue], except it uses
@@ -64,8 +83,9 @@ class _BigLogicValue extends LogicValue {
           (_invalid >> start) & _maskOfWidth(newWidth), newWidth);
     } else {
       return _SmallLogicValue(
-          ((_value >> start) & _maskOfWidth(newWidth)).toInt(),
-          ((_invalid >> start) & _maskOfWidth(newWidth)).toInt(),
+          ((_value >> start) & _maskOfWidth(newWidth)).toIntUnsigned(newWidth),
+          ((_invalid >> start) & _maskOfWidth(newWidth))
+              .toIntUnsigned(newWidth),
           newWidth);
     }
   }
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index b589800e..9d01be4e 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -79,7 +79,7 @@ abstract class LogicValue {
   /// [width] must be greater than or equal to 0.
   static LogicValue ofBigInt(BigInt value, int width) => width > _INT_BITS
       ? _BigLogicValue(value, BigInt.zero, width)
-      : _SmallLogicValue(value.toInt(), 0, width);
+      : _SmallLogicValue(value.toIntUnsigned(width), 0, width);
 
   /// Converts `BigInt` [value] to a valid [LogicValue] with [width]
   /// number of bits.
@@ -803,10 +803,7 @@ int _unsignedBinaryParse(String source) {
   if (val != null) {
     return val;
   } else {
-    final hex = BigInt.parse(source, radix: 2).toRadixString(16);
-
-    // With `0x` in front of a hex literal, it will be interpreted as unsigned.
-    return int.parse('0x$hex');
+    return BigInt.parse(source, radix: 2).toIntUnsigned(source.length);
   }
 }
 


Test patch:
diff --git a/test/changed_test.dart b/test/changed_test.dart
index 6ada837c..cd24965b 100644
--- a/test/changed_test.dart
+++ b/test/changed_test.dart
@@ -112,4 +112,61 @@ void main() {
 
     expect(qHadPosedge, equals(true));
   });
+
+  test('reconnected signal still hits changed events', () async {
+    final a = Logic(name: 'a');
+    final b = Logic(name: 'b');
+
+    var detectedAChanged = false;
+    a.changed.listen((event) {
+      detectedAChanged = true;
+    });
+
+    a <= b;
+
+    Simulator.registerAction(100, () {
+      b.put(1);
+    });
+
+    await Simulator.run();
+
+    expect(detectedAChanged, isTrue);
+  });
+
+  test('chain of reconnected signals still changes', () async {
+    final a = Logic(name: 'a');
+    final b = Logic(name: 'b');
+    final c = Logic(name: 'c');
+
+    var detectedAChanged = false;
+    a.changed.listen((event) {
+      detectedAChanged = true;
+    });
+
+    a <= b;
+    b <= c;
+
+    Simulator.registerAction(100, () {
+      c.put(1);
+    });
+
+    await Simulator.run();
+
+    expect(detectedAChanged, isTrue);
+  });
+
+  test('chain of reconnected signals still glitches', () async {
+    final a = Logic(name: 'a');
+    final b = Logic(name: 'b');
+    final c = Logic(name: 'c');
+
+    a.put(0);
+
+    a <= b;
+    b <= c;
+
+    c.put(1);
+
+    expect(a.value, equals(LogicValue.one));
+  });
 }
diff --git a/test/conditionals_test.dart b/test/conditionals_test.dart
index d8e11953..e53c113a 100644
--- a/test/conditionals_test.dart
+++ b/test/conditionals_test.dart
@@ -80,6 +80,19 @@ class IfBlockModule extends Module {
   }
 }
 
+class SingleIfBlockModule extends Module {
+  SingleIfBlockModule(Logic a) : super(name: 'singleifblockmodule') {
+    a = addInput('a', a);
+    final c = addOutput('c');
+
+    Combinational([
+      IfBlock([
+        Iff.s(a, c < 1),
+      ])
+    ]);
+  }
+}
+
 class ElseIfBlockModule extends Module {
   ElseIfBlockModule(Logic a, Logic b) : super(name: 'ifblockmodule') {
     a = addInput('a', a);
@@ -97,6 +110,21 @@ class ElseIfBlockModule extends Module {
   }
 }
 
+class SingleElseIfBlockModule extends Module {
+  SingleElseIfBlockModule(Logic a) : super(name: 'singleifblockmodule') {
+    a = addInput('a', a);
+    final c = addOutput('c');
+    final d = addOutput('d');
+
+    Combinational([
+      IfBlock([
+        ElseIf.s(a, c < 1),
+        Else([c < 0, d < 1])
+      ])
+    ]);
+  }
+}
+
 class CombModule extends Module {
   CombModule(Logic a, Logic b, Logic d) : super(name: 'combmodule') {
     a = addInput('a', a);
@@ -159,6 +187,36 @@ class SequentialModule extends Module {
   }
 }
 
+class SingleIfModule extends Module {
+  SingleIfModule(Logic a) : super(name: 'combmodule') {
+    a = addInput('a', a);
+
+    final q = addOutput('q');
+
+    Combinational(
+      [
+        If.s(a, q < 1),
+      ],
+    );
+  }
+}
+
+class SingleIfOrElseModule extends Module {
+  SingleIfOrElseModule(Logic a, Logic b) : super(name: 'combmodule') {
+    a = addInput('a', a);
+    b = addInput('b', b);
+
+    final q = addOutput('q');
+    final x = addOutput('x');
+
+    Combinational(
+      [
+        If.s(a, q < 1, x < 1),
+      ],
+    );
+  }
+}
+
 void main() {
   tearDown(Simulator.reset);
 
@@ -205,6 +263,18 @@ void main() {
       expect(simResult, equals(true));
     });
 
+    test('single iffblock comb', () async {
+      final mod = SingleIfBlockModule(Logic());
+      await mod.build();
+      final vectors = [
+        Vector({'a': 1}, {'c': 1}),
+      ];
+      await SimCompare.checkFunctionalVector(mod, vectors);
+      final simResult = SimCompare.iverilogVector(
+          mod.generateSynth(), mod.runtimeType.toString(), vectors);
+      expect(simResult, equals(true));
+    });
+
     test('elseifblock comb', () async {
       final mod = ElseIfBlockModule(Logic(), Logic());
       await mod.build();
@@ -220,6 +290,19 @@ void main() {
       expect(simResult, equals(true));
     });
 
+    test('single elseifblock comb', () async {
+      final mod = SingleElseIfBlockModule(Logic());
+      await mod.build();
+      final vectors = [
+        Vector({'a': 1}, {'c': 1}),
+        Vector({'a': 0}, {'c': 0, 'd': 1}),
+      ];
+      await SimCompare.checkFunctionalVector(mod, vectors);
+      final simResult = SimCompare.iverilogVector(
+          mod.generateSynth(), mod.runtimeType.toString(), vectors);
+      expect(simResult, equals(true));
+    });
+
     test('case comb', () async {
       final mod = CaseModule(Logic(), Logic());
       await mod.build();
@@ -252,5 +335,36 @@ void main() {
           signalToWidthMap: {'d': 8, 'q': 8});
       expect(simResult, equals(true));
     });
+
+    test(
+        'should return true on simcompare when '
+        'execute if.s() for single if...else conditional without orElse.',
+        () async {
+      final mod = SingleIfModule(Logic());
+      await mod.build();
+      final vectors = [
+        Vector({'a': 1}, {'q': 1}),
+      ];
+      await SimCompare.checkFunctionalVector(mod, vectors);
+      final simResult = SimCompare.iverilogVector(
+          mod.generateSynth(), mod.runtimeType.toString(), vectors);
+      expect(simResult, equals(true));
+    });
+
+    test(
+        'should return true on simcompare when '
+        'execute if.s() for single if...else conditional with orElse.',
+        () async {
+      final mod = SingleIfOrElseModule(Logic(), Logic());
+      await mod.build();
+      final vectors = [
+        Vector({'a': 1}, {'q': 1}),
+        Vector({'a': 0}, {'x': 1}),
+      ];
+      await SimCompare.checkFunctionalVector(mod, vectors);
+      final simResult = SimCompare.iverilogVector(
+          mod.generateSynth(), mod.runtimeType.toString(), vectors);
+      expect(simResult, equals(true));
+    });
   });
 }
diff --git a/test/counter_test.dart b/test/counter_test.dart
index e9bb9f5d..b48074fa 100644
--- a/test/counter_test.dart
+++ b/test/counter_test.dart
@@ -7,10 +7,8 @@
 /// 2021 May 10
 /// Author: Max Korbel <max.korbel@intel.com>
 ///
-import 'dart:async';
-
-// import 'dart:io';
 
+import 'dart:async';
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/utilities/simcompare.dart';
 import 'package:test/test.dart';
@@ -50,13 +48,9 @@ void main() {
       final counter = Counter(Logic(), reset);
       await counter.build();
       // WaveDumper(counter);
-      // File('tmp_counter.sv').writeAsStringSync(counter.generateSynth());
 
-      // check that 1 timestep after reset, the value has reset properly
       unawaited(reset.nextPosedge
-          .then((value) => Simulator.registerAction(Simulator.time + 1, () {
-                expect(counter.val.value.toInt(), equals(0));
-              })));
+          .then((value) => expect(counter.val.value.toInt(), equals(0))));
 
       final vectors = [
         Vector({'en': 0, 'reset': 0}, {}),
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 23dec967..1047ba41 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -838,4 +838,27 @@ void main() {
           equals(LogicValue.x));
     });
   });
+
+  group('64-bit conversions', () {
+    test(
+        '64-bit LogicValues larger than maximum positive value on integer'
+        ' are properly converted when converted from BigInt', () {
+      final extraWide = LogicValue.ofBigInt(
+        BigInt.parse('f' * 16 + 'f0' * 8, radix: 16),
+        128,
+      );
+      final smaller = extraWide.getRange(0, 64);
+      expect(smaller.toInt(), equals(0xf0f0f0f0f0f0f0f0));
+    });
+    test(
+        '64-bit BigInts larger than max pos int value constructing'
+        ' a LogicValue is correct', () {
+      final bigInt64Lv =
+          LogicValue.ofBigInt(BigInt.parse('fa' * 8, radix: 16), 64);
+      expect(bigInt64Lv.toInt(), equals(0xfafafafafafafafa));
+    });
+    test('64-bit binary negatives are converted properly with bin', () {
+      expect(bin('1110' * 16), equals(0xeeeeeeeeeeeeeeee));
+    });
+  });
 }
diff --git a/test/long_chain_test.dart b/test/long_chain_test.dart
new file mode 100644
index 00000000..b1ee90f4
--- /dev/null
+++ b/test/long_chain_test.dart
@@ -0,0 +1,39 @@
+/// Copyright (C) 2022 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// long_chain_test.dart
+/// Tests with long chains of combinational logic.
+///
+/// 2022 November 8
+/// Author: Max Korbel <max.korbel@intel.com>
+///
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/modules/passthrough.dart';
+import 'package:test/test.dart';
+
+class LongChain extends Module {
+  final int length;
+
+  Logic get chainOut => output('chainOut');
+
+  LongChain(Logic chainIn, {this.length = 1000}) : super(name: 'longChain') {
+    chainIn = addInput('chainIn', chainIn);
+    var intermediate = chainIn;
+    for (var i = 0; i < length; i++) {
+      intermediate = ~Passthrough(intermediate).out;
+    }
+    addOutput('chainOut') <= intermediate;
+  }
+}
+
+void main() {
+  test('long chain of combinational logic and modules', () async {
+    final chainIn = Logic(name: 'chainIn');
+    final chain = LongChain(chainIn);
+    await chain.build();
+
+    chainIn.put(0);
+    expect(chain.chainOut.value.toInt(), equals(chain.length % 2));
+  });
+}