Repository: intel/rohd (dart)

Issue:
Add a mechanism for generating random `LogicValue`s
### Motivation

`LogicValue` is a useful type since it supports arbitrary width in an efficient representation.  For testing purposes, it might be desirable to generate a random value on a bus of arbitrary width.  The built-in Dart randomization only randomizes up to 2^32, which means users have to invent their own little code snippets to generate random numbers bigger than 32 bits.

### Desired solution

A way to generate large random numbers, especially for `LogicValue`.

One nice option might be to add an `extension` on `Random`.

It might be nice to support both valid values or a mix of valid and invalid values.

It might be nice to support random `BigInt`s as well.

### 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

LogicValue is a useful type since it supports arbitrary width in an efficient representation. For testing purposes, it might be desirable to generate a random value on a bus of arbitrary width. The built-in Dart randomization only randomizes up to 2^32, which means users have to invent their own little code snippets to generate random numbers bigger than 32 bits.

## Related Issue(s)

Fix #232 

## Testing

Add unit test to test random generation of the `LogicValue`.

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible? If yes, how so?

No

## Documentation

Included in the public documentation.

Patch:
diff --git a/lib/src/exceptions/logic_value/invalid_random_logic_value_exception.dart b/lib/src/exceptions/logic_value/invalid_random_logic_value_exception.dart
new file mode 100644
index 000000000..0c7168f6a
--- /dev/null
+++ b/lib/src/exceptions/logic_value/invalid_random_logic_value_exception.dart
@@ -0,0 +1,24 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// invalid_random_logic_value_exception.dart
+/// An exception that is thrown when LogicValue generated from Random
+/// LogicValue is incorrect.
+///
+/// 2023 May 31
+/// Author: Yao Jing Quek <yao.jing.quek@intel.com>
+///
+
+import 'dart:math';
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that is thrown when the generation of the random [LogicValue]
+/// from [Random] results in errors or bugs.
+class InvalidRandomLogicValueException extends RohdException {
+  /// Creates an exception when the [LogicValue]'s bits generated from [Random]
+  /// is incorrect.
+  InvalidRandomLogicValueException(String message)
+      : super('Generation of the random value is incorrect or errors '
+            'due to $message');
+}
diff --git a/lib/src/values/random_logic_value.dart b/lib/src/values/random_logic_value.dart
new file mode 100644
index 000000000..b6c2d7bd2
--- /dev/null
+++ b/lib/src/values/random_logic_value.dart
@@ -0,0 +1,97 @@
+/// Copyright (C) 2021-2022 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// random_logic_value.dart
+/// Random Logic Value generation extension.
+///
+/// 2023 May 18
+/// Author: Yao Jing Quek <yao.jing.quek@intel.com>
+///
+
+part of values;
+
+/// Allows random generation of [LogicValue] for [BigInt] and [int].
+extension RandLogicValue on math.Random {
+  /// Generate unsigned random [BigInt] value that consists of
+  /// [numBits] bits.
+  BigInt _nextBigInt({required int numBits}) {
+    var result = BigInt.zero;
+    for (var i = 0; i < numBits; i += 32) {
+      result = (result << 32) | BigInt.from(nextInt(1 << 32));
+    }
+    return result & ((BigInt.one << numBits) - BigInt.one);
+  }
+
+  /// Generate unsigned random [LogicValue] based on [width] and [max] num.
+  ///
+  /// The random number can be mixed in invalid bits x and z by set
+  /// [includeInvalidBits] to `true`. [max] can be used to set the maximum
+  /// range of the generated number and its only accept runtimeType `int` and
+  /// `BigInt`. [max] only work when [includeInvalidBits] is set to false
+  /// else an exception will be thrown.
+  LogicValue nextLogicValue({
+    required int width,
+    dynamic max,
+    bool includeInvalidBits = false,
+  }) {
+    if (width == 0) {
+      return LogicValue.empty;
+    }
+
+    if (max != null) {
+      if (max is! int && max is! BigInt) {
+        throw InvalidRandomLogicValueException(
+            'max can be only runtimeType of int or BigInt.');
+      }
+
+      if (max is int && max == 0) {
+        return LogicValue.ofInt(max, width);
+      } else if (max is BigInt && max == BigInt.zero) {
+        return LogicValue.ofBigInt(BigInt.zero, width);
+      }
+
+      if ((max is int && max < 0) || (max is BigInt && max < BigInt.zero)) {
+        throw InvalidRandomLogicValueException('max cannot be less than 0');
+      }
+    }
+
+    if (includeInvalidBits) {
+      if (max != null) {
+        throw InvalidRandomLogicValueException(
+            'max does not work with invalid bits random number generation.');
+      }
+
+      final bitString = StringBuffer();
+      for (var i = 0; i < width; i++) {
+        bitString.write(const ['1', '0', 'x', 'z'][nextInt(4)]);
+      }
+
+      return LogicValue.ofString(bitString.toString());
+    } else {
+      if (width <= LogicValue._INT_BITS) {
+        final ranNum = width <= 32
+            ? LogicValue.ofInt(nextInt(1 << width), width)
+            : LogicValue.ofInt(_nextBigInt(numBits: width).toInt(), width);
+
+        if (max == null ||
+            (max is BigInt && max.bitLength > LogicValue._INT_BITS)) {
+          return ranNum;
+        } else {
+          return LogicValue.ofInt(
+              ranNum.toInt() % (max is int ? max : (max as BigInt).toInt()),
+              width);
+        }
+      } else {
+        final ranNum = _nextBigInt(numBits: width);
+
+        if (max == null ||
+            (max is BigInt && ranNum.bitLength < max.bitLength)) {
+          return LogicValue.ofBigInt(ranNum, width);
+        } else {
+          final maxBigInt = max is int ? BigInt.from(max) : max as BigInt;
+          return LogicValue.ofBigInt(ranNum % maxBigInt, width);
+        }
+      }
+    }
+  }
+}
diff --git a/lib/src/values/values.dart b/lib/src/values/values.dart
index e9494af87..bd338d50e 100644
--- a/lib/src/values/values.dart
+++ b/lib/src/values/values.dart
@@ -8,9 +8,10 @@ import 'dart:math' as math;
 import 'package:meta/meta.dart';
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/exceptions/exceptions.dart';
+import 'package:rohd/src/exceptions/logic_value/invalid_random_logic_value_exception.dart';
 import 'package:rohd/src/utilities/index_utilities.dart';
