Repository: intel/rohd (dart)

Issue:
Implement modulus operator %
Across applicable areas (gates, `Logic`, `LogicValues`, etc.)

Pull request:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

Add modulo support

## Related Issue(s)

[<!-- If there are any issues related to this PR, please link to the issues here -->](https://github.com/intel/rohd/issues/16)

## 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?

NO

## Documentation

> Does the change require any updates to documentation?  If so, where?  Are they included?

Yes

Patch:
diff --git a/README.md b/README.md
index 635f0954a..324c61188 100644
--- a/README.md
+++ b/README.md
@@ -237,6 +237,7 @@ a_plus_b  <=  a + b;   // addition
 a_sub_b   <=  a - b;   // subtraction
 a_times_b <=  a * b;   // multiplication
 a_div_b   <=  a / b;   // division
+a_mod_b   <=  a % b;   // modulo
 a_eq_b    <=  a.eq(b)  // equality              NOTE: == is for Object equality of Logic's
 a_lt_b    <=  a.lt(b)  // less than             NOTE: <  is for conditional assignment
 a_lte_b   <=  a.lte(b) // less than or equal    NOTE: <= is for assignment
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index 053f9d84e..31ec5552b 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -287,6 +287,9 @@ class Logic {
   /// WARNING: Signed math is not fully tested.
   Logic operator /(dynamic other) => Divide(this, other).y;
 
+  /// Modulo operation.
+  Logic operator %(Logic other) => Modulo(this, other).y;
+
   /// Arithmetic right-shift.
   Logic operator >>(Logic other) => ARShift(this, other).y;
 
diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart
index 28202d63d..2b01a8b8e 100644
--- a/lib/src/modules/gates.dart
+++ b/lib/src/modules/gates.dart
@@ -360,6 +360,12 @@ class Divide extends _TwoInputBitwiseGate {
       : super((a, b) => a / b, '/', a, b, name: name);
 }
 
+/// A two-input modulo module.
+class Modulo extends _TwoInputBitwiseGate {
+  Modulo(Logic a, dynamic b, {String name = 'modulo'})
+      : super((a, b) => a % b, '%', a, b, name: name);
+}
+
 /// A two-input equality comparison module.
 class Equals extends _TwoInputComparisonGate {
   Equals(Logic a, dynamic b, {String name = 'equals'})
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index d4e005a8d..917122f8a 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -437,6 +437,10 @@ abstract class LogicValue {
   /// WARNING: Signed math is not fully tested.
   LogicValue operator /(dynamic other) => _doMath(other, (a, b) => a ~/ b);
 
+  /// Modulo operation.
+  ///
+  LogicValue operator %(dynamic other) => _doMath(other, (a, b) => a % b);
+
   /// Executes mathematical operations between two [LogicValue]s
   ///
   /// Handles width and bounds checks as well as proper conversion between different types of representation.


Test patch:
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 5105514ff..8d70931d8 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -492,7 +492,32 @@ void main() {
           // length mismatch
           () => LogicValue.ofString('0000') - LogicValue.ofString('000100'),
           throwsA(isA<Exception>()));
+
+      expect(
+          // % normal
+          LogicValue.ofString('0001') % LogicValue.ofString('0011'),
+          equals(LogicValue.ofString('0001')) // 1 % 3 = 1
+          );
+      expect(
+          // % normal
+          LogicValue.ofString('0100') % LogicValue.ofString('0010'),
+          equals(LogicValue.ofString('0000')) // 4 % 2 = 0
+          );
+      expect(
+          // % 0 mod
+          LogicValue.ofString('0000') % LogicValue.ofString('0011'),
+          equals(LogicValue.ofString('0000')) // 0 % 3 = 0
+          );
+      expect(
+          // mod-by-0
+          () => LogicValue.ofString('0100') % LogicValue.ofString('0000'),
+          throwsA(isA<Exception>()));
+      expect(
+          // % num by num
+          LogicValue.ofString('0100') % LogicValue.ofString('0100'),
+          equals(LogicValue.ofString('0000')));
     });
+
     test('muldiv', () {
       expect(
           // * normal
diff --git a/test/math_test.dart b/test/math_test.dart
index 4e3ca9986..92e378aad 100644
--- a/test/math_test.dart
+++ b/test/math_test.dart
@@ -20,6 +20,7 @@ class MathTestModule extends Module {
   Logic get aMinusB => output('a_minus_b');
   Logic get aTimesB => output('a_times_b');
   Logic get aDividedByB => output('a_dividedby_b');
+  Logic get aModuloB => output('a_modulo_b');
 
   Logic get aPlusConst => output('a_plus_const');
   Logic get aMinusConst => output('a_minus_const');
@@ -37,6 +38,7 @@ class MathTestModule extends Module {
     var aMinusB = addOutput('a_minus_b', width: a.width);
     var aTimesB = addOutput('a_times_b', width: a.width);
     var aDividedByB = addOutput('a_dividedby_b', width: a.width);
+    var aModuloB = addOutput('a_modulo_b', width: a.width);
 
     var aPlusConst = addOutput('a_plus_const', width: a.width);
     var aMinusConst = addOutput('a_minus_const', width: a.width);
@@ -47,6 +49,7 @@ class MathTestModule extends Module {
     aMinusB <= a - b;
     aTimesB <= a * b;
     aDividedByB <= a / b;
+    aModuloB <= a % b;
 
     aPlusConst <= a + c;
     aMinusConst <= a - c;
@@ -68,6 +71,7 @@ void main() {
       'a_minus_b': 8,
       'a_times_b': 8,
       'a_dividedby_b': 8,
+      'a_modulo_b': 8,
       'a_plus_const': 8,
       'a_minus_const': 8,
       'a_times_const': 8,
@@ -144,5 +148,22 @@ void main() {
           signalToWidthMap: signalToWidthMap);
       expect(simResult, equals(true));
     });
+
+    test('modulo', () async {
+      var gtm = MathTestModule(Logic(width: 8), Logic(width: 8));
+      await gtm.build();
+      var vectors = [
+        Vector({'a': 0, 'b': 0}, {'a_modulo_b': LogicValue.x}),
+        Vector({'a': 1, 'b': 0}, {'a_modulo_b': LogicValue.x}),
+        Vector({'a': 4, 'b': 2}, {'a_modulo_b': 0}),
+        Vector({'a': 5, 'b': 2}, {'a_modulo_b': 1}),
+        Vector({'a': 9, 'b': 3}, {'a_modulo_b': 0}),
+      ];
+      await SimCompare.checkFunctionalVector(gtm, vectors);
+      var simResult = SimCompare.iverilogVector(
+          gtm.generateSynth(), gtm.runtimeType.toString(), vectors,
+          signalToWidthMap: signalToWidthMap);
+      expect(simResult, equals(true));
+    });
   });
 }