Repository: intel/rohd (dart)

Issue:
Add power functionality
### Motivation

In SystemVerilog, power (`a**b`) is a useful function.

It would be nice to have a synthesizable `Module` and associated functions for generating hardware and SystemVerilog with powers.

### Desired solution

- Create a new `Module` that generates `a**b` in output SystemVerilog and appropriately simulates powers in the ROHD simulator.
- Add functions (probably to `Logic`) to automatically generate this module in a convenient API.

### Alternatives considered

_No response_

### Additional details

_No response_

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation
In SystemVerilog, power (a**b) is a useful function.

It would be nice to have a synthesizable Module and associated functions for generating hardware and SystemVerilog with powers as per issue #336 

<!-- Description of changes, and motivation for adding them. -->

## Testing

<!-- Please describe how you tested your changes. -->
Added necessary test cases to test/logic_value_test.dart and test/math_test.dart

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

<!-- Answer here. -->
Yes. Updated both README.md and doc/tutorials/chapter_2/00_basic_logic.md

Patch:
diff --git a/README.md b/README.md
index 839d25c72..586b2e203 100644
--- a/README.md
+++ b/README.md
@@ -254,6 +254,7 @@ a_xor_b   <=  a ^ b;   // xor
 and_a     <=  a.and(); // unary and
 or_a      <=  a.or();  // unary or
 xor_a     <=  a.xor(); // unary xor
+a_pow_b   <=  a.pow(b);// exponent
 a_plus_b  <=  a + b;   // addition
 a_sub_b   <=  a - b;   // subtraction
 a_times_b <=  a * b;   // multiplication
diff --git a/doc/tutorials/chapter_2/00_basic_logic.md b/doc/tutorials/chapter_2/00_basic_logic.md
index 704bfab0b..711e8a05c 100644
--- a/doc/tutorials/chapter_2/00_basic_logic.md
+++ b/doc/tutorials/chapter_2/00_basic_logic.md
@@ -149,6 +149,7 @@ a_xor_b   <=  a ^ b;   // xor
 and_a     <=  a.and(); // unary and
 or_a      <=  a.or();  // unary or
 xor_a     <=  a.xor(); // unary xor
+a_pow_b   <=  a.pow(b);// exponent
 a_plus_b  <=  a + b;   // addition
 a_sub_b   <=  a - b;   // subtraction
 a_times_b <=  a * b;   // multiplication
diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart
index d9448341e..748637134 100644
--- a/lib/src/exceptions/exceptions.dart
+++ b/lib/src/exceptions/exceptions.dart
@@ -2,6 +2,7 @@
 /// SPDX-License-Identifier: BSD-3-Clause
 export './conditionals/conditional_exceptions.dart';
 export './logic/logic_exceptions.dart';
+export './logic_value/logic_value_exceptions.dart';
 export './module/module_exceptions.dart';
 export './name/name_exceptions.dart';
 export './sim_compare/sim_compare_exceptions.dart';
