Repository: intel/rohd (dart)

Issue:
Access bits of a `Logic` by `Logic` index
## Motivation
Sometimes it's desirable to access a bit of a `Logic` signal by another `Logic`.  This is achievable with `>> n` and `& Const(1)`, but it would be nice to just use `[n]` directly to generate that code for ease of use and readability.

## Desired solution
Modify the overloaded `operator[]` on `Logic` so that it can accept either an `int` or another `Logic`, and appropriately generates hardware if necessary (if it's a `Logic`).

Pull request:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

Modified the overloaded operator[] on Logic so that it can accept either an int or another Logic, and appropriately generates hardware if necessary (if it's a Logic).

## Related Issue(s)

resolves #153 

## Testing

Added unit test: 'simcompare Index by Logic test'

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible?  If yes, how so?

No. Fully backward compatible.

## Documentation

> Does the change require any updates to documentation?  If so, where?  Are they included?

No.

Patch:
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index 453344b1e..41f98dc6f 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -450,7 +450,14 @@ class Logic {
 
   /// Accesses the [index]th bit of this signal.
   ///
-  /// Negative/Positive index values are allowed. (The negative indexing starts from the end=[width]-1)
+  /// Accepts both [int] and [Logic] as [index].
+  ///
+  /// Throws [Exception] when index is not an [int] or [Logic].
+  ///
+  /// Negative/Positive index values are allowed (only when index is an int).
+  /// When, index is a Logic, the index value is treated as an unsigned value.
+  /// The negative indexing starts from the end=[width]-1
+  ///
   /// -([width]) <= [index] < [width]
   ///
   /// ```dart
@@ -467,7 +474,14 @@ class Logic {
   /// nextVal <= val[8]; // Error!: allowed values [-8, 7]
   /// ```
   ///
-  Logic operator [](int index) => slice(index, index);
+  Logic operator [](dynamic index) {
+    if (index is Logic) {
+      return IndexGate(this, index).selection;
+    } else if (index is int) {
+      return slice(index, index);
+    }
+    throw Exception('Expected `int` or `Logic`');
+  }
 
   /// Accesses a subset of this signal from [startIndex] to [endIndex],
   /// both inclusive.
diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart
index e8ef57e96..37c5d6709 100644
--- a/lib/src/modules/gates.dart
+++ b/lib/src/modules/gates.dart
@@ -610,3 +610,72 @@ class Mux extends Module with InlineSystemVerilog {
     return '$control ? $d1 : $d0';
   }
 }
+
+/// A two-input bit index gate [Module].
+///
+/// It always takes two inputs and has one output of width 1.
+class IndexGate extends Module with InlineSystemVerilog {
+  late final String _originalName;
+  late final String _indexName;
+  late final String _selectionName;
+
+  /// The primary input to this gate.
+  Logic get _original => input(_originalName);
+
+  /// The bit index for this gate.
+  Logic get _index => input(_indexName);
+
+  /// The output of this gate.
+  Logic get selection => output(_selectionName);
+
+  /// Constructs a two-input bit index gate for an abitrary custom functional
+  /// implementation.
+  ///
+  /// The bit [index] will be indexed as an output.
+  /// [Module] is in-lined as SystemVerilog, it will use original[index], where
+  /// target is index's int value
+  IndexGate(Logic original, Logic index) : super() {
+    _originalName = 'original_${original.name}';
+    _indexName = Module.unpreferredName('index_${index.name}');
+    _selectionName =
+        Module.unpreferredName('${original.name}_indexby_${index.name}');
+
+    addInput(_originalName, original, width: original.width);
+    addInput(_indexName, index, width: index.width);
+    addOutput(_selectionName);
+
+    _setup();
+  }
+
+  /// Performs setup steps for custom functional behavior.
+  void _setup() {
+    _execute(); // for initial values
+    _original.glitch.listen((args) {
+      _execute();
+    });
+    _index.glitch.listen((args) {
+      _execute();
+    });
+  }
+
+  /// Executes the functional behavior of this gate.
+  void _execute() {
+    if (_index.hasValidValue()) {
+      final indexVal = _index.value.toInt();
+      selection.put(_original.value.getRange(indexVal, indexVal + 1));
+    } else {
+      selection.put(LogicValue.x);
+    }
+  }
+
+  @override
+  String inlineVerilog(Map<String, String> inputs) {
+    if (inputs.length != 2) {
+      throw Exception('Gate has exactly two inputs.');
+    }
+
+    final target = inputs[_originalName]!;
+    final idx = inputs[_indexName]!;
+    return '$target[$idx]';
+  }
+}


Test patch:
diff --git a/test/gate_test.dart b/test/gate_test.dart
index 19f592e40..f94c9e9b6 100644
--- a/test/gate_test.dart
+++ b/test/gate_test.dart
@@ -91,6 +91,17 @@ class MuxWrapper extends Module {
   }
 }
 
+class IndexGateTestModule extends Module {
+  IndexGateTestModule(Logic original, Logic index)
+      : super(name: 'indexgatetestmodule') {
+    original = addInput('original', original, width: original.width);
+    index = addInput('index', index, width: index.width);
+    final bitSet = addOutput('index_output');
+
+    bitSet <= original[index];
+  }
+}
+
 void main() {
   tearDown(Simulator.reset);
 
@@ -418,5 +429,37 @@ void main() {
           gtm.generateSynth(), gtm.runtimeType.toString(), vectors);
       expect(simResult, equals(true));
     });
+
+    test('Index Logic by Logic test', () async {
+      final gtm = IndexGateTestModule(Logic(width: 8), Logic(width: 8));
+      await gtm.build();
+      final vectors = [
+        Vector({'original': 14, 'index': 0}, {'index_output': 0}),
+        Vector({'original': 14, 'index': 2}, {'index_output': 1}),
+        Vector({'original': 14, 'index': LogicValue.x},
+            {'index_output': LogicValue.x}),
+        Vector({'original': LogicValue.x, 'index': LogicValue.x},
+            {'index_output': LogicValue.x}),
+        Vector({'original': LogicValue.x, 'index': 0},
+            {'index_output': LogicValue.x})
+      ];
+      await SimCompare.checkFunctionalVector(gtm, vectors);
+      final simResult = SimCompare.iverilogVector(
+          gtm.generateSynth(), gtm.runtimeType.toString(), vectors,
+          signalToWidthMap: {'original': 8, 'index': 8});
+      expect(simResult, equals(true));
+    });
+
+    test('Index Logic by an Integer test', () {
+      final testLogic = Logic(width: 8)..put(14);
+      expect(testLogic[0].value.toInt(), 0);
+      expect(testLogic[2].value.toInt(), 1);
+      expect(() => testLogic[10], throwsException);
+    });
+
+    test('Index Logic by does not accept input other than int or Logic', () {
+      final testLogic = Logic(width: 8)..put(14);
+      expect(() => testLogic[10.05], throwsException);
+    });
   });
 }
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 36d2a44c7..64c6bd0b0 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -81,12 +81,12 @@ void main() {
       expect(LogicValue.z.toString(), equals("1'bz"));
       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.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>()));
+      expect(LogicValue.x.toInt, throwsA(isA<Exception>()));
+      expect(LogicValue.z.toInt, throwsA(isA<Exception>()));
       expect(LogicValue.ofString(''), equals(LogicValue.ofInt(1, 0)));
       expect(
           <LogicValue>[].swizzle(), equals(LogicValue.ofBigInt(BigInt.two, 0)));