From b7612baa4cdd5f87ba37d3de5aa444a067497a1e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 19 Sep 2023 10:15:29 -0700 Subject: [PATCH] Fixes and improvements related to shifts (#412) --- lib/src/exceptions/exceptions.dart | 1 + .../value_width_mismatch_exception.dart | 4 +- .../non_supported_type_exception.dart | 4 +- .../unsupported_type_exception.dart | 18 + lib/src/modules/gates.dart | 2 +- lib/src/utilities/simcompare.dart | 36 +- lib/src/values/big_logic_value.dart | 57 ++-- lib/src/values/filled_logic_value.dart | 39 ++- lib/src/values/logic_value.dart | 224 +++++++++---- lib/src/values/small_logic_value.dart | 42 ++- lib/src/values/values.dart | 1 + test/gate_test.dart | 261 ++++++++++----- test/logic_value_test.dart | 312 +++++++++++++++++- test/logic_value_width_test.dart | 31 +- 14 files changed, 806 insertions(+), 226 deletions(-) create mode 100644 lib/src/exceptions/unsupported_type_exception.dart diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart index 1f3e9a635..e71095031 100644 --- a/lib/src/exceptions/exceptions.dart +++ b/lib/src/exceptions/exceptions.dart @@ -9,3 +9,4 @@ export './module/module_exceptions.dart'; export './name/name_exceptions.dart'; export './sim_compare/sim_compare_exceptions.dart'; export 'rohd_exception.dart'; +export 'unsupported_type_exception.dart'; diff --git a/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart b/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart index 28945b40b..7fd3ac3bf 100644 --- a/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart +++ b/lib/src/exceptions/logic_value/value_width_mismatch_exception.dart @@ -15,6 +15,6 @@ class ValueWidthMismatchException extends RohdException { /// Creates an exception when two [LogicValue] considered for the operation /// are of different width. ValueWidthMismatchException(LogicValue a, LogicValue b) - : super('Width Mismatch ${a.width} & ${b.width}: ' - 'LogicValue must be of same width'); + : super('Width mismatch between $a (${a.width}) & $b (${b.width}): ' + 'LogicValues must be of same width for this operation.'); } diff --git a/lib/src/exceptions/sim_compare/non_supported_type_exception.dart b/lib/src/exceptions/sim_compare/non_supported_type_exception.dart index b4fc6d172..6ba5e5654 100644 --- a/lib/src/exceptions/sim_compare/non_supported_type_exception.dart +++ b/lib/src/exceptions/sim_compare/non_supported_type_exception.dart @@ -18,7 +18,7 @@ class NonSupportedTypeException extends RohdException { /// with default error [message]. /// /// Creates a [NonSupportedTypeException] with an optional error [message]. - NonSupportedTypeException(String vector, + NonSupportedTypeException(dynamic vector, [String message = 'The runtimetype of expected vector is unsupported: ']) - : super(message + vector.runtimeType.toString()); + : super('$message $vector (${vector.runtimeType})'); } diff --git a/lib/src/exceptions/unsupported_type_exception.dart b/lib/src/exceptions/unsupported_type_exception.dart new file mode 100644 index 000000000..a645a5c41 --- /dev/null +++ b/lib/src/exceptions/unsupported_type_exception.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// unsupported_type_exception.dart +// An exception that is thrown when an unsupported type is used. +// +// 2023 September 14 +// Author: Max Korbel supportedTypes) + : super('Unsupported type ${value.runtimeType} used ($value).' + ' Supported types are ${supportedTypes.join(',')}'); +} diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart index dc7ef504a..7b94f39dc 100644 --- a/lib/src/modules/gates.dart +++ b/lib/src/modules/gates.dart @@ -351,7 +351,7 @@ class _ShiftGate extends Module with InlineSystemVerilog { : super(name: name) { final shiftAmountLogic = shiftAmount is Logic ? shiftAmount - : Const(shiftAmount, width: in_.width); + : Const(LogicValue.ofInferWidth(shiftAmount)); _inName = Module.unpreferredName('in_${in_.name}'); _shiftAmountName = diff --git a/lib/src/utilities/simcompare.dart b/lib/src/utilities/simcompare.dart index 5d55f7ed9..58cb6453c 100644 --- a/lib/src/utilities/simcompare.dart +++ b/lib/src/utilities/simcompare.dart @@ -47,15 +47,22 @@ class Vector { /// the [inputValues]. static String _errorCheckString(String sigName, dynamic expected, LogicValue expectedVal, String inputValues) { - final expectedHexStr = expected is int - ? '0x${expected.toRadixString(16)}' - : expected.toString(); - final expectedValStr = expectedVal.toString(); + if (expected is! int && expected is! LogicValue && expected is! BigInt) { + throw NonSupportedTypeException(expected); + } - if (expected is! int && expected is! LogicValue) { - throw Exception( - 'Support for ${expected.runtimeType} is not supported (yet?).'); + String expectedHexStr; + if (expected is int) { + expectedHexStr = + BigInt.from(expected).toUnsigned(expectedVal.width).toRadixString(16); + } else if (expected is BigInt) { + expectedHexStr = expected.toUnsigned(expectedVal.width).toRadixString(16); + } else { + expectedHexStr = expected.toString(); } + expectedHexStr = '0x$expectedHexStr'; + + final expectedValStr = expectedVal.toString(); return 'if($sigName !== $expectedValStr) ' '\$error(\$sformatf("Expected $sigName=$expectedHexStr,' @@ -80,7 +87,11 @@ class Vector { } return arrAssigns.toString(); } else { - return '$signalName = ${inputValues[signalName]};'; + var assignmentValue = inputValues[signalName]; + if (assignmentValue is BigInt) { + assignmentValue = LogicValue.of(assignmentValue, width: signal.width); + } + return '$signalName = $assignmentValue;'; } }).join('\n'); @@ -151,7 +162,12 @@ abstract class SimCompare { ' expected $o to be $value, but it was ${o.value}.'; if (value is int) { expect(o.value.isValid, isTrue, reason: errorReason); - expect(o.value.toInt(), equals(value), reason: errorReason); + expect(o.value.toBigInt(), + equals(BigInt.from(value).toUnsigned(o.width)), + reason: errorReason); + } else if (value is BigInt) { + expect(o.value.isValid, isTrue, reason: errorReason); + expect(o.value.toBigInt(), equals(value), reason: errorReason); } else if (value is LogicValue) { if (o.width > 1 && (value == LogicValue.x || value == LogicValue.z)) { @@ -162,7 +178,7 @@ abstract class SimCompare { expect(o.value, equals(value), reason: errorReason); } } else { - throw NonSupportedTypeException(value.runtimeType.toString()); + throw NonSupportedTypeException(value); } } }).catchError( diff --git a/lib/src/values/big_logic_value.dart b/lib/src/values/big_logic_value.dart index f76aab2f9..09d2faa96 100644 --- a/lib/src/values/big_logic_value.dart +++ b/lib/src/values/big_logic_value.dart @@ -42,7 +42,7 @@ class _BigLogicValue extends LogicValue { late final BigInt _invalid; BigInt get _mask => _maskOfWidth(width); - static final Map _masksOfWidth = {}; + static final Map _masksOfWidth = HashMap(); static BigInt _maskOfWidth(int width) { if (!_masksOfWidth.containsKey(width)) { _masksOfWidth[width] = @@ -121,15 +121,22 @@ class _BigLogicValue extends LogicValue { @override BigInt toBigInt() { if (_invalid.sign != 0) { - throw Exception('Cannot convert invalid LogicValue to BigInt: $this'); + throw InvalidValueOperationException(this, 'toBigInt'); } return _value; } @override - int toInt() => - throw Exception('LogicValue width $width is too long to convert to int.' + int toInt() { + final bigInt = toBigInt(); + if (bigInt.isValidInt) { + return bigInt.toIntUnsigned(LogicValue._INT_BITS); + } else { + throw InvalidTruncationException( + 'LogicValue $this is too long to convert to int.' ' Use toBigInt() instead.'); + } + } @override LogicValue operator ~() => LogicValue._bigLogicValueOrFilled( @@ -197,28 +204,34 @@ class _BigLogicValue extends LogicValue { } @override - LogicValue _shiftLeft(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._bigLogicValueOrFilled( - (_value << shamt) & _mask, (_invalid << shamt) & _mask, width); + LogicValue _shiftLeft(int shamt) => LogicValue._bigLogicValueOrFilled( + (_value << shamt) & _mask, (_invalid << shamt) & _mask, width); @override - LogicValue _shiftRight(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._bigLogicValueOrFilled( - _value >> shamt, _invalid >> shamt, width); + LogicValue _shiftRight(int shamt) => LogicValue._bigLogicValueOrFilled( + // we can just use >> because these are unsigned + _value >> shamt, + _invalid >> shamt, + width); @override - LogicValue _shiftArithmeticRight(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._bigLogicValueOrFilled( - (_value | - (this[width - 1] == LogicValue.one - ? ((_mask >> (width - shamt)) << (width - shamt)) - : BigInt.zero)) >> - shamt, - _invalid >> shamt, - width); + LogicValue _shiftArithmeticRight(int shamt) { + shamt; + + var value = (_value.toSigned(width) >> shamt).toUnsigned(width); + var invalid = (_invalid.toSigned(width) >> shamt).toUnsigned(width); + + // if uppermost bit is invalid, then turn the shifted bits into X's + if (!this[-1].isValid) { + // for affected bits of value: zero out value + value &= _mask >> shamt; + + // for affected bits of invalid: make sure they are high + invalid |= ~_mask >> shamt; + } + + return LogicValue._bigLogicValueOrFilled(value, invalid, width); + } @override BigInt get _bigIntInvalid => _invalid; diff --git a/lib/src/values/filled_logic_value.dart b/lib/src/values/filled_logic_value.dart index 0a0ceab75..718a79de7 100644 --- a/lib/src/values/filled_logic_value.dart +++ b/lib/src/values/filled_logic_value.dart @@ -73,8 +73,7 @@ class _FilledLogicValue extends LogicValue { int get _hashCode => _value.hashCode; @override - bool get isValid => - !(_value == _LogicValueEnum.x || _value == _LogicValueEnum.z); + bool get isValid => _value.isValid; @override bool get isFloating => _value == _LogicValueEnum.z; @@ -86,21 +85,24 @@ class _FilledLogicValue extends LogicValue { } else if (_value == _LogicValueEnum.zero) { return BigInt.zero; } - throw Exception('Cannot convert invalid value "$_value" to BigInt.'); + throw InvalidValueOperationException(this, 'toBigInt'); } @override int toInt() { - if (width > LogicValue._INT_BITS) { - throw Exception('LogicValue width $width is too long to convert to int.' + if (_value == _LogicValueEnum.zero) { + return 0; + } else if (!isValid) { + throw InvalidValueOperationException(this, 'toInt'); + } else if (width > LogicValue._INT_BITS) { + throw InvalidTruncationException( + 'LogicValue $this is too long to convert to int.' ' Use toBigInt() instead.'); - } - if (_value == _LogicValueEnum.one) { + } else if (_value == _LogicValueEnum.one) { return _SmallLogicValue._maskOfWidth(width); - } else if (_value == _LogicValueEnum.zero) { - return 0; } - throw Exception('Cannot convert invalid value "$_value" to an int.'); + + throw InvalidValueOperationException(this, 'toInt'); } @override @@ -289,10 +291,6 @@ class _FilledLogicValue extends LogicValue { return this; } - if (shamt >= width) { - return _FilledLogicValue(_LogicValueEnum.zero, width); - } - return [ getRange(0, width - shamt), _FilledLogicValue(_LogicValueEnum.zero, shamt), @@ -305,18 +303,19 @@ class _FilledLogicValue extends LogicValue { return this; } - if (shamt >= width) { - return _FilledLogicValue(_LogicValueEnum.zero, width); - } - return [ _FilledLogicValue(_LogicValueEnum.zero, shamt), - getRange(shamt, width), + _getRange(shamt, width), ].swizzle(); } @override - LogicValue _shiftArithmeticRight(int shamt) => this; + LogicValue _shiftArithmeticRight(int shamt) => _value != _LogicValueEnum.z + ? this + : [ + _FilledLogicValue(_LogicValueEnum.x, shamt), + _getRange(shamt, width), + ].swizzle(); @override BigInt get _bigIntInvalid => diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart index c845feeec..462be1b0a 100644 --- a/lib/src/values/logic_value.dart +++ b/lib/src/values/logic_value.dart @@ -67,7 +67,8 @@ abstract class LogicValue implements Comparable { /// /// [width] must be greater than or equal to 0. static LogicValue ofInt(int value, int width) => width > _INT_BITS - ? _bigLogicValueOrFilled(BigInt.from(value), BigInt.zero, width) + ? _bigLogicValueOrFilled( + BigInt.from(value).toUnsigned(_INT_BITS), BigInt.zero, width) : _smallLogicValueOrFilled(value, 0, width); /// Converts `int` [value] to a valid [LogicValue] with [width] @@ -100,6 +101,7 @@ abstract class LogicValue implements Comparable { static LogicValue filled(int width, LogicValue fill) => _FilledLogicValue(fill._enum, width); + /// Gets the [_LogicValueEnum] for single-bit [LogicValue]s. _LogicValueEnum get _enum { if (width != 1) { throw Exception( @@ -116,7 +118,37 @@ abstract class LogicValue implements Comparable { : throw Exception('Failed to convert.'); } - // complete test suite for LogicValue.of + /// Creates a [LogicValue] of [val] using [of], but attempts to infer the + /// width that would fit [val] automatically. + /// + /// Only accepts [val]s of types [int], [BigInt], and [LogicValue]. + /// + /// The width of negative numbers cannot be inferred and an exception will + /// be thrown. + static LogicValue ofInferWidth(dynamic val) { + int width; + if (val is int) { + if (val < 0) { + throw LogicValueConstructionException( + 'Cannot infer width of a negative int.'); + } else { + width = val.bitLength; + } + } else if (val is BigInt) { + if (val.isNegative) { + throw LogicValueConstructionException( + 'Cannot infer width of a negative BigInt.'); + } else { + width = val.bitLength; + } + } else if (val is LogicValue) { + width = val.width; + } else { + throw UnsupportedTypeException(val, [int, BigInt, LogicValue]); + } + + return LogicValue.of(val, width: width); + } /// Constructs a [LogicValue] from [val] which could be of a variety of types. /// @@ -261,9 +293,8 @@ abstract class LogicValue implements Comparable { } else if (val == null) { throw LogicValueConstructionException('Cannot construct from `null`.'); } else { - throw LogicValueConstructionException('Unrecognized value type "$val" - ' - 'Unknown type ${val.runtimeType}' - ' cannot be converted to `LogicValue.'); + throw UnsupportedTypeException( + val, [LogicValue, int, BigInt, bool, String, Iterable]); } } @@ -415,6 +446,11 @@ abstract class LogicValue implements Comparable { return const _FilledLogicValue(_LogicValueEnum.zero, 0); } + if (stringRepresentation.contains(RegExp('[^01xz]'))) { + throw LogicValueConstructionException( + 'Invalid characters found, must only contain 0, 1, x, and z.'); + } + final valueString = _valueString(stringRepresentation); final invalidString = _invalidString(stringRepresentation); final width = stringRepresentation.length; @@ -762,7 +798,7 @@ abstract class LogicValue implements Comparable { /// Converts valid a [LogicValue] to an [int]. /// - /// Throws an `Exception` if not [isValid] or the width doesn't fit in + /// Throws an `Exception` if not [isValid] or the value doesn't fit in /// an [int]. int toInt(); @@ -842,29 +878,35 @@ abstract class LogicValue implements Comparable { LogicValue xor(); /// Addition operation. - /// - // ignore: avoid_dynamic_calls - LogicValue operator +(dynamic other) => _doMath(other, (a, b) => a + b); + LogicValue operator +(dynamic other) => + // ignore: avoid_dynamic_calls + _doMath(other, (a, b) => a + b); /// Subtraction operation. - /// - // ignore: avoid_dynamic_calls - LogicValue operator -(dynamic other) => _doMath(other, (a, b) => a - b); + LogicValue operator -(dynamic other) => + // ignore: avoid_dynamic_calls + _doMath(other, (a, b) => a - b); /// Multiplication operation. - /// - // ignore: avoid_dynamic_calls - LogicValue operator *(dynamic other) => _doMath(other, (a, b) => a * b); + LogicValue operator *(dynamic other) => + // ignore: avoid_dynamic_calls + _doMath(other, (a, b) => a * b); /// Division operation. - /// - // ignore: avoid_dynamic_calls - LogicValue operator /(dynamic other) => _doMath(other, (a, b) => a ~/ b); + LogicValue operator /(dynamic other) => _doMath( + other, + // ignore: avoid_dynamic_calls + (a, b) => a ~/ b, + isDivision: true, + ); /// Modulo operation. - /// - // ignore: avoid_dynamic_calls - LogicValue operator %(dynamic other) => _doMath(other, (a, b) => a % b); + LogicValue operator %(dynamic other) => _doMath( + other, + // ignore: avoid_dynamic_calls + (a, b) => a % b, + isDivision: true, + ); /// Ceil of log base 2 operation. /// @@ -879,9 +921,7 @@ abstract class LogicValue implements Comparable { if (!isValid) { return LogicValue.filled(width, LogicValue.x); } - if (this is BigInt || - this is _BigLogicValue || - (this is _FilledLogicValue && width > _INT_BITS)) { + if (width > _INT_BITS) { final a = toBigInt(); return LogicValue.ofBigInt(op(a, width) as BigInt, width); } else { @@ -898,6 +938,7 @@ abstract class LogicValue implements Comparable { if (a is int) { return a < 0 ? width : (a - 1).bitLength; } + if (a is BigInt) { return a < BigInt.zero ? BigInt.from(width) @@ -905,36 +946,42 @@ abstract class LogicValue implements Comparable { } } - /// Executes mathematical operations between two [LogicValue]s + /// Executes mathematical operations between two [LogicValue]s. /// /// Handles width and bounds checks as well as proper conversion between /// different types of representation. - LogicValue _doMath(dynamic other, dynamic Function(dynamic a, dynamic b) op) { + /// + /// If the math [isDivision], then 64-bit ([_INT_BITS]) operations have some + /// special consideration for two's complement math, so it will use an + /// unsigned [BigInt] for math. + LogicValue _doMath(dynamic other, dynamic Function(dynamic a, dynamic b) op, + {bool isDivision = false}) { if (!(other is int || other is LogicValue || other is BigInt)) { - throw Exception('Improper argument ${other.runtimeType}, should be int,' - ' LogicValue, or BigInt.'); + throw UnsupportedTypeException(other, [int, LogicValue, BigInt]); } + if (other is LogicValue && other.width != width) { - throw Exception('Widths must match, but found "$this" and "$other".'); + throw ValueWidthMismatchException(this, other); } if (!isValid) { return LogicValue.filled(width, LogicValue.x); } + if (other is LogicValue && !other.isValid) { return LogicValue.filled(other.width, LogicValue.x); } - if (width > _INT_BITS || (other is LogicValue && other.width > _INT_BITS)) { + final widthComparison = isDivision ? _INT_BITS - 1 : _INT_BITS; + + if (width > widthComparison || + (other is LogicValue && other.width > widthComparison)) { final a = toBigInt(); final b = other is BigInt ? other : other is int ? BigInt.from(other).toUnsigned(_INT_BITS) - : other is LogicValue - ? other.toBigInt() - : throw Exception( - 'Unexpected big type: ${other.runtimeType}.'); + : (other as LogicValue).toBigInt(); return LogicValue.ofBigInt(op(a, b) as BigInt, width); } else { final a = toInt(); @@ -967,30 +1014,26 @@ abstract class LogicValue implements Comparable { LogicValue pow(dynamic exponent) => _doMath(exponent, _powerOperation); /// Less-than operation. - /// LogicValue operator <(dynamic other) => // ignore: avoid_dynamic_calls _doCompare(other, (a, b) => (a < b) as bool); /// Greater-than operation. - /// LogicValue operator >(dynamic other) => // ignore: avoid_dynamic_calls _doCompare(other, (a, b) => (a > b) as bool); /// Less-than-or-equal operation. - /// LogicValue operator <=(dynamic other) => // ignore: avoid_dynamic_calls _doCompare(other, (a, b) => (a <= b) as bool); /// Greater-than-or-equal operation. - /// LogicValue operator >=(dynamic other) => // ignore: avoid_dynamic_calls _doCompare(other, (a, b) => (a >= b) as bool); - /// Power operation + /// 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 @@ -1014,17 +1057,17 @@ abstract class LogicValue implements Comparable { } } - /// Executes comparison operations between two [LogicValue]s + /// Executes comparison operations between two [LogicValue]s. /// /// Handles width and bounds checks as well as proper conversion between /// different types of representation. LogicValue _doCompare(dynamic other, bool Function(dynamic a, dynamic b) op) { if (!(other is int || other is LogicValue || other is BigInt)) { - throw Exception('Improper arguments ${other.runtimeType},' - ' should be int, LogicValue, or BigInt.'); + throw UnsupportedTypeException(other, [int, LogicValue, BigInt]); } + if (other is LogicValue && other.width != width) { - throw Exception('Widths must match, but found "$this" and "$other"'); + throw ValueWidthMismatchException(this, other); } if (!isValid) { @@ -1042,10 +1085,7 @@ abstract class LogicValue implements Comparable { ? other : other is int ? BigInt.from(other).toUnsigned(_INT_BITS) - : other is LogicValue - ? other.toBigInt() - : throw Exception( - 'Unexpected big type: ${other.runtimeType}.'); + : (other as LogicValue).toBigInt(); } else { if (width < _INT_BITS) { a = toInt(); @@ -1074,30 +1114,80 @@ abstract class LogicValue implements Comparable { /// Logical right-shift operation. LogicValue operator >>>(dynamic shamt) => _shift(shamt, _ShiftType.right); + /// Performs a shift by a huge amount (more than [width]). + LogicValue _shiftHuge(_ShiftType direction) { + if (direction == _ShiftType.arithmeticRight && + this[-1] != LogicValue.zero) { + return LogicValue.filled( + width, this[-1].isValid ? LogicValue.one : LogicValue.x); + } else { + return LogicValue.filled(width, LogicValue.zero); + } + } + /// Performs shift operations in the specified direction LogicValue _shift(dynamic shamt, _ShiftType direction) { if (width == 0) { // ignore: avoid_returning_this return this; } - int shamtInt; + + var shamtNum = shamt; if (shamt is LogicValue) { if (!shamt.isValid) { return LogicValue.filled(width, LogicValue.x); } - shamtInt = shamt.toInt(); - } else if (shamt is int) { - shamtInt = shamt; - } else { - throw Exception('Cannot shift by type ${shamt.runtimeType}.'); + + if (shamt.width > _INT_BITS) { + shamtNum = shamt.toBigInt(); + } else { + shamtNum = shamt.toInt(); + } } - if (direction == _ShiftType.left) { - return _shiftLeft(shamtInt); - } else if (direction == _ShiftType.right) { - return _shiftRight(shamtInt); + + int shamtInt; + if (shamtNum is int) { + shamtInt = shamtNum; + } else if (shamtNum is BigInt) { + if (shamtNum >= BigInt.from(width)) { + // if the shift amount is huge, we can still calculate it + return _shiftHuge(direction); + } + + assert( + shamtNum <= BigInt.from(-1).toUnsigned(_INT_BITS), + 'It should not be possible for the shift amount to be less ' + 'than the width, but more than fits in an int.'); + + assert(shamtNum.isValidInt, + 'Should have returned already if it does not fit.'); + + shamtInt = shamtNum.toInt(); } else { - // if(direction == ShiftType.ArithmeticRight) { - return _shiftArithmeticRight(shamtInt); + throw UnsupportedTypeException(shamt, [int, BigInt, LogicValue]); + } + + if (shamtInt < 0) { + // since we're limited in width to 2^(_INT_BITS-1) (must be positive), + // we know that any negative shift amount must quality as "huge" + return _shiftHuge(direction); + } + + if (shamtInt == 0) { + return this; + } + + if (shamtInt >= width) { + return _shiftHuge(direction); + } + + switch (direction) { + case _ShiftType.left: + return _shiftLeft(shamtInt); + case _ShiftType.right: + return _shiftRight(shamtInt); + case _ShiftType.arithmeticRight: + return _shiftArithmeticRight(shamtInt); } } @@ -1276,4 +1366,18 @@ int _unsignedBinaryParse(String source) { } /// Enum for a [LogicValue]'s value. -enum _LogicValueEnum { zero, one, x, z } +enum _LogicValueEnum { + /// A value of 0. + zero, + + /// A value of 1. + one, + + /// A value of X (contention). + x, + + /// A value of Z (floating). + z; + + bool get isValid => this == zero || this == one; +} diff --git a/lib/src/values/small_logic_value.dart b/lib/src/values/small_logic_value.dart index aaaba70dc..15511fae3 100644 --- a/lib/src/values/small_logic_value.dart +++ b/lib/src/values/small_logic_value.dart @@ -32,7 +32,7 @@ class _SmallLogicValue extends LogicValue { final int _invalid; int get _mask => _maskOfWidth(width); - static final Map _masksOfWidth = {}; + static final Map _masksOfWidth = HashMap(); static int _maskOfWidth(int width) { if (!_masksOfWidth.containsKey(width)) { _masksOfWidth[width] = (1 << width) - 1; @@ -178,26 +178,34 @@ class _SmallLogicValue extends LogicValue { } @override - LogicValue _shiftLeft(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._smallLogicValueOrFilled( - (_value << shamt) & _mask, (_invalid << shamt) & _mask, width); + LogicValue _shiftLeft(int shamt) => LogicValue._smallLogicValueOrFilled( + (_value << shamt) & _mask, (_invalid << shamt) & _mask, width); @override - LogicValue _shiftRight(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._smallLogicValueOrFilled( - _value >> shamt, _invalid >> shamt, width); + LogicValue _shiftRight(int shamt) => LogicValue._smallLogicValueOrFilled( + _value >>> shamt, _invalid >>> shamt, width); @override - LogicValue _shiftArithmeticRight(int shamt) => !isValid - ? _FilledLogicValue(_LogicValueEnum.x, width) - : LogicValue._smallLogicValueOrFilled( - ((_value | (this[width - 1] == LogicValue.one ? ~_mask : 0)) >> - shamt) & - _mask, - _invalid >> shamt, - width); + LogicValue _shiftArithmeticRight(int shamt) { + final upperMostBit = this[-1]; + + var value = + ((_value | (upperMostBit == LogicValue.one ? ~_mask : 0)) >> shamt) & + _mask; + + var invalid = _invalid >> shamt; + + // if uppermost bit is invalid, then turn the shifted bits into X's + if (!upperMostBit.isValid) { + // for affected bits of value: zero out value + value &= _mask >>> shamt; + + // for affected bits of invalid: make sure they are high + invalid |= ~_mask >> shamt; + } + + return LogicValue._smallLogicValueOrFilled(value, invalid, width); + } @override BigInt get _bigIntInvalid => diff --git a/lib/src/values/values.dart b/lib/src/values/values.dart index ef3b9900c..bfb635aab 100644 --- a/lib/src/values/values.dart +++ b/lib/src/values/values.dart @@ -3,6 +3,7 @@ library; +import 'dart:collection'; import 'dart:math' as math; import 'package:meta/meta.dart'; diff --git a/test/gate_test.dart b/test/gate_test.dart index a916c47b2..5293c4ece 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -57,8 +57,9 @@ class UnaryGateTestModule extends Module { } class ShiftTestModule extends Module { - int constantInt; - ShiftTestModule(Logic a, Logic b, {this.constantInt = 3}) + dynamic constant; // int or BigInt + + ShiftTestModule(Logic a, Logic b, {this.constant = 3}) : super(name: 'shifttestmodule') { a = addInput('a', a, width: a.width); b = addInput('b', b, width: b.width); @@ -71,7 +72,7 @@ class ShiftTestModule extends Module { final aLshiftConst = addOutput('a_lshift_const', width: a.width); final aArshiftConst = addOutput('a_arshift_const', width: a.width); - final c = Const(constantInt, width: b.width); + final c = Const(constant, width: b.width); aRshiftB <= a >>> b; aLshiftB <= a << b; aArshiftB <= a >> b; @@ -424,84 +425,192 @@ void main() { expect(simResult, equals(true)); }); - test('lshift logic', () async { - final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010'), 'b': 0}, {'a_lshift_b': bin('010')}), - Vector({'a': bin('010'), 'b': 1}, {'a_lshift_b': bin('100')}), - Vector({'a': bin('010'), 'b': 2}, {'a_lshift_b': bin('000')}), - Vector({'a': bin('010'), 'b': 6}, {'a_lshift_b': bin('000')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); - }); + group('shift', () { + test('lshift logic', () async { + final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010'), 'b': 0}, {'a_lshift_b': bin('010')}), + Vector({'a': bin('010'), 'b': 1}, {'a_lshift_b': bin('100')}), + Vector({'a': bin('010'), 'b': 2}, {'a_lshift_b': bin('000')}), + Vector({'a': bin('010'), 'b': 6}, {'a_lshift_b': bin('000')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); - test('rshift logic', () async { - final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010'), 'b': 0}, {'a_rshift_b': bin('010')}), - Vector({'a': bin('010'), 'b': 1}, {'a_rshift_b': bin('001')}), - Vector({'a': bin('010'), 'b': 2}, {'a_rshift_b': bin('000')}), - Vector({'a': bin('010'), 'b': 6}, {'a_rshift_b': bin('000')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); - }); + test('rshift logic', () async { + final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010'), 'b': 0}, {'a_rshift_b': bin('010')}), + Vector({'a': bin('010'), 'b': 1}, {'a_rshift_b': bin('001')}), + Vector({'a': bin('010'), 'b': 2}, {'a_rshift_b': bin('000')}), + Vector({'a': bin('010'), 'b': 6}, {'a_rshift_b': bin('000')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); - test('arshift logic', () async { - final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010'), 'b': 0}, {'a_arshift_b': bin('010')}), - Vector({'a': bin('010'), 'b': 1}, {'a_arshift_b': bin('001')}), - Vector({'a': bin('010'), 'b': 2}, {'a_arshift_b': bin('000')}), - Vector({'a': bin('010'), 'b': 6}, {'a_arshift_b': bin('000')}), - Vector({'a': bin('110'), 'b': 0}, {'a_arshift_b': bin('110')}), - Vector({'a': bin('110'), 'b': 6}, {'a_arshift_b': bin('111')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); - }); + test('arshift logic', () async { + final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8)); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010'), 'b': 0}, {'a_arshift_b': bin('010')}), + Vector({'a': bin('010'), 'b': 1}, {'a_arshift_b': bin('001')}), + Vector({'a': bin('010'), 'b': 2}, {'a_arshift_b': bin('000')}), + Vector({'a': bin('010'), 'b': 6}, {'a_arshift_b': bin('000')}), + Vector({'a': bin('110'), 'b': 0}, {'a_arshift_b': bin('110')}), + Vector({'a': bin('110'), 'b': 6}, {'a_arshift_b': bin('111')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); - test('lshift int', () async { - final gtm = - ShiftTestModule(Logic(width: 3), Logic(width: 8), constantInt: 1); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010')}, {'a_lshift_const': bin('100')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); - }); + test('lshift int', () async { + final gtm = + ShiftTestModule(Logic(width: 3), Logic(width: 8), constant: 1); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010')}, {'a_lshift_const': bin('100')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); - test('rshift int', () async { - final gtm = - ShiftTestModule(Logic(width: 3), Logic(width: 8), constantInt: 1); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010')}, {'a_rshift_const': bin('001')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); - }); + test('rshift int', () async { + final gtm = + ShiftTestModule(Logic(width: 3), Logic(width: 8), constant: 1); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010')}, {'a_rshift_const': bin('001')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); - test('arshift int', () async { - final gtm = - ShiftTestModule(Logic(width: 3), Logic(width: 8), constantInt: 1); - await gtm.build(); - final vectors = [ - Vector({'a': bin('010')}, {'a_arshift_const': bin('001')}), - ]; - await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); + test('arshift int', () async { + final gtm = + ShiftTestModule(Logic(width: 3), Logic(width: 8), constant: 1); + await gtm.build(); + final vectors = [ + Vector({'a': bin('010')}, {'a_arshift_const': bin('001')}), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + test('large logic shifted by small bus', () async { + final gtm = + ShiftTestModule(Logic(width: 200), Logic(width: 60), constant: 70); + await gtm.build(); + final vectors = [ + Vector({ + 'a': BigInt.one << 100, + 'b': 70 + }, { + 'a_lshift_const': BigInt.one << 170, + 'a_rshift_const': BigInt.one << 30, + 'a_arshift_const': BigInt.one << 30, + 'a_lshift_b': BigInt.one << 170, + 'a_rshift_b': BigInt.one << 30, + 'a_arshift_b': BigInt.one << 30, + }), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + test('large logic shifted by large bus', () async { + final gtm = ShiftTestModule(Logic(width: 200), Logic(width: 200), + constant: BigInt.from(70)); + await gtm.build(); + final vectors = [ + Vector({ + 'a': BigInt.one << 100, + 'b': 70 + }, { + 'a_lshift_const': BigInt.one << 170, + 'a_rshift_const': BigInt.one << 30, + 'a_arshift_const': BigInt.one << 30, + 'a_lshift_b': BigInt.one << 170, + 'a_rshift_b': BigInt.one << 30, + 'a_arshift_b': BigInt.one << 30, + }), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + test('small logic shifted by large bus', () async { + final gtm = + ShiftTestModule(Logic(width: 40), Logic(width: 200), constant: 10); + await gtm.build(); + final vectors = [ + Vector({ + 'a': BigInt.one << 20, + 'b': 10 + }, { + 'a_lshift_const': BigInt.one << 30, + 'a_rshift_const': BigInt.one << 10, + 'a_arshift_const': BigInt.one << 10, + 'a_lshift_b': BigInt.one << 30, + 'a_rshift_b': BigInt.one << 10, + 'a_arshift_b': BigInt.one << 10, + }), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + test('large logic shifted by huge value on large bus', () async { + final gtm = ShiftTestModule(Logic(width: 200), Logic(width: 200), + constant: BigInt.one << 100); + await gtm.build(); + final vectors = [ + Vector({ + 'a': BigInt.one << 199 | BigInt.one << 100, + 'b': BigInt.one << 100 + }, { + 'a_lshift_const': 0, + 'a_rshift_const': 0, + 'a_arshift_const': LogicValue.filled(200, LogicValue.one), + 'a_lshift_b': 0, + 'a_rshift_b': 0, + 'a_arshift_b': LogicValue.filled(200, LogicValue.one), + }), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + test('small logic shifted by huge value on large bus', () async { + final gtm = ShiftTestModule(Logic(width: 40), Logic(width: 200), + constant: BigInt.one << 100); + await gtm.build(); + final vectors = [ + Vector({ + 'a': BigInt.one << 200 | BigInt.one << 100, + 'b': BigInt.one << 100 + }, { + 'a_lshift_const': 0, + 'a_rshift_const': 0, + 'a_arshift_const': LogicValue.filled(40, LogicValue.one), + 'a_lshift_b': 0, + 'a_rshift_b': 0, + 'a_arshift_b': LogicValue.filled(40, LogicValue.one), + }), + ]; + await SimCompare.checkFunctionalVector(gtm, vectors); + SimCompare.checkIverilogVector(gtm, vectors); + }); + + // test plan: + // - large logic shifted by int + // - large logic shifted by (big) BigInt + // - large logic shifted by large logic + // - small logic shifted by large logic + // excessively large number (>64bits) }); test('And2Gate single bit', () async { diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart index 1ce278b7c..1d5d8103f 100644 --- a/test/logic_value_test.dart +++ b/test/logic_value_test.dart @@ -94,9 +94,23 @@ void main() { expect( [].swizzle(), equals(LogicValue.ofBigInt(BigInt.two, 0))); }); + test('big unsigned int string', () { expect(LogicValue.ofString('1' * 64), equals(LogicValue.ofInt(-1, 64))); }); + + test('-1 int vs. 64 1s for big width', () { + expect(LogicValue.ofInt(-1, 100), + LogicValue.ofBigInt((BigInt.one << 64) - BigInt.one, 100)); + }); + + test('invalid string ofString', () { + expect(() => LogicValue.ofString('-1'), + throwsA(isA())); + expect(() => LogicValue.ofString('a'), + throwsA(isA())); + }); + test('unary', () { expect(LogicValue.one.isValid, equals(true)); expect(LogicValue.zero.isValid, equals(true)); @@ -353,6 +367,24 @@ void main() { equals(BigInt.parse('36893488147419103231'))); }); + group('large-width toInt', () { + test('big', () { + expect(LogicValue.ofInt(1234, 100).toInt(), 1234); + expect(() => LogicValue.ofBigInt(-BigInt.two, 100).toInt(), + throwsA(isA())); + expect(() => LogicValue.ofString('x' * 10 + '0' * 90).toInt(), + throwsA(isA())); + }); + + test('filled', () { + expect(LogicValue.ofInt(0, 100).toInt(), 0); + expect(() => LogicValue.ofBigInt(-BigInt.one, 100).toInt(), + throwsA(isA())); + expect(() => LogicValue.ofString('z' * 100).toInt(), + throwsA(isA())); + }); + }); + test('properties+indexing', () { expect( // index - LSb @@ -460,8 +492,10 @@ void main() { LogicValue.ofString('zzz1').isFloating, equals(false)); }); + }); - test('shifts', () { + group('shifts', () { + test('basic', () { expect( // sll LogicValue.ofString('1111') << 2, @@ -475,7 +509,283 @@ void main() { LogicValue.ofString('1111') >>> 2, equals(LogicValue.ofString('0011'))); }); + + test('small int boundary shift right logical', () { + // at boundary + expect((LogicValue.ofInt(-5, 64) >>> 28).toInt(), 0xfffffffff); + expect(LogicValue.ofString('x' * 50 + '0' * 14) >>> 20, + LogicValue.ofString('0' * 20 + 'x' * 44)); + expect(LogicValue.ofString('z' * 50 + '0' * 14) >>> 20, + LogicValue.ofString('0' * 20 + 'z' * 44)); + + // below boundary + expect((LogicValue.ofInt(-5, 60) >>> 28).toInt(), 0xffffffff); + expect(LogicValue.ofString('x' * 50 + '0' * 13) >>> 20, + LogicValue.ofString('0' * 20 + 'x' * 43)); + expect(LogicValue.ofString('z' * 50 + '0' * 13) >>> 20, + LogicValue.ofString('0' * 20 + 'z' * 43)); + }); + + test('small int boundary shift left logical', () { + // at boundary + expect((LogicValue.ofInt(-1, 64) << 20).toInt(), -1 << 20); + expect(LogicValue.ofString('0' * 14 + 'x' * 50) << 20, + LogicValue.ofString('x' * 44 + '0' * 20)); + expect(LogicValue.ofString('0' * 14 + 'z' * 50) << 20, + LogicValue.ofString('z' * 44 + '0' * 20)); + + // below boundary + expect((LogicValue.ofInt(-1, 32) << 24).toInt(), 0xff000000); + expect(LogicValue.ofString('0' * 14 + 'x' * 49) << 20, + LogicValue.ofString('x' * 43 + '0' * 20)); + expect(LogicValue.ofString('0' * 14 + 'z' * 49) << 20, + LogicValue.ofString('z' * 43 + '0' * 20)); + }); + + test('small int boundary shift right arithmetic', () { + // at boundary + expect((LogicValue.ofInt(0xffff, 64) >> 8).toInt(), 0xff); + expect((LogicValue.ofInt(-5, 64) >> 20).toInt(), -1); + expect(LogicValue.ofString('x' * 50 + '0' * 14) >> 20, + LogicValue.filled(64, LogicValue.x)); + expect(LogicValue.ofString('x' * 50 + '0' * 14) >> 10, + LogicValue.ofString('x' * 60 + '0' * 4)); + expect(LogicValue.ofString('z' * 50 + '0' * 14) >> 20, + LogicValue.ofString('x' * 20 + 'z' * 44)); + + // below boundary + expect((LogicValue.ofInt(0xffff, 63) >> 8).toInt(), 0xff); + expect((LogicValue.ofInt(-5, 63) >> 20).toInt(), -1 >>> 1); + expect(LogicValue.ofString('x' * 50 + '0' * 13) >> 20, + LogicValue.filled(63, LogicValue.x)); + expect(LogicValue.ofString('x' * 50 + '0' * 13) >> 10, + LogicValue.ofString('x' * 60 + '0' * 3)); + expect(LogicValue.ofString('z' * 50 + '0' * 13) >> 20, + LogicValue.ofString('x' * 20 + 'z' * 43)); + }); + + test('big shift right logical', () { + expect((LogicValue.ofInt(-5, 65) >>> 28).toInt(), 0xfffffffff); + expect(LogicValue.ofBigInt(-BigInt.two, 128) >>> 96, + LogicValue.ofInt(0xffffffff, 128)); + expect(LogicValue.ofString('x' * 51 + '0' * 14) >>> 20, + LogicValue.ofString('0' * 20 + 'x' * 45)); + expect(LogicValue.ofString('z' * 51 + '0' * 14) >>> 20, + LogicValue.ofString('0' * 20 + 'z' * 45)); + }); + + test('big shift left logical', () { + expect((LogicValue.ofInt(-1, 65) << 20).toBigInt(), + (-BigInt.one).toUnsigned(65 - 20) << 20); + expect((LogicValue.ofBigInt(-BigInt.two, 128) << 20).toBigInt(), + (-BigInt.two).toUnsigned(128 - 20) << 20); + expect(LogicValue.ofString('0' * 15 + 'x' * 50) << 20, + LogicValue.ofString('x' * 45 + '0' * 20)); + expect(LogicValue.ofString('0' * 15 + 'z' * 50) << 20, + LogicValue.ofString('z' * 45 + '0' * 20)); + }); + + test('big shift right arithmetic', () { + expect((LogicValue.ofInt(0xffff, 65) >> 8).toInt(), 0xff); + expect((LogicValue.ofInt(-5, 65) >> 20).toInt(), -5 >>> 20); + expect(LogicValue.ofBigInt(-BigInt.two, 128) >> 3, + LogicValue.ofBigInt(-BigInt.one, 128)); + expect(LogicValue.ofString('x' * 51 + '0' * 14) >> 20, + LogicValue.filled(65, LogicValue.x)); + expect(LogicValue.ofString('x' * 51 + '0' * 14) >> 10, + LogicValue.ofString('x' * 61 + '0' * 4)); + expect(LogicValue.ofString('z' * 51 + '0' * 14) >> 20, + LogicValue.ofString('x' * 20 + 'z' * 45)); + }); + + test('filled shift right logical', () { + for (var width = 62; width < 67; width++) { + expect(LogicValue.filled(width, LogicValue.one) >>> 20, + LogicValue.ofString('0' * 20 + '1' * (width - 20))); + expect(LogicValue.filled(width, LogicValue.zero) >>> 20, + LogicValue.ofString('0' * width)); + expect(LogicValue.filled(width, LogicValue.x) >>> 20, + LogicValue.ofString('0' * 20 + 'x' * (width - 20))); + expect(LogicValue.filled(width, LogicValue.z) >>> 20, + LogicValue.ofString('0' * 20 + 'z' * (width - 20))); + } + }); + + test('filled shift left logical', () { + for (var width = 62; width < 67; width++) { + expect(LogicValue.filled(width, LogicValue.one) << 20, + LogicValue.ofString('1' * (width - 20) + '0' * 20)); + expect(LogicValue.filled(width, LogicValue.zero) << 20, + LogicValue.ofString('0' * width)); + expect(LogicValue.filled(width, LogicValue.x) << 20, + LogicValue.ofString('x' * (width - 20) + '0' * 20)); + expect(LogicValue.filled(width, LogicValue.z) << 20, + LogicValue.ofString('z' * (width - 20) + '0' * 20)); + } + }); + + test('filled shift right arithmetic', () { + for (var width = 62; width < 67; width++) { + expect(LogicValue.filled(width, LogicValue.one) >> 20, + LogicValue.ofString('1' * width)); + expect(LogicValue.filled(width, LogicValue.zero) >> 20, + LogicValue.ofString('0' * width)); + expect(LogicValue.filled(width, LogicValue.x) >> 20, + LogicValue.ofString('x' * width)); + expect(LogicValue.filled(width, LogicValue.z) >> 20, + LogicValue.ofString('x' * 20 + 'z' * (width - 20))); + } + }); + + test('more than width', () { + expect(LogicValue.ofInt(10, 50) >> 50, LogicValue.ofInt(0, 50)); + expect(LogicValue.ofInt(10, 64) >> 65, LogicValue.ofInt(0, 64)); + expect(LogicValue.ofInt(10, 80) >> 100, LogicValue.ofInt(0, 80)); + expect( + LogicValue.ofString('x' * 80) >> 100, LogicValue.ofString('x' * 80)); + expect( + LogicValue.ofString('z' * 80) >> 100, LogicValue.ofString('x' * 80)); + }); + + test('huge left', () { + expect((LogicValue.ofInt(45, 32) << -1).toInt(), 0); + expect((LogicValue.ofInt(45, 64) << -4).toInt(), 0); + expect((LogicValue.ofInt(45, 80) << -4).toInt(), 0); + expect((LogicValue.ofInt(-39, 80) << -4).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) << -4).toInt(), 0); + + expect((LogicValue.ofInt(45, 32) << BigInt.from(-1)).toInt(), 0); + expect((LogicValue.ofInt(45, 64) << BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(45, 80) << BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-39, 80) << BigInt.from(-7)).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) << BigInt.from(-9)).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) << (BigInt.one << 80)).toInt(), 0); + }); + + test('huge right', () { + expect((LogicValue.ofInt(45, 32) >>> -4).toInt(), 0); + expect((LogicValue.ofInt(45, 64) >>> -4).toInt(), 0); + expect((LogicValue.ofInt(45, 80) >>> -4).toInt(), 0); + expect((LogicValue.ofInt(-39, 80) >>> -4).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) >>> -4).toInt(), 0); + + expect((LogicValue.ofInt(45, 32) >>> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(45, 64) >>> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(45, 80) >>> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-39, 80) >>> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) >>> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-39, 60) >>> (BigInt.one << 80)).toInt(), 0); + }); + + test('huge right arithmetic', () { + expect((LogicValue.ofInt(45, 32) >> -1).toInt(), 0); + expect((LogicValue.ofInt(-45, 64) >> -12).toInt(), -1); + expect((LogicValue.ofInt(-45, 8) >> -18).toInt(), 0xff); + expect((LogicValue.ofInt(45, 80) >> -1).toInt(), 0); + expect((LogicValue.ofInt(-45, 128) >> -18).and().toBool(), false); + expect((LogicValue.ofBigInt(BigInt.from(-45), 128) >> -18).and().toBool(), + true); + + expect((LogicValue.ofInt(45, 32) >> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-45, 64) >> BigInt.from(-4)).toInt(), -1); + expect((LogicValue.ofInt(-45, 8) >> BigInt.from(-4)).toInt(), 0xff); + expect((LogicValue.ofInt(45, 80) >> BigInt.from(-4)).toInt(), 0); + expect((LogicValue.ofInt(-45, 128) >> BigInt.from(-4)).and().toBool(), + false); + expect( + (LogicValue.ofBigInt(BigInt.from(-45), 128) >> BigInt.from(-4)) + .and() + .toBool(), + true); + expect((LogicValue.ofInt(45, 80) >> (BigInt.one << 80)).toInt(), 0); + expect((LogicValue.ofInt(-45, 8) >> (BigInt.one << 80)).toInt(), 0xff); + }); + + group('invalid values', () { + test('shift left', () { + expect(LogicValue.filled(10, LogicValue.x) << 3, + LogicValue.ofString('xxxxxxx000')); + expect(LogicValue.filled(10, LogicValue.x) << (BigInt.one << 80), + LogicValue.ofString('0000000000')); + }); + + test('shift right', () { + expect(LogicValue.filled(10, LogicValue.x) >>> 3, + LogicValue.ofString('000xxxxxxx')); + expect(LogicValue.filled(10, LogicValue.x) >>> (BigInt.one << 80), + LogicValue.ofString('0000000000')); + }); + + test('shift right arithmetic', () { + expect(LogicValue.filled(10, LogicValue.x) >> 3, + LogicValue.filled(10, LogicValue.x)); + expect(LogicValue.filled(10, LogicValue.z) >> 3, + LogicValue.ofString('xxxzzzzzzz')); + expect(LogicValue.filled(10, LogicValue.x) >> (BigInt.one << 80), + LogicValue.filled(10, LogicValue.x)); + expect(LogicValue.filled(10, LogicValue.z) >> (BigInt.one << 80), + LogicValue.filled(10, LogicValue.x)); + }); + }); + + test('shift by 0', () { + expect(LogicValue.filled(10, LogicValue.x) >> 0, + LogicValue.filled(10, LogicValue.x)); + }); + + test('invalid shamt', () { + expect( + LogicValue.filled(10, LogicValue.one) >> LogicValue.ofString('0x10'), + LogicValue.filled(10, LogicValue.x)); + }); + + test('unsupported shamt type', () { + expect(() => LogicValue.filled(10, LogicValue.one) >> Logic(), + throwsA(isA())); + }); + + test('example large shifts', () { + expect((LogicValue.filled(64, LogicValue.one) >> 2).toInt(), + equals(-1 >> 2)); + expect( + LogicValue.filled(65, LogicValue.one) >>> 10, + equals([ + LogicValue.filled(10, LogicValue.zero), + LogicValue.filled(55, LogicValue.one) + ].swizzle())); + }); }); + + group('infer width', () { + test('int', () { + expect(LogicValue.ofInferWidth(45).width, 6); + }); + + test('bigint', () { + expect( + LogicValue.ofInferWidth((BigInt.one << 70) + BigInt.two).width, 71); + }); + + test('negative int', () { + expect(() => LogicValue.ofInferWidth(-345).width, + throwsA(isA())); + }); + + test('negative BigInt', () { + expect(() => LogicValue.ofInferWidth(-BigInt.one), + throwsA(isA())); + }); + + test('logicvalue', () { + expect(LogicValue.ofInferWidth(LogicValue.ofString('01010')).width, 5); + }); + + test('unsupported', () { + expect(() => LogicValue.ofInferWidth(Logic()).width, + throwsA(isA())); + }); + }); + group('comparison operations', () { test('equalsWithDontCare', () { expect( diff --git a/test/logic_value_width_test.dart b/test/logic_value_width_test.dart index efa3123e1..196262311 100644 --- a/test/logic_value_width_test.dart +++ b/test/logic_value_width_test.dart @@ -44,6 +44,11 @@ void main() { LogicValue.ofInt(0, 128)); }); + test('mod3 sizes', () { + expect((LogicValue.ofInt(-5, 64) % 3).toInt(), + (LogicValue.ofInt(-5, 80) % 3).toInt()); + }); + group('values test', () { for (final len in [63, 64, 65, 66, 67]) { final sslv = LogicValue.ofInt(4, len); // small Int hold Big @@ -88,24 +93,20 @@ void main() { expect(fblv > bblv, LogicValue.one); }); - test('big math len=$len', () { - expect(sslv + fslv, LogicValue.ofInt(2, len)); - expect(sslv - fslv, LogicValue.ofInt(6, len)); - expect(fslv - sslv, LogicValue.ofInt(-6, len)); - - expect(sslv * fslv, LogicValue.ofInt(-8, len)); - - expect(sslv + fslv, LogicValue.ofBigInt(BigInt.from(2), len)); - expect(sslv - fslv, LogicValue.ofBigInt(BigInt.from(6), len)); - expect(fslv - sslv, LogicValue.ofBigInt(BigInt.from(-6), len)); - - expect(fslv * sslv, LogicValue.ofBigInt(BigInt.from(-8), len)); + test('cross compare len=$len', () { + if (len <= 64) { + expect(bslv.eq(bblv), LogicValue.one); + } else { + expect(bslv < bblv, LogicValue.one); + } + }); + test('big math len=$len', () { expect(sblv + fblv, LogicValue.ofInt(2, len)); expect(sblv - fblv, LogicValue.ofInt(6, len)); - expect(fblv - sblv, LogicValue.ofInt(-6, len)); + expect(fblv - sblv, LogicValue.ofBigInt(BigInt.from(-6), len)); - expect(sblv * fblv, LogicValue.ofInt(-8, len)); + expect(sblv * fblv, LogicValue.ofBigInt(BigInt.from(-8), len)); expect(sblv + fblv, LogicValue.ofBigInt(BigInt.from(2), len)); expect(sblv - fblv, LogicValue.ofBigInt(BigInt.from(6), len)); @@ -131,7 +132,7 @@ void main() { }); test('clog test len=$len', () { - final negnum = LogicValue.ofInt(-1, len); + final negnum = LogicValue.ofBigInt(-BigInt.one, len); expect(negnum.clog2(), LogicValue.ofInt(len, len)); for (final l in [1, 2, 3]) { expect((negnum >>> l).clog2(), LogicValue.ofInt(len - l, len));