Repository: intel/rohd (dart)

Issue:
`ReplicationOp` should support multi-bit replication
### Summary

PR https://github.com/intel/rohd/pull/203 introduced `ReplicationOp`, but only 1-bit `Logic`s are supported, it does support multi-bit `Logic`s.  

### To Reproduce

Use `ReplicationOp` with a multi-bit value for the `bit` input.

### Expected behavior

It's probably nice to support multi-bit, but requires a bit more logic to handle that.

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation
Fixes: 223
PR https://github.com/intel/rohd/pull/203 introduced ReplicationOp, but only 1-bit Logics are supported, it does support multi-bit Logics.

To Reproduce
Use ReplicationOp with a multi-bit value for the bit input.

Expected behavior
It's probably nice to support multi-bit, but requires a bit more logic to handle that.

Fixes: 224
Add some function on Logic, like Logic.replicate(int multiplier) or something like that.

<!-- 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 -->
[223](https://github.com/intel/rohd/issues/223)
[224](https://github.com/intel/rohd/issues/224)

## Testing

<!-- Please describe how you tested your changes -->
- Added tests ([tests/replication_test.dart])
  - Test invalid multipliers (0, -1)
  - Test that multiply by 1 returns the same signal
  - Test other multipliers
 
## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible?  If yes, how so?

<!--Answer here-->
No.
## Documentation

> Does the change require any updates to documentation?  If so, where?  Are they included?

<!--Answer here-->
Added/Modified the required doc comments.

Patch:
diff --git a/lib/src/exceptions/logic/invalid_multiplier_exception.dart b/lib/src/exceptions/logic/invalid_multiplier_exception.dart
new file mode 100644
index 000000000..1d35f0ad7
--- /dev/null
+++ b/lib/src/exceptions/logic/invalid_multiplier_exception.dart
@@ -0,0 +1,21 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// invalid_multiplier_exception.dart
+/// An exception that thrown when a signal has an invalid width.
+///
+/// 2023 January 24
+/// Author: Akshay Wankhede <akshay.wankhede@intel.com>
+///
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that thrown when a [Logic] is replicated with an invalid (<1)
+/// multiplier.
+class InvalidMultiplierException extends RohdException {
+  /// Creates an exception when a logic input/output is replicated with an
+  /// invalid multiplier
+  InvalidMultiplierException(int multiplier)
+      : super('A multiplier can not be 0 or negative: $multiplier');
+}
diff --git a/lib/src/exceptions/logic/logic_exceptions.dart b/lib/src/exceptions/logic/logic_exceptions.dart
index 861808203..26c95581f 100644
--- a/lib/src/exceptions/logic/logic_exceptions.dart
+++ b/lib/src/exceptions/logic/logic_exceptions.dart
@@ -1,4 +1,5 @@
 /// Copyright (C) 2023 Intel Corporation
 /// SPDX-License-Identifier: BSD-3-Clause
 
+export 'invalid_multiplier_exception.dart';
 export 'put_exception.dart';
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index cc0f06899..d6e800d96 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -1,4 +1,4 @@
-/// Copyright (C) 2021-2022 Intel Corporation
+/// Copyright (C) 2021-2023 Intel Corporation
 /// SPDX-License-Identifier: BSD-3-Clause
 ///
 /// logic.dart
@@ -847,6 +847,12 @@ class Logic {
     ].swizzle();
   }
 
+  /// Returns a replicated signal using [ReplicationOp] with new
+  /// width = this.width * [multiplier]
+  /// The input [multiplier] cannot be negative or 0; an exception will be
+  /// thrown, otherwise.
+  Logic replicate(int multiplier) => ReplicationOp(this, multiplier).replicated;
+
   /// Returns `1` (of [width]=1) if the [Logic] calling this function is in
   /// [list]. Else `0` (of [width]=1) if not present.
   ///
diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart
index 746e9e734..50a50fcab 100644
--- a/lib/src/modules/gates.dart
+++ b/lib/src/modules/gates.dart
@@ -1,4 +1,4 @@
-/// Copyright (C) 2021-2022 Intel Corporation
+/// Copyright (C) 2021-2023 Intel Corporation
 /// SPDX-License-Identifier: BSD-3-Clause
 ///
 /// gates.dart
@@ -9,6 +9,7 @@
 ///
 
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/exceptions.dart';
 
 /// A [Module] which has only combinational logic within it and defines
 /// custom functionality.
@@ -762,7 +763,7 @@ class ReplicationOp extends Module
   // output component name
   final String _outputName;
   // Width of the output signal
-  final int _width;
+  final int _multiplier;
 
   /// The primary input to this gate.
   Logic get _input => input(_inputName);
@@ -772,13 +773,21 @@ class ReplicationOp extends Module
 
   /// Constructs a ReplicationOp
   ///
-  /// The bit [bit] will be repeated over the [_width] as an output.
+  /// The signal [original] will be repeated over the [_multiplier] times as an
+  /// output.
+  /// Input [_multiplier] cannot be negative or zero, an exception will be
+  /// thrown, otherwise.
   /// [Module] is in-lined as SystemVerilog, it will use {width{bit}}
-  ReplicationOp(Logic bit, this._width)
-      : _inputName = 'input_${bit.name}',
-        _outputName = Module.unpreferredName('output_${bit.name}') {
-    addInput(_inputName, bit);
-    addOutput(_outputName, width: _width);
+  ReplicationOp(Logic original, this._multiplier)
+      : _inputName = Module.unpreferredName('input_${original.name}'),
+        _outputName = Module.unpreferredName('output_${original.name}') {
+    final newWidth = original.width * _multiplier;
+    if (newWidth < 1) {
+      throw InvalidMultiplierException(newWidth);
+    }
+
+    addInput(_inputName, original, width: original.width);
+    addOutput(_outputName, width: original.width * _multiplier);
     _setup();
   }
 
@@ -792,7 +801,7 @@ class ReplicationOp extends Module
 
   /// Executes the functional behavior of this gate.
   void _execute() {
-    replicated.put(_input.value, fill: true);
+    replicated.put(_input.value.replicate(_multiplier));
   }
 
   @override
@@ -802,7 +811,7 @@ class ReplicationOp extends Module
     }
 
     final target = inputs[_inputName]!;
-    final width = _width;
+    final width = _multiplier;
     return '{$width{$target}}';
   }
 }
diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart
index 14bb4b34e..764723038 100644
--- a/lib/src/values/logic_value.dart
+++ b/lib/src/values/logic_value.dart
@@ -927,6 +927,16 @@ abstract class LogicValue {
       getRange(0, startIndex),
     ].swizzle();
   }
+
+  /// Returns new [LogicValue] replicated [multiplier] times. An exception will
+  /// be thrown in case the multiplier is <1
+  LogicValue replicate(int multiplier) {
+    if (multiplier < 1) {
+      throw InvalidMultiplierException(multiplier);
+    }
+
+    return LogicValue.of(List.filled(multiplier, this));
+  }
 }
 
 /// Enum for direction of shift
diff --git a/lib/src/values/values.dart b/lib/src/values/values.dart
index 6c982b1a5..1877b2770 100644
--- a/lib/src/values/values.dart
+++ b/lib/src/values/values.dart
@@ -5,6 +5,7 @@ library values;
 
 import 'package:meta/meta.dart';
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/exceptions.dart';
 
 part 'logic_value.dart';
 part 'small_logic_value.dart';


Test patch:
diff --git a/test/replication_test.dart b/test/replication_test.dart
new file mode 100644
index 000000000..b506957f7
--- /dev/null
+++ b/test/replication_test.dart
@@ -0,0 +1,92 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// replication_test.dart
+/// Unit tests for extend and withSet operations
+///
+/// 2023 Jan 18
+/// Author: Akshay Wankhede <akshay.wankhede@intel.com>
+///
+
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/utilities/simcompare.dart';
+import 'package:test/test.dart';
+
+class ReplicationOpModule extends Module {
+  ReplicationOpModule(Logic a, int multiplier) {
+    final newWidth = a.width * multiplier;
+    a = addInput('a', a, width: a.width);
+    final b = addOutput('b', width: newWidth < 1 ? a.width : newWidth);
+
+    b <= a.replicate(multiplier);
+  }
+}
+
+void main() {
+  group('Logic', () {
+    tearDown(Simulator.reset);
+    group('replicate', () {
+      Future<void> replicateVectors(List<Vector> vectors, int multiplier,
+          {int originalWidth = 8}) async {
+        final mod =
+            ReplicationOpModule(Logic(width: originalWidth), multiplier);
+        await mod.build();
+        await SimCompare.checkFunctionalVector(mod, vectors);
+        final simResult = SimCompare.iverilogVector(mod, vectors);
+        expect(simResult, equals(true));
+      }
+
+      test('multiply by a multiplier <1 throws exception', () async {
+        expect(
+            () => replicateVectors([
+                  Vector({'a': 0}, {'b': 0})
+                ], 0, originalWidth: 4),
+            throwsException);
+        expect(() => replicateVectors([], -1), throwsException);
+      });
+
+      test('multiply by 1 returns same thing', () async {
+        await replicateVectors([
+          Vector({'a': 0}, {'b': 0}),
+          Vector({'a': 0xf}, {'b': 0xf}),
+          Vector({'a': 0x5}, {'b': 0x5}),
+        ], 1, originalWidth: 4);
+      });
+
+      test('multiply by 2 replicates the input signal twice', () async {
+        await replicateVectors([
+          Vector({'a': 0}, {'b': 0}),
+          Vector({'a': 0xf}, {'b': 0xff}),
+          Vector({'a': 0x5}, {'b': 0x55}),
+        ], 2, originalWidth: 4);
+      });
+
+      test('multiply by 3 replicates the input signal thrice', () async {
+        await replicateVectors([
+          Vector({'a': 0}, {'b': 0}),
+          Vector({'a': 0xf}, {'b': 0xfff}),
+          Vector({'a': 0x5}, {'b': 0x555}),
+        ], 3, originalWidth: 4);
+      });
+
+      test('LogicValue.replicate tests', () async {
+        expect(LogicValue.one.replicate(2).toString(includeWidth: false),
+            equals('11'));
+        expect(LogicValue.zero.replicate(2).toString(includeWidth: false),
+            equals('00'));
+        expect(LogicValue.x.replicate(2).toString(includeWidth: false),
+            equals('xx'));
+        expect(LogicValue.z.replicate(2).toString(includeWidth: false),
+            equals('zz'));
+        expect(
+            LogicValue.ofString('1011')
+                .replicate(2)
+                .toString(includeWidth: false),
+            equals('10111011'));
+        expect(() => LogicValue.ofString('1011').replicate(0), throwsException);
+        expect(
+            () => LogicValue.ofString('1011').replicate(-2), throwsException);
+      });
+    });
+  });
+}