Repository: intel/rohd (dart)

Issue:
Create an extensive test suite for `LogicValue` and `LogicValues`.
Need special attention around negatives, 64-bit wide, and other corner cases related to the various implementations.

Pull request description:
…alues. Still some issues to resolve - see comments in file.

<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

<!-- Description of changes, and motivation for adding them. -->

## Related Issue(s)

<!-- If there are any issues related to this PR, please link to the issues here -->

## Testing

<!-- Please describe how you tested your changes -->

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible?  If yes, how so?

## Documentation

> Does the change require any updates to documentation?  If so, where?  Are they included?

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 63aafb87d..8f3eb7ad9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -23,6 +23,9 @@ You can access the discussions area here: https://github.com/intel/rohd/discussi
 ### StackOverflow
 StackOverflow.com is a great tool to ask questions and get answers from the community.  Use the `rohd` tag when asking your question so that others in the community who subscribe to that tag can find and answer your question more quickly!
 
+### Meetings in the ROHD Forum
+The [ROHD Forum](https://github.com/intel/rohd/wiki/ROHD-Forum) is a periodic virtual meeting for developers and users of ROHD that anyone can join.  Feel free to join the call!
+
 ## Getting Started
 
 ### Requirements
diff --git a/README.md b/README.md
index cf1133552..1228600a7 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Features of ROHD include:
 - Easy **IP integration** and **interfaces**; using an IP is as easy as an import.  Reduces tedious, redundant, and error prone aspects of integration
 - **Simple and fast build**, free of complex build systems and EDA vendor tools
 - Can use the excellent pub.dev **package manager** and all the packages it has to offer
-- Built-in event-based **fast simulator**, with **waveform dumper** to .vcd file format
+- Built-in event-based **fast simulator** with **4-value** (0, 1, X, and Z) support and a **waveform dumper** to .vcd file format
 - Conversion of modules to equivalent, human-readable, structurally similar **SystemVerilog** for integration or downstream tool consumption
 - **Run-time dynamic** module port definitions (numbers, names, widths, etc.) and internal module logic, including recursive module contents
 - Simple, free, **open source tool stack** without any headaches from library dependencies, file ordering, elaboration/analysis options, +defines, etc.
@@ -82,6 +82,8 @@ There are complete API docs available at https://intel.github.io/rohd/rohd/rohd-
 
 If you need some help, you can visit our [Discussions](https://github.com/intel/rohd/discussions) page.  This is a friendly place where you can ask questions, share ideas, or just discuss openly!  You could also head to [StackOverflow.com](https://stackoverflow.com/) (use the tag `rohd`) to ask questions or look for answers.
 
+You also may be interested to join the [ROHD Forum](https://github.com/intel/rohd/wiki/ROHD-Forum) periodic meetings with other users and developers in the ROHD community.  The meetings are open to anyone interested!
+
 Be sure to note the minimum Dart version required for ROHD specified in pubspec.yaml (at least 2.14.0).  If you're using the version of Dart that came with Flutter, it might be older than that.
 
 ## Package Managers for Hardware
diff --git a/lib/src/interface.dart b/lib/src/interface.dart
index 807fe276d..c7f46ac3e 100644
--- a/lib/src/interface.dart
+++ b/lib/src/interface.dart
@@ -61,23 +61,23 @@ class Interface<TagType> {
     uniquify = uniquify ?? (String original) => original;
 
     if (inputTags != null) {
-      getPorts(inputTags).forEach((port) {
+      for (var port in getPorts(inputTags)) {
         setPort(
             // ignore: invalid_use_of_protected_member
-            module.addInput(uniquify!(port.name), srcInterface.port(port.name),
+            module.addInput(uniquify(port.name), srcInterface.port(port.name),
                 width: port.width),
             portName: port.name);
-      });
+      }
     }
 
     if (outputTags != null) {
-      getPorts(outputTags).forEach((port) {
+      for (var port in getPorts(outputTags)) {
         // ignore: invalid_use_of_protected_member
-        var output = module.addOutput(uniquify!(port.name), width: port.width);
+        var output = module.addOutput(uniquify(port.name), width: port.width);
         port <= output;
         srcInterface.port(port.name) <= port;
         setPort(output, portName: port.name);
-      });
+      }
     }
   }
 
diff --git a/lib/src/modules/conditional.dart b/lib/src/modules/conditional.dart
index c3ad97bac..29b2431e0 100644
--- a/lib/src/modules/conditional.dart
+++ b/lib/src/modules/conditional.dart
@@ -308,10 +308,10 @@ abstract class Conditional {
   ) {
     _assignedReceiverToOutputMap = assignedReceiverToOutputMap;
     _assignedDriverToInputMap = assignedDriverToInputMap;
-    getConditionals().forEach((element) {
-      element._updateAssignmentMaps(
+    for (var conditional in getConditionals()) {
+      conditional._updateAssignmentMaps(
           assignedReceiverToOutputMap, assignedDriverToInputMap);
-    });
+    }
   }
 
   /// Updates the value of [_driverValueOverrideMap] and passes it down to all
@@ -319,9 +319,9 @@ abstract class Conditional {
   void _updateOverrideMap(Map<Logic, LogicValues> driverValueOverrideMap) {
     // this is for always_ff pre-tick values
     _driverValueOverrideMap = driverValueOverrideMap;
-    getConditionals().forEach((element) {
-      element._updateOverrideMap(driverValueOverrideMap);
-    });
+    for (var conditional in getConditionals()) {
+      conditional._updateOverrideMap(driverValueOverrideMap);
+    }
   }
 
   /// Gets the value that should be used for execution for the input port associated with [driver].
@@ -485,9 +485,9 @@ class Case extends Conditional {
     //TODO: what about for CaseZ where epxressions can have Z?  BUG?
     if (!expression.value.isValid) {
       // if expression has X or Z, then propogate X's!
-      getReceivers().forEach((receiver) {
+      for (var receiver in getReceivers()) {
         receiverOutput(receiver).put(LogicValue.x);
-      });
+      }
       return [];
     }
 
@@ -703,9 +703,9 @@ class IfBlock extends Conditional {
         break;
       } else if (driverValue(iff.condition)[0] != LogicValue.zero) {
         // x and z propagation
-        getReceivers().forEach((receiver) {
+        for (var receiver in getReceivers()) {
           receiverOutput(receiver).put(driverValue(iff.condition)[0]);
-        });
+        }
         break;
       }
       // if it's 0, then continue searching down the path
@@ -833,9 +833,9 @@ class If extends Conditional {
       }
     } else {
       // x and z propagation
-      getReceivers().forEach((receiver) {
+      for (var receiver in getReceivers()) {
         receiverOutput(receiver).put(driverValue(condition)[0]);
-      });
+      }
     }
     return drivenLogics;
   }
diff --git a/lib/src/utilities/simcompare.dart b/lib/src/utilities/simcompare.dart
index a23626fa3..c947f56bb 100644
--- a/lib/src/utilities/simcompare.dart
+++ b/lib/src/utilities/simcompare.dart
@@ -87,9 +87,9 @@ class SimCompare {
             expect(o.valueInt, equals(value), reason: errorReason);
           } else if (value is LogicValue &&
               (value == LogicValue.x || value == LogicValue.z)) {
-            o.value.toList().forEach((element) {
-              expect(element, equals(value), reason: errorReason);
-            });
+            for (var oBit in o.value.toList()) {
+              expect(oBit, equals(value), reason: errorReason);
+            }
           } else {
             throw Exception(
                 'Value type ${value.runtimeType} is not supported (yet?)');
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index 16239e833..644a37e13 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -24,6 +24,9 @@ class LogicValue {
   /// Logical value of `z`
   static const LogicValue z = LogicValue._(_LogicValueEnum.z);
 
+  /// Convert a bool to a one or zero
+  static LogicValue fromBool(bool v) => v ? one : zero;
+
   final _LogicValueEnum _value;
   const LogicValue._(this._value);
 
diff --git a/lib/src/values/logic_values.dart b/lib/src/values/logic_values.dart
index 5c81ab191..567647d52 100644
--- a/lib/src/values/logic_values.dart
+++ b/lib/src/values/logic_values.dart
@@ -557,6 +557,10 @@ abstract class LogicValues {
 
   const LogicValues._(this.length) : assert(length >= 0);
 
+  /// Converts `bool` [value] to a valid [LogicValues] with 1 bits either one or zero.
+  static LogicValues fromBool(bool value) =>
+      _SmallLogicValues(value ? 1 : 0, 0, 1);
+
   /// Converts `int` [value] to a valid [LogicValues] with [length] number of bits.
   ///
   /// [length] must be greater than or equal to 0.


Test patch:
diff --git a/test/logic_values_test.dart b/test/logic_values_test.dart
index 2196f2278..da3b63e6c 100644
--- a/test/logic_values_test.dart
+++ b/test/logic_values_test.dart
@@ -11,21 +11,757 @@
 import 'package:rohd/rohd.dart';
 import 'package:test/test.dart';
 
-//TODO: reversed looks broken
-//TODO: add a test with x & 0 == 0
+// All logicvalues to support trying all possiblities
+const allLv = [LogicValue.zero, LogicValue.one, LogicValue.x, LogicValue.z];
+
+// shorten some names to make tests read better
+final lv = LogicValues.fromString;
+LogicValues large(LogicValue lv) => LogicValues.filled(100, lv);
+
 void main() {
   group('two input bitwise', () {
     test('and2', () {
-      expect(LogicValues.fromString('01xz') & LogicValues.fromString('1111'),
-          equals(LogicValues.fromString('01xx')));
+      // test z & 1 == x, rest unchanged
+      expect(lv('01xz') & lv('1111'), equals(lv('01xx')));
+      // Large filled test of * & 1
+      for (final v in allLv) {
+        expect(large(v) & large(LogicValue.one),
+            equals(large(v & LogicValue.one)));
+      }
+      // Large logicValues test of &
+      expect(lv('01xz' * 100) & lv('1111' * 100), equals(lv('01xx' * 100)));
+      // test * & 0 = 0
+      expect(lv('01xz') & lv('0000'), equals(lv('0000')));
+      // try mixing .fromString with .filled
+      expect(lv('01xz') & LogicValues.filled(4, LogicValue.zero),
+          equals(LogicValues.filled(4, LogicValue.zero)));
+    });
+  });
+
+  group('LogicValues Misc', () {
+    test('reversed', () {
+      expect(lv('01xz').reversed, equals(lv('zx10')));
+      expect(lv('010').reversed, equals(lv('010')));
+      // reverse large values
+      expect(lv('01' * 100).reversed, equals(lv('10' * 100)));
+      expect(lv('01xz' * 100).reversed, equals(lv('zx10' * 100)));
+      // reverse filled
+      for (final v in allLv) {
+        expect(large(v).reversed, equals(large(v)));
+      }
+    });
+  });
+
+  group('logic value', () {
+    test('fromBool', () {
+      expect(LogicValue.fromBool(true), equals(LogicValue.one));
+      expect(LogicValue.fromBool(false), equals(LogicValue.zero));
+      expect(LogicValues.fromBool(true), equals(LogicValues.fromString('1')));
+      expect(LogicValues.fromBool(false), equals(LogicValues.fromString('0')));
+    });
+  });
+
+  group('LogicValue', () {
+    test('factory and to methods', () {
+      expect(LogicValue.one.toString(), equals('1'));
+      expect(LogicValue.zero.toString(), equals('0'));
+      expect(LogicValue.x.toString(), equals('x'));
+      expect(LogicValue.z.toString(), equals('z'));
+      expect(LogicValue.one.toBool(), equals(true));
+      expect(LogicValue.zero.toBool(), equals(false));
+      expect(() => LogicValue.x.toBool(), throwsA(isA<Exception>()));
+      expect(() => LogicValue.z.toBool(), throwsA(isA<Exception>()));
+      expect(LogicValue.one.toInt(), equals(1));
+      expect(LogicValue.zero.toInt(), equals(0));
+      expect(() => LogicValue.x.toInt(), throwsA(isA<Exception>()));
+      expect(() => LogicValue.z.toInt(), throwsA(isA<Exception>()));
+    });
+    test('unary', () {
+      expect(LogicValue.one.isValid, equals(true));
+      expect(LogicValue.zero.isValid, equals(true));
+      expect(LogicValue.x.isValid, equals(false));
+      expect(LogicValue.z.isValid, equals(false));
+      expect(~LogicValue.one, equals(LogicValue.zero));
+      expect(~LogicValue.zero, equals(LogicValue.one));
+      expect(~LogicValue.x, equals(LogicValue.x));
+      expect(~LogicValue.z, equals(LogicValue.x));
+    });
+    test('and', () {
+      expect(LogicValue.one & LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.one & LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.one & LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.one & LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.zero & LogicValue.one, equals(LogicValue.zero));
+      expect(LogicValue.zero & LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.zero & LogicValue.x, equals(LogicValue.zero));
+      expect(LogicValue.zero & LogicValue.z, equals(LogicValue.zero));
+      expect(LogicValue.x & LogicValue.one, equals(LogicValue.x));
+      expect(LogicValue.x & LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.x & LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.x & LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.z & LogicValue.one, equals(LogicValue.x));
+      expect(LogicValue.z & LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.z & LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.z & LogicValue.z, equals(LogicValue.x));
+    });
+    test('or', () {
+      expect(LogicValue.one | LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.one | LogicValue.zero, equals(LogicValue.one));
+      expect(LogicValue.one | LogicValue.x, equals(LogicValue.one));
+      expect(LogicValue.one | LogicValue.z, equals(LogicValue.one));
+      expect(LogicValue.zero | LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.zero | LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.zero | LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.zero | LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.x | LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.x | LogicValue.zero, equals(LogicValue.x));
+      expect(LogicValue.x | LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.x | LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.z | LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.z | LogicValue.zero, equals(LogicValue.x));
+      expect(LogicValue.z | LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.z | LogicValue.z, equals(LogicValue.x));
+    });
+    test('xor', () {
+      expect(LogicValue.one ^ LogicValue.one, equals(LogicValue.zero));
+      expect(LogicValue.one ^ LogicValue.zero, equals(LogicValue.one));
+      expect(LogicValue.one ^ LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.one ^ LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.zero ^ LogicValue.one, equals(LogicValue.one));
+      expect(LogicValue.zero ^ LogicValue.zero, equals(LogicValue.zero));
+      expect(LogicValue.zero ^ LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.zero ^ LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.x ^ LogicValue.one, equals(LogicValue.x));
+      expect(LogicValue.x ^ LogicValue.zero, equals(LogicValue.x));
+      expect(LogicValue.x ^ LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.x ^ LogicValue.z, equals(LogicValue.x));
+      expect(LogicValue.z ^ LogicValue.one, equals(LogicValue.x));
+      expect(LogicValue.z ^ LogicValue.zero, equals(LogicValue.x));
+      expect(LogicValue.z ^ LogicValue.x, equals(LogicValue.x));
+      expect(LogicValue.z ^ LogicValue.z, equals(LogicValue.x));
+    });
+    test('==', () {
+      expect(LogicValue.one == LogicValue.one, equals(true));
+      expect(LogicValue.one == LogicValue.zero, equals(false));
+      expect(LogicValue.one == LogicValue.x, equals(false));
+      expect(LogicValue.one == LogicValue.z, equals(false));
+      expect(LogicValue.zero == LogicValue.one, equals(false));
+      expect(LogicValue.zero == LogicValue.zero, equals(true));
+      expect(LogicValue.zero == LogicValue.x, equals(false));
+      expect(LogicValue.zero == LogicValue.z, equals(false));
+      expect(LogicValue.x == LogicValue.one, equals(false));
+      expect(LogicValue.x == LogicValue.zero, equals(false));
+      expect(LogicValue.x == LogicValue.x, equals(true));
+      expect(LogicValue.x == LogicValue.z, equals(false));
+      expect(LogicValue.z == LogicValue.one, equals(false));
+      expect(LogicValue.z == LogicValue.zero, equals(false));
+      expect(LogicValue.z == LogicValue.x, equals(false));
+      expect(LogicValue.z == LogicValue.z, equals(true));
+    });
+    test('isPosEdge', () {
+      expect(
+          LogicValue.isPosedge(LogicValue.one, LogicValue.zero), equals(false));
+      expect(
+          LogicValue.isPosedge(LogicValue.zero, LogicValue.one), equals(true));
+      expect(
+          LogicValue.isPosedge(LogicValue.x, LogicValue.one,
+              ignoreInvalid: true),
+          equals(false));
+      expect(
+          LogicValue.isPosedge(LogicValue.one, LogicValue.z,
+              ignoreInvalid: true),
+          equals(false));
+      expect(
+          () => LogicValue.isPosedge(LogicValue.x, LogicValue.one,
+              ignoreInvalid: false),
+          throwsA(isA<Exception>()));
+      expect(
+          () => LogicValue.isPosedge(LogicValue.one, LogicValue.z,
+              ignoreInvalid: false),
+          throwsA(isA<Exception>()));
+    });
+    test('isNegEdge', () {
       expect(
+          LogicValue.isNegedge(LogicValue.one, LogicValue.zero), equals(true));
+      expect(
+          LogicValue.isNegedge(LogicValue.zero, LogicValue.one), equals(false));
+      expect(
+          LogicValue.isNegedge(LogicValue.x, LogicValue.one,
+              ignoreInvalid: true),
+          equals(false));
+      expect(
+          LogicValue.isNegedge(LogicValue.one, LogicValue.z,
+              ignoreInvalid: true),
+          equals(false));
+      expect(
+          () => LogicValue.isNegedge(LogicValue.x, LogicValue.one,
+              ignoreInvalid: false),
+          throwsA(isA<Exception>()));
+      expect(
+          () => LogicValue.isNegedge(LogicValue.one, LogicValue.z,
+              ignoreInvalid: false),
+          throwsA(isA<Exception>()));
+    });
+  });
+
+  group('two input bitwise', () {
+    test('and2', () {
+      expect(
+          // test all possible combinations (and fromString)
+          LogicValues.fromString('00001111xxxxzzzz') &
+              LogicValues.fromString('01xz01xz01xz01xz'),
+          equals(LogicValues.fromString('000001xx0xxx0xxx')));
+      expect(
+          // test filled
           LogicValues.filled(100, LogicValue.zero) &
               LogicValues.filled(100, LogicValue.one),
           equals(LogicValues.filled(100, LogicValue.zero)));
       expect(
-          LogicValues.fromString('01xz' * 100) &
-              LogicValues.fromString('1111' * 100),
-          equals(LogicValues.fromString('01xx' * 100)));
+          // test length mismatch
+          () => LogicValues.fromString('0') & LogicValues.fromString('01'),
+          throwsA(isA<Exception>()));
+    });
+
+    test('or2', () {
+      expect(
+          // test all possible combinations
+          LogicValues.fromString('00001111xxxxzzzz') |
+              LogicValues.fromString('01xz01xz01xz01xz'),
+          equals(LogicValues.fromString('01xx1111x1xxx1xx')));
+      expect(
+          // test fromInt
+          LogicValues.fromInt(1, 32) | LogicValues.fromInt(0, 32),
+          equals(LogicValues.fromInt(1, 32)));
+      expect(
+          // test fromBigInt - success
+          LogicValues.fromBigInt(BigInt.one, 65) |
+              LogicValues.fromBigInt(BigInt.zero, 65),
+          equals(LogicValues.fromBigInt(BigInt.one, 65)));
+      expect(
+          // test fromBigInt
+          () =>
+              LogicValues.fromBigInt(BigInt.one, 32) |
+              LogicValues.fromBigInt(BigInt.zero, 32),
+          throwsA(isA<AssertionError>()));
+    });
+
+    test('xor2', () {
+      expect(
+          // test all possible combinations
+          LogicValues.fromString('00001111xxxxzzzz') ^
+              LogicValues.fromString('01xz01xz01xz01xz'),
+          equals(LogicValues.fromString('01xx10xxxxxxxxxx')));
+      expect(
+          // test from Iterable
+          LogicValues.from([LogicValue.one, LogicValue.zero]) ^
+              LogicValues.from([LogicValue.one, LogicValue.zero]),
+          equals(LogicValues.from([LogicValue.zero, LogicValue.zero])));
+    });
+  });
+  group('unary operations (including "to")', () {
+    test('toMethods', () {
+      expect(
+          // toString
+          LogicValues.fromString('0').toString(),
+          equals('1\'b0'));
+      expect(
+          // toList
+          LogicValues.fromString('0101').toList(),
+          equals([
+            LogicValue.one,
+            LogicValue.zero,
+            LogicValue.one,
+            LogicValue.zero
+          ]) // NOTE: "reversed" by construction (see function definition)
+          );
+      expect(
+          // toInt - valid
+          LogicValues.fromString('111').toInt(),
+          equals(7));
+      expect(
+          // toInt - invalid
+          () => LogicValues.filled(65, LogicValue.one).toInt(),
+          throwsA(isA<Exception>()));
+      expect(
+          // toBigInt - valid
+          LogicValues.filled(65, LogicValue.one).toBigInt(),
+          equals(BigInt.parse('36893488147419103231')));
+    });
+
+    test('properties+indexing', () {
+      expect(
+          // index - LSb
+          LogicValues.fromString('0101')[0],
+          equals(LogicValue.one) // NOTE: index 0 refers to LSb
+          );
+      expect(
+          // index - MSb
+          LogicValues.fromString('0101')[3],
+          equals(LogicValue.zero) // NOTE: index (length-1) refers to MSb
+          );
+      expect(
+          // index - out of range
+          () => LogicValues.fromString('0101')[10],
+          throwsA(isA<IndexError>()));
+      expect(
+          // index - negative
+          () => LogicValues.fromString('0101')[-1],
+          throwsA(isA<IndexError>()));
+      expect(
+          // reversed
+          LogicValues.fromString('0101').reversed,
+          equals(LogicValues.fromString('1010')));
+      expect(
+          // getRange - good inputs
+          LogicValues.fromString('0101').getRange(0, 2),
+          equals(LogicValues.fromString('01')));
+      expect(
+          // getRange - bad inputs start < 0
+          () => LogicValues.fromString('0101').getRange(-2, 1),
+          throwsA(isA<Exception>()));
+      expect(
+          // getRange - bad inputs start > end
+          () => LogicValues.fromString('0101').getRange(2, 1),
+          throwsA(isA<Exception>()));
+      expect(
+          // getRange - bad inputs end > length-1
+          () => LogicValues.fromString('0101').getRange(0, 7),
+          throwsA(isA<Exception>()));
+      expect(
+          // isValid - valid
+          LogicValues.fromString('0101').isValid,
+          equals(true));
+      expect(
+          // isValid - invalid ('x')
+          LogicValues.fromString('01x1').isValid,
+          equals(false));
+      expect(
+          // isValid - invalid ('z')
+          LogicValues.fromString('01z1').isValid,
+          equals(false));
+      expect(
+          // isFloating - floating
+          LogicValues.fromString('zzzz').isFloating,
+          equals(true));
+      expect(
+          // isFloating - not floating
+          LogicValues.fromString('zzz1').isFloating,
+          equals(false));
+    });
+
+    test('shifts', () {
+      expect(
+          // sll
+          LogicValues.fromString('1111') << 2,
+          equals(LogicValues.fromString('1100')));
+      expect(
+          // sra
+          LogicValues.fromString('1111') >> 2,
+          equals(LogicValues.fromString('1111')));
+      expect(
+          // srl
+          LogicValues.fromString('1111') >>> 2,
+          equals(LogicValues.fromString('0011')));
+    });
+  });
+  group('comparison operations', () {
+    test('equality', () {
+      expect(
+          // == equal
+          LogicValues.fromString('1111') == LogicValues.fromString('1111'),
+          equals(true));
+      expect(
+          // == not equal
+          LogicValues.fromString('1111') == LogicValues.fromString('1110'),
+          equals(false));
+      expect(
+          // eq equal
+          LogicValues.fromString('1111').eq(LogicValues.fromString('1111')),
+          equals(LogicValue.one));
+      expect(
+          // eq not equal, valid
+          LogicValues.fromString('1111').eq(LogicValues.fromString('1110')),
+          equals(LogicValue.zero));
+      expect(
+          // eq not equal, invalid
+          LogicValues.fromString('1111').eq(LogicValues.fromString('111x')),
+          equals(LogicValue.x));
+    });
+
+    test('greater', () {
+      expect(
+          // >
+          LogicValues.fromString('0111') > LogicValues.fromString('0110'),
+          equals(LogicValue.one));
+      expect(
+          // not >
+          LogicValues.fromString('0111') > LogicValues.fromString('0111'),
+          equals(LogicValue.zero));
+      expect(
+          // >=
+          LogicValues.fromString('0111') >= LogicValues.fromString('0111'),
+          equals(LogicValue.one));
+      expect(
+          // not >=
+          LogicValues.fromString('0110') >= LogicValues.fromString('0111'),
+          equals(LogicValue.zero));
+      expect(
+          // x involved
+          LogicValues.fromString('0110') >= LogicValues.fromString('011x'),
+          equals(LogicValue.x));
+      expect(
+          // mismatched lengths
+          () =>
+              LogicValues.fromString('0110') >=
+              LogicValues.fromString('011000'),
+          throwsA(isA<Exception>()));
+    });
+
+    test('less', () {
+      expect(
+          // <
+          LogicValues.fromString('0111') < 8,
+          equals(LogicValue.one));
+      expect(
+          // not <
+          LogicValues.fromString('0111') < 7,
+          equals(LogicValue.zero));
+      expect(
+          // <=
+          LogicValues.fromString('0111') <= 7,
+          equals(LogicValue.one));
+      expect(
+          // not <=
+          LogicValues.fromString('0110') <= 5,
+          equals(LogicValue.zero));
+      expect(
+          // x involved
+          LogicValues.fromString('011x') <= 10,
+          equals(LogicValue.x));
+    });
+  });
+  group('arithmetic operations', () {
+    test('addsub', () {
+      expect(
+          // + normal
+          LogicValues.fromString('0001') + LogicValues.fromString('0011'),
+          equals(LogicValues.fromString('0100')) // 1 + 3 = 4
+          );
+      expect(
+          // - normal
+          LogicValues.fromString('0001') - LogicValues.fromString('0001'),
+          equals(LogicValues.fromString('0000')) // 1 - 1 = 0
+          );
+      expect(
+          // + overflow
+          LogicValues.fromString('1111') + LogicValues.fromString('0001'),
+          equals(LogicValues.fromString('0000')));
+      expect(
+          // - overflow
+          LogicValues.fromString('0000') - LogicValues.fromString('0001'),
+          equals(LogicValues.fromString('1111')));
+      expect(
+          // x involved
+          LogicValues.fromString('0000') + LogicValues.fromString('111x'),
+          equals(LogicValues.fromString('xxxx')));
+      expect(
+          // length mismatch
+          () =>
+              LogicValues.fromString('0000') - LogicValues.fromString('000100'),
+          throwsA(isA<Exception>()));
+    });
+    test('muldiv', () {
+      expect(
+          // * normal
+          LogicValues.fromString('0001') * LogicValues.fromString('0011'),
+          equals(LogicValues.fromString('0011')) // 1 * 3 = 3
+          );
+      expect(
+          // / normal
+          LogicValues.fromString('0100') / LogicValues.fromString('0010'),
+          equals(LogicValues.fromString('0010')) // 4 / 2 = 2
+          );
+      expect(
+          // / truncate
+          LogicValues.fromString('0100') / LogicValues.fromString('0011'),
+          equals(LogicValues.fromString('0001')) // 4 / 3 = 1 (integer division)
+          );
+      expect(
+          // div-by-0
+          () => LogicValues.fromString('0100') / LogicValues.fromString('0000'),
+          throwsA(isA<Exception>()));
+      expect(
+          // * overflow
+          LogicValues.fromString('0100') * LogicValues.fromString('0100'),
+          equals(LogicValues.fromString('0000')));
+    });
+  });
+
+  group('not and reductions', () {
+    test('not', () {
+      expect(
+          // not - valid
+          ~LogicValues.fromString('0100'),
+          equals(LogicValues.fromString('1011')));
+      expect(
+          // not - invalid
+          ~LogicValues.fromString('zzxx'),
+          equals(LogicValues.fromString('xxxx')));
+    });
+    test('and', () {
+      expect(
+          // and - valid
+          LogicValues.fromString('0100').and(),
+          equals(LogicValue.zero));
+      expect(
+          // and - valid (1's)
+          LogicValues.fromString('1111').and(),
+          equals(LogicValue.one));
+      expect(
+          // and - invalid
+          LogicValues.fromString('010x').and(),
+          equals(LogicValue.zero));
+      expect(
+          // and - invalid (1's)
+          LogicValues.fromString('111z').and(),
+          equals(LogicValue.x));
+    });
+    test('or', () {
+      expect(
+          // or - valid
+          LogicValues.fromString('0100').or(),
+          equals(LogicValue.one));
+      expect(
+          // or - valid (0's)
+          LogicValues.fromString('0000').or(),
+          equals(LogicValue.zero));
+      expect(
+          // or - invalid
+          LogicValues.fromString('010x').or(),
+          equals(LogicValue.one));
+      expect(
+          // or - invalid (1's)
+          LogicValues.fromString('000z').or(),
+          equals(LogicValue.x));
+    });
+    test('xor', () {
+      expect(
+          // xor - valid (even)
+          LogicValues.fromString('1100').xor(),
+          equals(LogicValue.zero));
+      expect(
+          // xor - valid (odd)
+          LogicValues.fromString('1110').xor(),
+          equals(LogicValue.one));
+      expect(
+          // xor - invalid
+          LogicValues.fromString('010x').xor(),
+          equals(LogicValue.x));
+    });
+  });
+  group('BigLogicValues', () {
+    test('overrides', () {
+      expect(
+          // reversed
+          LogicValues.fromString('01' * 100).reversed,
+          equals(LogicValues.fromString('10' * 100)));
+      expect(
+          // isValid - valid
+          LogicValues.fromString('01' * 100).isValid,
+          equals(true));
+      expect(
+          // isValid - invalid ('x')
+          LogicValues.fromString('0x' * 100).isValid,
+          equals(false));
+      expect(
+          // isValid - invalid ('z')
+          LogicValues.fromString('1z' * 100).isValid,
+          equals(false));
+      expect(
+          // isFloating - floating
+          LogicValues.fromString('z' * 100).isFloating,
+          equals(true));
+      expect(
+          // isFloating - not floating
+          LogicValues.fromString('z1' * 100).isFloating,
+          equals(false));
+      expect(
+          // toInt - always invalid
+          () => LogicValues.fromString('11' * 100).toInt(),
+          throwsA(isA<Exception>()));
+      expect(
+          // toBigInt - invalid
+          () => LogicValues.fromString('1x' * 100).toBigInt(),
+          throwsA(isA<Exception>()));
+      expect(
+          // toBigInt - valid
+          LogicValues.fromString('0' * 100).toBigInt(),
+          equals(BigInt.from(0)));
+      expect(
+          // not - valid
+          ~LogicValues.fromString('0' * 100),
+          equals(LogicValues.fromString('1' * 100)));
+      expect(
+          // not - invalid
+          ~LogicValues.fromString('z1' * 100),
+          equals(LogicValues.fromString('x0' * 100)));
+      expect(
+          // and - valid
+          LogicValues.fromString('01' * 100).and(),
+          equals(LogicValue.zero));
+      expect(
+          // and - valid (1's)
+          LogicValues.fromString('1' * 100).and(),
+          equals(LogicValue.one));
+      expect(
+          // and - invalid
+          LogicValues.fromString('01x' * 100).and(),
+          equals(LogicValue.zero));
+      expect(
+          // and - invalid (1's)
+          LogicValues.fromString('111z' * 100).and(),
+          equals(LogicValue.x));
+      expect(
+          // or - valid
+          LogicValues.fromString('01' * 100).or(),
+          equals(LogicValue.one));
+      expect(
+          // or - valid (0's)
+          LogicValues.fromString('0' * 100).or(),
+          equals(LogicValue.zero));
+      expect(
+          // or - invalid
+          LogicValues.fromString('10x' * 100).or(),
+          equals(LogicValue.one));
+      expect(
+          // or - invalid (1's)
+          LogicValues.fromString('0z' * 100).or(),
+          equals(LogicValue.x));
+      expect(
+          // xor - valid (even)
+          LogicValues.fromString('1100' * 100).xor(),
+          equals(LogicValue.zero));
+      expect(
+          // xor - valid (odd)
+          LogicValues.fromString('1110' * 99).xor(),
+          equals(LogicValue.one));
+      expect(
+          // xor - invalid
+          LogicValues.fromString('010x' * 100).xor(),
+          equals(LogicValue.x));
+    });
+  });
+
+  group('FilledLogicValues', () {
+    test('overrides', () {
+      expect(
+          // reversed
+          LogicValues.filled(100, LogicValue.one).reversed,
+          equals(LogicValues.filled(100, LogicValue.one)));
+      expect(
+          // reversed
+          LogicValues.filled(100, LogicValue.zero).reversed,
+          equals(LogicValues.filled(100, LogicValue.zero)));
+      expect(
+          // isValid - valid
+          LogicValues.filled(100, LogicValue.zero).isValid,
+          equals(true));
+      expect(
+          // isValid - valid
+          LogicValues.filled(100, LogicValue.one).isValid,
+          equals(true));
+      expect(
+          // isValid - invalid ('x')
+          LogicValues.filled(100, LogicValue.x).isValid,
+          equals(false));
+      expect(
+          // isValid - invalid ('z')
+          LogicValues.filled(100, LogicValue.z).isValid,
+          equals(false));
+      expect(
+          // isFloating - floating
+          LogicValues.filled(100, LogicValue.z).isFloating,
+          equals(true));
+      expect(
+          // isFloating - not floating
+          LogicValues.filled(100, LogicValue.one).isFloating,
+          equals(false));
+      expect(
+          // toInt - invalid
+          () => LogicValues.filled(100, LogicValue.one).toInt(),
+          throwsA(isA<Exception>()));
+      expect(
+          // toInt - valid
+          LogicValues.filled(64, LogicValue.zero).toInt(),
+          equals(0));
+      expect(
+          // toBigInt - invalid
+          () => LogicValues.filled(100, LogicValue.x).toBigInt(),
+          throwsA(isA<Exception>()));
+      expect(
+          // toBigInt - valid
+          LogicValues.filled(100, LogicValue.zero).toBigInt(),
+          equals(BigInt.from(0)));
+      expect(
+          // not - valid
+          ~LogicValues.filled(100, LogicValue.zero),
+          equals(LogicValues.filled(100, LogicValue.one)));
+      expect(
+          // not - valid
+          ~LogicValues.filled(100, LogicValue.one),
+          equals(LogicValues.filled(100, LogicValue.zero)));
+      expect(
+          // not - invalid
+          ~LogicValues.filled(100, LogicValue.z),
+          equals(LogicValues.filled(100, LogicValue.x)));
+      expect(
+          // and - valid 0
+          LogicValues.filled(100, LogicValue.zero).and(),
+          equals(LogicValue.zero));
+      expect(
+          // and - valid 1
+          LogicValues.filled(100, LogicValue.one).and(),
+          equals(LogicValue.one));
+      expect(
+          // and - invalid x
+          LogicValues.filled(100, LogicValue.x).and(),
+          equals(LogicValue.x));
+      expect(
+          // and - invalid z
+          LogicValues.filled(100, LogicValue.z).and(),
+          equals(LogicValue.x));
+      expect(
+          // or - valid 0
+          LogicValues.filled(100, LogicValue.zero).and(),
+          equals(LogicValue.zero));
+      expect(
+          // or - valid 1
+          LogicValues.filled(100, LogicValue.one).and(),
+          equals(LogicValue.one));
+      expect(
+          // or - invalid x
+          LogicValues.filled(100, LogicValue.x).and(),
+          equals(LogicValue.x));
+      expect(
+          // or - invalid z
+          LogicValues.filled(100, LogicValue.z).and(),
+          equals(LogicValue.x));
+      expect(
+          // xor - valid 0
+          LogicValues.filled(100, LogicValue.zero).and(),
+          equals(LogicValue.zero));
+      expect(
+          // xor - valid 1
+          LogicValues.filled(99, LogicValue.one).and(),
+          equals(LogicValue.one));
+      expect(
+          // xor - invalid x
+          LogicValues.filled(100, LogicValue.x).and(),
+          equals(LogicValue.x));
+      expect(
+          // xor - invalid z
+          LogicValues.filled(100, LogicValue.z).and(),
+          equals(LogicValue.x));
     });
   });
 }