diff --git a/lib/src/exceptions/logic_value/invalid_truncation_exception.dart b/lib/src/exceptions/logic_value/invalid_truncation_exception.dart
new file mode 100644
index 000000000..cf42e2bee
--- /dev/null
+++ b/lib/src/exceptions/logic_value/invalid_truncation_exception.dart
@@ -0,0 +1,21 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// invalid_truncation_exception.dart
+/// An exception that is thrown when invalid truncation takes place.
+///
+/// 2023 May 13
+/// Author: Sanchit Kumar <sanchit.dabgotra@gmail.com>
+///
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+///An exception that is thrown when a [LogicValue] operation
+/// couldn't be performed due invalid data truncation.
+class InvalidTruncationException extends RohdException {
+  ///Creates an exception when an invalid data truncation occurs
+  ///from present type to required type.
+  InvalidTruncationException(String message)
+      : super("Logical operation couldn't be performed as $message");
+}
diff --git a/lib/src/exceptions/logic_value/logic_value_exceptions.dart b/lib/src/exceptions/logic_value/logic_value_exceptions.dart
new file mode 100644
index 000000000..25f4acacc
--- /dev/null
+++ b/lib/src/exceptions/logic_value/logic_value_exceptions.dart
@@ -0,0 +1,4 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+
+export 'invalid_truncation_exception.dart';
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index 0bdc182dc..08d9ced0e 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -530,6 +530,9 @@ class Logic {
   /// Logical bitwise XOR.
   Logic operator ^(Logic other) => Xor2Gate(this, other).out;
 
+  /// Power operation
+  Logic pow(dynamic other) => Power(this, other).out;
+
   /// Addition.
   ///
   /// WARNING: Signed math is not fully tested.
diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart
index b385f7f41..25c61a1ba 100644
--- a/lib/src/modules/gates.dart
+++ b/lib/src/modules/gates.dart
@@ -424,6 +424,15 @@ class Xor2Gate extends _TwoInputBitwiseGate {
       : super((a, b) => a ^ b, '^', in0, in1, name: name);
 }
 
+/// A two-input power module.
+class Power extends _TwoInputBitwiseGate {
+  /// Calculates [in0] raise to power of [in1].
+  ///
+  /// [in1] can be either a [Logic] or [int].
+  Power(Logic in0, dynamic in1, {String name = 'power'})
+      : super((a, b) => a.pow(b), '**', in0, in1, name: name);
+}
+
 /// A two-input addition module.
 class Add extends _TwoInputBitwiseGate {
   /// Calculates the sum of [in0] and [in1].
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index 4fab13bb2..4cc34876a 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -7,7 +7,6 @@
 /// 2021 August 2
 /// Author: Max Korbel <max.korbel@intel.com>
 ///
-
 part of values;
 
 /// Deprecated: use [LogicValue] instead.
@@ -733,6 +732,13 @@ abstract class LogicValue {
   /// invalid, [neq] will return `x`.
   LogicValue neq(dynamic other) => _doCompare(other, (a, b) => a != b);
 
+  /// Power operation.
+  ///
+  /// This will return a [LogicValue] of some input 'base' to the power of other
+  /// input [exponent]. If one of the two input values is invalid, [pow] will
+  /// return ‘x’ of input size width.
+  LogicValue pow(dynamic exponent) => _doMath(exponent, _powerOperation);
+
   /// Less-than operation.
   ///
   /// WARNING: Signed math is not fully tested.
@@ -761,6 +767,30 @@ abstract class LogicValue {
       // ignore: avoid_dynamic_calls
       _doCompare(other, (a, b) => (a >= b) as bool);
 
+  /// Power operation
+  ///
+  /// Both inputs [base] and [exponent] are either of type [int] or [BigInt].
+  /// Returns [base] raise to the power [exponent] of same input type else
+  /// it will throw an exception.
+  dynamic _powerOperation(dynamic base, dynamic exponent) {
+    if ((base is int) && (exponent is int)) {
+      return math.pow(base, exponent);
+    }
+    if ((base is BigInt) && (exponent is BigInt)) {
+      if (base == BigInt.one) {
+        return BigInt.one;
+      } else if (base == BigInt.zero && exponent > BigInt.zero) {
+        return BigInt.zero;
+      } else if (!exponent.isValidInt) {
+        throw InvalidTruncationException(
+            "BigInt (${exponent.bitLength} bits) won't fit in "
+            'int ($_INT_BITS bits)');
+      } else {
+        return base.pow(exponent.toInt());
+      }
+    }
+  }
+
   /// Executes comparison operations between two [LogicValue]s
   ///
   /// Handles width and bounds checks as well as proper conversion between
diff --git a/lib/src/values/values.dart b/lib/src/values/values.dart
index 1877b2770..a38f6a213 100644
--- a/lib/src/values/values.dart
+++ b/lib/src/values/values.dart
@@ -3,6 +3,8 @@
 
 library values;
 
+import 'dart:math' as math;
+
 import 'package:meta/meta.dart';
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/exceptions/exceptions.dart';


Test patch:
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 5052809c7..162bd4a4d 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -9,6 +9,7 @@
 ///
 
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/exceptions.dart';
 import 'package:test/test.dart';
 
 // All logicvalues to support trying all possiblities
@@ -551,6 +552,80 @@ void main() {
     });
   });
   group('arithmetic operations', () {
+    test('power', () {
+      expect(
+          // test ofInt
+          LogicValue.ofInt(2, 32).pow(LogicValue.ofInt(0, 32)),
+          equals(LogicValue.ofInt(1, 32)));
+      expect(
+          // test ofInt
+          LogicValue.ofInt(3, 32).pow(LogicValue.ofInt(5, 32)),
+          equals(LogicValue.ofInt(243, 32)));
+      expect(
+          // test ofInt
+          LogicValue.ofInt(0, 32).pow(LogicValue.ofInt(0, 32)),
+          equals(LogicValue.ofInt(1, 32)));
+      expect(
+          // test int with BigInt
+          LogicValue.ofInt(2, 64)
+              .pow(LogicValue.ofBigInt(BigInt.parse('10'), 64)),
+          equals(LogicValue.ofBigInt(BigInt.from(1024), 64)));
+
+      expect(
+          // test BigInt with int
+          LogicValue.ofBigInt(BigInt.two, 64).pow(LogicValue.ofInt(10, 64)),
+          equals(LogicValue.ofBigInt(BigInt.from(1024), 64)));
+
+      expect(
+          // test ofBigInt
+          LogicValue.ofBigInt(BigInt.from(31), 128)
+              .pow(LogicValue.ofBigInt(BigInt.from(21), 128)),
+          equals(LogicValue.ofBigInt(
+              BigInt.parse('20825506393391550743120420649631'), 128)));
+      expect(
+          // test ofBigInt
+          LogicValue.ofBigInt(BigInt.parse('111234234231234523412665554'), 256)
+              .pow(LogicValue.ofBigInt(BigInt.from(2), 256)),
+          equals(LogicValue.ofBigInt(
+              BigInt.parse(
+                  '12373054865009146225795242412633846245734343458126916'),
+              256)));
+      expect(
+          // test ofBigInt
+          LogicValue.ofBigInt(BigInt.zero, 64)
+              .pow(LogicValue.ofBigInt(BigInt.zero, 64)),
+          equals(LogicValue.ofBigInt(BigInt.one, 64)));
+      expect(
+          // test ofBigInt
+          LogicValue.ofBigInt(BigInt.one, 512).pow(LogicValue.ofBigInt(
+              BigInt.parse('100000000000000000000000000000000000000'), 512)),
+          equals(LogicValue.ofBigInt(BigInt.one, 512)));
+      expect(
+          // test ofBigInt
+          LogicValue.ofBigInt(BigInt.zero, 512).pow(LogicValue.ofBigInt(
+              BigInt.parse('100000000000000000000000000000000000000'), 512)),
+          equals(LogicValue.ofBigInt(BigInt.zero, 512)));
+      expect(
+          // exception when BigInt exponent won't fit in int
+          () => LogicValue.ofBigInt(BigInt.from(2), 512).pow(
+              LogicValue.ofBigInt(
+                  BigInt.parse('100000000000000000000000000000000000000'),
+                  512)),
+          throwsA(isA<InvalidTruncationException>()));
+      expect(
+          //test string
+          LogicValue.ofString('000010').pow(LogicValue.ofString('000100')),
+          equals(LogicValue.ofString('010000')));
+      expect(
+          //test invalid exponent input
+          LogicValue.ofString('0001').pow(LogicValue.ofString('000x')),
+          equals(LogicValue.filled(4, LogicValue.x)));
+      expect(
+          //test invalid base input
+          LogicValue.ofString('001x').pow(LogicValue.ofString('0001')),
+          equals(LogicValue.filled(4, LogicValue.x)));
+    });
+
     test('addsub', () {
       expect(
           // + normal
diff --git a/test/math_test.dart b/test/math_test.dart
index 20ff96b3f..b8254e231 100644
--- a/test/math_test.dart
+++ b/test/math_test.dart
@@ -23,6 +23,7 @@ class MathTestModule extends Module {
     a = addInput('a', a, width: a.width);
     b = addInput('b', b, width: b.width);
 
+    final aPowerB = addOutput('a_power_b', width: a.width);
     final aPlusB = addOutput('a_plus_b', width: a.width);
     final aMinusB = addOutput('a_minus_b', width: a.width);
     final aTimesB = addOutput('a_times_b', width: a.width);
@@ -30,6 +31,7 @@ class MathTestModule extends Module {
     final aModuloB = addOutput('a_modulo_b', width: a.width);
     final aModuloConst = addOutput('a_modulo_const', width: a.width);
 
+    final aPowerConst = addOutput('a_power_const', width: a.width);
     final aPlusConst = addOutput('a_plus_const', width: a.width);
     final aMinusConst = addOutput('a_minus_const', width: a.width);
     final aTimesConst = addOutput('a_times_const', width: a.width);
@@ -43,6 +45,7 @@ class MathTestModule extends Module {
     final aSrlConst = addOutput('a_srl_const', width: a.width);
     final aSraConst = addOutput('a_sra_const', width: a.width);
 
+    aPowerB <= a.pow(b);
     aPlusB <= a + b;
     aMinusB <= a - b;
     aTimesB <= a * b;
@@ -50,6 +53,7 @@ class MathTestModule extends Module {
     aModuloB <= a % b;
     aModuloConst <= a % c;
 
+    aPowerConst <= a.pow(c);
     aPlusConst <= a + c;
     aMinusConst <= a - c;
     aTimesConst <= a * c;
@@ -79,6 +83,21 @@ void main() {
       expect(simResult, equals(true));
     }
 
+    test('power', () async {
+      await runMathVectors([
+        Vector({'a': 1, 'b': 100}, {'a_power_b': 1}),
+        Vector({'a': 1043, 'b': 0}, {'a_power_b': 1}),
+        Vector({'a': 3, 'b': 3}, {'a_power_b': 27}),
+        Vector({'a': 2, 'b': 1}, {'a_power_b': 2}),
+        Vector({'a': 2, 'b': 7}, {'a_power_b': 128}),
+        Vector({'a': 2}, {'a_power_const': 32}),
+        Vector({'a': 5, 'b': 3}, {'a_power_b': 125}),
+        Vector({'a': 7, 'b': 2}, {'a_power_b': 49}),
+        Vector({'a': 0, 'b': 0}, {'a_power_b': 1}),
+        Vector({'a': 0, 'b': 3}, {'a_power_b': 0}),
+      ]);
+    });
+
     test('addition', () async {
       await runMathVectors([
         Vector({'a': 0, 'b': 0}, {'a_plus_b': 0}),