-
 part 'logic_value.dart';
 part 'small_logic_value.dart';
 part 'big_logic_value.dart';
 part 'filled_logic_value.dart';
+part 'random_logic_value.dart';


Test patch:
diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart
index 9488098f6..866f236f5 100644
--- a/test/logic_value_test.dart
+++ b/test/logic_value_test.dart
@@ -8,8 +8,11 @@
 /// Author: Max Korbel <max.korbel@intel.com>
 ///
 
+import 'dart:math';
+
 import 'package:rohd/rohd.dart';
 import 'package:rohd/src/exceptions/exceptions.dart';
+import 'package:rohd/src/exceptions/logic_value/invalid_random_logic_value_exception.dart';
 import 'package:test/test.dart';
 
 // All logicvalues to support trying all possiblities
@@ -1158,4 +1161,95 @@ void main() {
           BigInt.from(128));
     });
   });
+
+  group('random value generation ', () {
+    test('should throw exception when max is not int or BigInt.', () {
+      expect(() => Random(5).nextLogicValue(width: 10, max: '10'),
+          throwsA(isA<InvalidRandomLogicValueException>()));
+      expect(() => Random(5).nextLogicValue(width: 10, max: 10.5),
+          throwsA(isA<InvalidRandomLogicValueException>()));
+    });
+
+    test('should throw exception when max is less than 0.', () {
+      expect(() => Random(5).nextLogicValue(width: 10, max: -1),
+          throwsA(isA<InvalidRandomLogicValueException>()));
+      expect(() => Random(5).nextLogicValue(width: 10, max: BigInt.from(-10)),
+          throwsA(isA<InvalidRandomLogicValueException>()));
+    });
+
+    test(
+        'should throw exception when max is set when generate random num with'
+        ' invalid bits.', () {
+      expect(
+          () => Random(5).nextLogicValue(
+              width: 10,
+              includeInvalidBits: true,
+              max: LogicValue.ofInt(10, 10)),
+          throwsA(isA<InvalidRandomLogicValueException>()));
+    });
+
+    test(
+        'should return random logic value with invalid bits when '
+        'includeInvalidBits is set to true.', () {
+      final lvRand =
+          Random(5).nextLogicValue(width: 10, includeInvalidBits: true);
+
+      expect(lvRand.toString(), contains('x'));
+    });
+
+    test('should return empty LogicValue when width is 0.', () {
+      expect(Random(5).nextLogicValue(width: 0), equals(LogicValue.empty));
+    });
+
+    test('should return empty LogicValue when max is 0 for int and big int.',
+        () {
+      final maxBigInt = BigInt.zero;
+      const maxInt = 0;
+      expect(Random(5).nextLogicValue(width: 10, max: maxInt).toInt(),
+          equals(maxInt));
+
+      expect(Random(5).nextLogicValue(width: 80, max: maxBigInt).toBigInt(),
+          equals(maxBigInt));
+    });
+
+    test(
+        'should return random int logic value without invalid bits when'
+        ' having different width and max constraint.', () {
+      const maxValInt = 8888;
+
+      for (var i = 1; i <= 64; i++) {
+        final lvRand = Random(5).nextLogicValue(width: i);
+        final lvRandMaxInt = Random(5).nextLogicValue(width: i, max: maxValInt);
+        final lvMaxBigInt = Random(5)
+            .nextLogicValue(width: i, max: BigInt.parse('9999999999999999999'));
+        final lvMaxIntBigInt =
+            Random(5).nextLogicValue(width: i, max: BigInt.from(maxValInt));
+
+        expect(lvRand.toInt(), isA<int>());
+        expect(lvRandMaxInt.toInt(), lessThan(maxValInt));
+        expect(lvMaxBigInt, equals(Random(5).nextLogicValue(width: i)));
+        expect(lvMaxIntBigInt.toInt(), lessThan(maxValInt));
+      }
+    });
+
+    test(
+        'should return random big integer logic value with width '
+        'greater than 64 when having different width and max constraint.', () {
+      final maxValBigInt = BigInt.parse('179289266005644583');
+      final maxValBigIntlv =
+          LogicValue.ofBigInt(maxValBigInt, maxValBigInt.bitLength);
+      const maxInt = 30;
+
+      for (var i = 65; i <= 500; i++) {
+        final lvRand = Random(5).nextLogicValue(width: i);
+        final lvRandMax = Random(5).nextLogicValue(width: i, max: maxValBigInt);
+
+        final randMaxInt = Random(5).nextLogicValue(width: i, max: maxInt);
+        expect(randMaxInt.toBigInt(), lessThan(BigInt.from(maxInt)));
+
+        expect(lvRand.toBigInt(), isA<BigInt>());
+        expect(lvRandMax.toBigInt(), lessThan(maxValBigIntlv.toBigInt()));
+      }
+    });
+  });
 }