diff --git a/README.md b/README.md index 586b2e203..68583f4ff 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ Combinational([ c < 0, d < 1, ], - conditionalType: ConditionalType.Unique + conditionalType: ConditionalType.unique ), CaseZ([b,a].swizzle(),[ CaseItem(Const(LogicValue.ofString('z1')), [ @@ -496,7 +496,7 @@ Combinational([ ], defaultItem: [ e < 0, ], - conditionalType: ConditionalType.Priority + conditionalType: ConditionalType.priority ) ]); ``` diff --git a/benchmark/logic_value_of_benchmark.dart b/benchmark/logic_value_of_benchmark.dart index 8041ce075..2c79d4cc3 100644 --- a/benchmark/logic_value_of_benchmark.dart +++ b/benchmark/logic_value_of_benchmark.dart @@ -33,7 +33,7 @@ class LogicValueOfBenchmark extends BenchmarkBase { @override void run() { - LogicValue.of(toOf); + LogicValue.ofIterable(toOf); } } diff --git a/doc/user_guide/_docs/logic-arrays.md b/doc/user_guide/_docs/logic-arrays.md new file mode 100644 index 000000000..f22e75456 --- /dev/null +++ b/doc/user_guide/_docs/logic-arrays.md @@ -0,0 +1,44 @@ +--- +title: "Logic Arrays" +permalink: /docs/logic-arrays/ +last_modified_at: 2022-6-5 +toc: true +--- + +A `LogicArray` is a type of `LogicStructure` that mirrors multi-dimensional arrays in hardware languages like SystemVerilog. In ROHD, the `LogicArray` type inherits a lot of functionality from `LogicStructure`, so it can behave like a `Logic` where it makes sense or be individually referenced in other places. + +`LogicArray`s can be constructed easily using the constructor: + +```dart +// A 1D array with ten 8-bit elements. +LogicArray([10], 8); + +// A 4x3 2D array, with four arrays, each with three 2-bit elements. +LogicArray([4, 3], 2, name: 'array4x3'); + +// A 5x5x5 3D array, with 125 total elements, each 128 bits. +LogicArray([5, 5, 5], 128); +``` + +As long as the total width of a `LogicArray` and another type of `Logic` (including `Logic`, `LogicStructure`, and another `LogicArray`) are the same, assignments and bitwise operations will work in per-element order. This means you can assign two `LogicArray`s of different dimensions to each other as long as the total width matches. + +## Unpacked arrays + +In SystemVerilog, there is a concept of "packed" vs. "unpacked" arrays which have different use cases and capabilities. In ROHD, all arrays act the same and you get the best of both worlds. You can indicate when constructing a `LogicArray` that some number of the dimensions should be "unpacked" as a hint to `Synthesizer`s. Marking an array with a non-zero `numUnpackedDimensions`, for example, will make that many of the dimensions "unpacked" in generated SystemVerilog signal declarations. + +```dart +// A 4x3 2D array, with four arrays, each with three 2-bit elements. +// The first dimension (4) will be unpacked. +LogicArray( + [4, 3], + 2, + name: 'array4x3w1unpacked', + numUnpackedDimensions: 1, +); +``` + +## Array ports + +You can declare ports of `Module`s as being arrays (including with some dimensions "unpacked") using `addInputArray` and `addOutputArray`. Note that these do _not_ automatically do validation that the dimensions, element width, number of unpacked dimensions, etc. are equal between the port and the original signal. As long as the overall width matches, the assignment will be clean. + +Array ports in generated SystemVerilog will match dimensions (including unpacked) as specified when the port is created. diff --git a/doc/user_guide/_docs/logic-structures.md b/doc/user_guide/_docs/logic-structures.md new file mode 100644 index 000000000..dd1ff2974 --- /dev/null +++ b/doc/user_guide/_docs/logic-structures.md @@ -0,0 +1,68 @@ +--- +title: "Logic Structures" +permalink: /docs/logic-structures/ +last_modified_at: 2022-6-5 +toc: true +--- + +A `LogicStructure` is a useful way to group or bundle related `Logic` signals together. They operate in a similar way to "`packed` `structs`" in SystemVerilog, or a `class` containing multiple `Logic`s in ROHD, but with some important differences. + +**`LogicStructure`s will _not_ convert to `struct`s in generated SystemVerilog.** They are purely a way to deal with signals during generation time in ROHD. + +**`LogicStructure`s can be used anywhere a `Logic` can be**. This means you can assign one structure to another structure, or inter-assign between normal signals and structures. As long as the overall width matches, the assignment will work. The order of assignment of bits is based on the order of the `elements` in the structure. + +**Elements within a `LogicStructure` can be individually assigned.** This is a notable difference from individual bits of a plain `Logic` where you'd have to use something like `withSet` to effectively modify bits within a signal. + +`LogicArray`s are a type of `LogicStructure` and thus inherit these behavioral traits. + +## Using `LogicStructure` to group signals + +The simplest way to use a `LogicStructure` is to just use its constructor, which requires a collection of `Logic`s. + +For example, if you wanted to bundle together a `ready` and a `valid` signal together into one structure, you could do this: + +```dart +final rvStruct = LogicStructure([Logic(name: 'ready'), Logic(name: 'valid')]); +``` + +You could now assign this like any other `Logic` all together: + +```dart +Logic ready, valid; +rvStruct <= [ready, valid].rswizzle(); +``` + +Or you can assign individual `elements`: + +```dart +rvStruct.elements[0] <= ready; +rvStruct.elements[1] <= valid; +``` + +## Making your own structure + +Referencing elements by index is often not ideal for named signals. We can do better by building our own structure that inherits from `LogicStructure`. + +```dart +class ReadyValidStruct extends LogicStructure { + final Logic ready; + final Logic valid; + + factory ReadyValidStruct() => MyStruct._( + Logic(name: 'ready'), + Logic(name: 'valid'), + ); + + ReadyValidStruct._(this.ready, this.valid) + : super([ready, valid], name: 'readyValid'); + + @override + LogicStructure clone({String? name}) => ReadyValidStruct(); +} +``` + +Here we've built a class that has `ready` and `valid` as fields, so we can reference those instead of by element index. We use some tricks with `factory`s to make this easier to work with. + +We override the `clone` function so that we can make a duplicate structure of the same type. + +There's a lot more that can be done with a custom class like this, but this is a good start. There are places where it may even make sense to prefer a custom `LogicStructure` to an `Interface`. diff --git a/example/fir_filter.dart b/example/fir_filter.dart index abbbfe7d9..1212b4eef 100644 --- a/example/fir_filter.dart +++ b/example/fir_filter.dart @@ -71,7 +71,7 @@ class FirFilter extends Module { ], orElse: [out < 0] + // Set all 'z' to zero. - List.generate(depth, (index) => z[index] < 0)) + List.generate(depth, (index) => z[index] < 0)) ]); } } diff --git a/example/oven_fsm.dart b/example/oven_fsm.dart index 7bee071bd..679e3ec80 100644 --- a/example/oven_fsm.dart +++ b/example/oven_fsm.dart @@ -200,7 +200,9 @@ Future main({bool noPrint = false}) async { // // Check on https://mermaid.js.org/intro/ to view the diagram generated. // If you are using vscode, you can download the mermaid extension. - oven.ovenStateMachine.generateDiagram(outputPath: 'oven_fsm.md'); + if (!noPrint) { + oven.ovenStateMachine.generateDiagram(outputPath: 'oven_fsm.md'); + } // Before we can simulate or generate code with the counter, we need // to build it. diff --git a/lib/rohd.dart b/lib/rohd.dart index bee7965e8..fb8e40046 100644 --- a/lib/rohd.dart +++ b/lib/rohd.dart @@ -3,9 +3,9 @@ export 'src/external.dart'; export 'src/interface.dart'; -export 'src/logic.dart'; export 'src/module.dart'; export 'src/modules/modules.dart'; +export 'src/signals/signals.dart'; export 'src/simulator.dart'; export 'src/state_machine.dart'; export 'src/swizzle.dart'; diff --git a/lib/src/collections/iterable_removable_queue.dart b/lib/src/collections/iterable_removable_queue.dart index bd0a561bc..d91d1178e 100644 --- a/lib/src/collections/iterable_removable_queue.dart +++ b/lib/src/collections/iterable_removable_queue.dart @@ -24,7 +24,7 @@ class IterableRemovableQueue { /// Adds a new item to the end of the queue. void add(T item) { final newElement = _IterableRemovableElement(item); - if (_first == null) { + if (isEmpty) { _first = newElement; _last = _first; } else { diff --git a/lib/src/exceptions/conditionals/signal_redriven_exception.dart b/lib/src/exceptions/conditionals/signal_redriven_exception.dart index 0d36d5f2c..8cd0b1b98 100644 --- a/lib/src/exceptions/conditionals/signal_redriven_exception.dart +++ b/lib/src/exceptions/conditionals/signal_redriven_exception.dart @@ -18,7 +18,7 @@ class SignalRedrivenException extends RohdException { /// with default error [message]. /// /// Creates a [SignalRedrivenException] with an optional error [message]. - SignalRedrivenException(String signals, + SignalRedrivenException(Iterable signals, [String message = 'Sequential drove the same signal(s) multiple times: ']) - : super(message + signals); + : super(message + signals.toString()); } diff --git a/lib/src/exceptions/logic/logic_construction_exception.dart b/lib/src/exceptions/logic/logic_construction_exception.dart new file mode 100644 index 000000000..bb6c2dc1e --- /dev/null +++ b/lib/src/exceptions/logic/logic_construction_exception.dart @@ -0,0 +1,21 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_construction_exception.dart +// An exception thrown when a logical signal fails to construct. +// +// 2023 June 1 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/rohd_exception.dart'; + +/// An exception that thrown when a [Logic] is connecting to itself. +class LogicConstructionException extends RohdException { + /// A message describing why the construction failed. + final String reason; + + /// Creates an exception when a [Logic] is trying to connect itself. + LogicConstructionException(this.reason) + : super('Failed to construct signal: $reason'); +} diff --git a/lib/src/exceptions/logic/logic_exceptions.dart b/lib/src/exceptions/logic/logic_exceptions.dart index ae89ff1a9..e08fb1ad9 100644 --- a/lib/src/exceptions/logic/logic_exceptions.dart +++ b/lib/src/exceptions/logic/logic_exceptions.dart @@ -2,5 +2,7 @@ /// SPDX-License-Identifier: BSD-3-Clause export 'invalid_multiplier_exception.dart'; +export 'logic_construction_exception.dart'; export 'put_exception.dart'; export 'self_connecting_logic_exception.dart'; +export 'signal_width_mismatch_exception.dart'; diff --git a/lib/src/exceptions/logic/signal_width_mismatch_exception.dart b/lib/src/exceptions/logic/signal_width_mismatch_exception.dart new file mode 100644 index 000000000..806ec7f5c --- /dev/null +++ b/lib/src/exceptions/logic/signal_width_mismatch_exception.dart @@ -0,0 +1,21 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// port_width_mismatch_exception.dart +// Definition for exception when a signal has the wrong width. +// +// 2023 June 2 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/rohd_exception.dart'; + +/// An [Exception] thrown when a signal has the wrong width. +class SignalWidthMismatchException extends RohdException { + /// Constructs a new [Exception] for when a signal has the wrong width. + SignalWidthMismatchException(Logic signal, int expectedWidth, + {String additionalMessage = ''}) + : super('Signal ${signal.name} has the wrong width.' + ' Expected $expectedWidth but found ${signal.width}.' + ' $additionalMessage'); +} diff --git a/lib/src/exceptions/logic_value/logic_value_construction_exception.dart b/lib/src/exceptions/logic_value/logic_value_construction_exception.dart new file mode 100644 index 000000000..3ad028d8f --- /dev/null +++ b/lib/src/exceptions/logic_value/logic_value_construction_exception.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_value_construction_exception.dart +// An exception that thrown when a signal failes to `put`. +// +// 2023 May 1 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/rohd_exception.dart'; + +/// An exception that thrown when a [LogicValue] cannot be properly constructed. +class LogicValueConstructionException extends RohdException { + /// Creates an exception for when a construction of a `LogicValue` fails. + LogicValueConstructionException(String message) + : super('Failed to construct `LogicValue`: $message'); +} diff --git a/lib/src/exceptions/logic_value/logic_value_exceptions.dart b/lib/src/exceptions/logic_value/logic_value_exceptions.dart index 25f4acacc..e2d4a37a9 100644 --- a/lib/src/exceptions/logic_value/logic_value_exceptions.dart +++ b/lib/src/exceptions/logic_value/logic_value_exceptions.dart @@ -1,4 +1,5 @@ -/// Copyright (C) 2023 Intel Corporation -/// SPDX-License-Identifier: BSD-3-Clause +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause export 'invalid_truncation_exception.dart'; +export 'logic_value_construction_exception.dart'; diff --git a/lib/src/exceptions/module/port_width_mismatch_exception.dart b/lib/src/exceptions/module/port_width_mismatch_exception.dart index 710cf9acc..8ba427e6a 100644 --- a/lib/src/exceptions/module/port_width_mismatch_exception.dart +++ b/lib/src/exceptions/module/port_width_mismatch_exception.dart @@ -13,9 +13,11 @@ import 'package:rohd/src/exceptions/rohd_exception.dart'; /// An [Exception] thrown when a port has the wrong width. class PortWidthMismatchException extends RohdException { /// Constructs a new [Exception] for when a port has the wrong width. - PortWidthMismatchException(Logic port, int expectedWidth) + PortWidthMismatchException(Logic port, int expectedWidth, + {String additionalMessage = ''}) : super('Port ${port.name} has the wrong width.' - ' Expected $expectedWidth but found ${port.width}.'); + ' Expected $expectedWidth but found ${port.width}.' + ' $additionalMessage'); /// Constructs a new [Exception] for when two ports should have been the /// same width, but were not. diff --git a/lib/src/interface.dart b/lib/src/interface.dart index 2433d5c50..5c1b109e2 100644 --- a/lib/src/interface.dart +++ b/lib/src/interface.dart @@ -12,18 +12,6 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; -import 'package:rohd/src/exceptions/name/name_exceptions.dart'; -import 'package:rohd/src/utilities/sanitizer.dart'; - -/// An extension of [Logic] useful for [Interface] definitions. -class Port extends Logic { - /// Constructs a [Logic] intended to be used for ports in an [Interface]. - Port(String name, [int width = 1]) : super(name: name, width: width) { - if (!Sanitizer.isSanitary(name)) { - throw InvalidPortNameException(name); - } - } -} /// Represents a logical interface to a [Module]. /// diff --git a/lib/src/module.dart b/lib/src/module.dart index 26a06191e..f55a3baff 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -111,11 +111,15 @@ abstract class Module { /// Returns true iff [net] is the same [Logic] as the input port of this /// [Module] with the same name. - bool isInput(Logic net) => _inputs[net.name] == net; + bool isInput(Logic net) => + _inputs[net.name] == net || + (net.isArrayMember && isInput(net.parentStructure!)); /// Returns true iff [net] is the same [Logic] as the output port of this /// [Module] with the same name. - bool isOutput(Logic net) => _outputs[net.name] == net; + bool isOutput(Logic net) => + _outputs[net.name] == net || + (net.isArrayMember && isOutput(net.parentStructure!)); /// Returns true iff [net] is the same [Logic] as an input or output port of /// this [Module] with the same name. @@ -421,7 +425,12 @@ abstract class Module { await _traceInputForModuleContents(dstConnection); } } - if (signal.srcConnection != null) { + + if (signal is LogicStructure) { + for (final srcConnection in signal.srcConnections) { + await _traceOutputForModuleContents(srcConnection); + } + } else if (signal.srcConnection != null) { await _traceOutputForModuleContents(signal.srcConnection!); } } @@ -442,6 +451,7 @@ abstract class Module { if (!Sanitizer.isSanitary(name)) { throw InvalidPortNameException(name); } + if (outputs.containsKey(name) || inputs.containsKey(name)) { throw Exception('Already defined a port with name "$name".'); } @@ -455,15 +465,50 @@ abstract class Module { Logic addInput(String name, Logic x, {int width = 1}) { _checkForSafePortName(name); if (x.width != width) { - throw Exception('Port width mismatch, signal "$x" does not' - ' have specified width "$width".'); + throw PortWidthMismatchException(x, width); } - _inputs[name] = Logic(name: name, width: width)..gets(x); - // ignore: invalid_use_of_protected_member - _inputs[name]!.parentModule = this; + if (x is LogicStructure) { + // ignore: parameter_assignments + x = x.packed; + } + + final inPort = Port(name, width) + ..gets(x) + // ignore: invalid_use_of_protected_member + ..parentModule = this; - return _inputs[name]!; + _inputs[name] = inPort; + + return inPort; + } + + /// Registers and returns an input [LogicArray] port to this [Module] with + /// the specified [dimensions], [elementWidth], and [numUnpackedDimensions] + /// named [name]. + /// + /// This is very similar to [addInput], except for [LogicArray]s. + /// + /// Performs validation on overall width matching for [x], but not on + /// [dimensions], [elementWidth], or [numUnpackedDimensions]. + LogicArray addInputArray( + String name, + Logic x, { + List dimensions = const [1], + int elementWidth = 1, + int numUnpackedDimensions = 0, + }) { + _checkForSafePortName(name); + + final inArr = LogicArray(dimensions, elementWidth, + name: name, numUnpackedDimensions: numUnpackedDimensions) + ..gets(x) + // ignore: invalid_use_of_protected_member + ..parentModule = this; + + _inputs[name] = inArr; + + return inArr; } /// Registers an output to this [Module] and returns an output port that @@ -473,12 +518,37 @@ abstract class Module { @protected Logic addOutput(String name, {int width = 1}) { _checkForSafePortName(name); - _outputs[name] = Logic(name: name, width: width); - // ignore: invalid_use_of_protected_member - _outputs[name]!.parentModule = this; + final outPort = Port(name, width) + // ignore: invalid_use_of_protected_member + ..parentModule = this; + + _outputs[name] = outPort; + + return outPort; + } + + /// Registers and returns an output [LogicArray] port to this [Module] with + /// the specified [dimensions], [elementWidth], and [numUnpackedDimensions] + /// named [name]. + /// + /// This is very similar to [addOutput], except for [LogicArray]s. + LogicArray addOutputArray( + String name, { + List dimensions = const [1], + int elementWidth = 1, + int numUnpackedDimensions = 0, + }) { + _checkForSafePortName(name); + + final outArr = LogicArray(dimensions, elementWidth, + name: name, numUnpackedDimensions: numUnpackedDimensions) + // ignore: invalid_use_of_protected_member + ..parentModule = this; + + _outputs[name] = outArr; - return _outputs[name]!; + return outArr; } @override @@ -519,3 +589,17 @@ abstract class Module { .join('\n\n////////////////////\n\n'); } } + +extension _ModuleLogicStructureUtils on LogicStructure { + /// Provides a list of all source connections of all elements within + /// this structure, recursively. + /// + /// Useful for searching during [Module] build. + Iterable get srcConnections => [ + for (final element in elements) + if (element is LogicStructure) + ...element.srcConnections + else if (element.srcConnection != null) + element.srcConnection! + ]; +} diff --git a/lib/src/modules/bus.dart b/lib/src/modules/bus.dart index 315077759..b80ca6e65 100644 --- a/lib/src/modules/bus.dart +++ b/lib/src/modules/bus.dart @@ -159,7 +159,8 @@ class Swizzle extends Module with InlineSystemVerilog { /// Executes the functional behavior of this gate. void _execute() { - final updatedVal = LogicValue.of(_swizzleInputs.map((e) => e.value)); + final updatedVal = + LogicValue.ofIterable(_swizzleInputs.map((e) => e.value)); out.put(updatedVal); } diff --git a/lib/src/modules/conditional.dart b/lib/src/modules/conditional.dart index fd3677412..2990ec954 100644 --- a/lib/src/modules/conditional.dart +++ b/lib/src/modules/conditional.dart @@ -42,9 +42,10 @@ abstract class _Always extends Module with CustomSystemVerilog { /// If [reset] is provided, then all signals driven by this block will be /// conditionally reset when the signal is high. /// The default reset value is to `0`, but if [resetValues] is provided then - /// the corresponding value - /// associated with the driven signal will be set to that value instead upon - /// reset. + /// the corresponding value associated with the driven signal will be set to + /// that value instead upon reset. If a signal is in [resetValues] but not + /// driven by any other [Conditional] in this block, it will be driven to the + /// specified reset value. _Always(this._conditionals, {Logic? reset, Map? resetValues, super.name = 'always'}) { // create a registration of all inputs and outputs of this module @@ -52,21 +53,55 @@ abstract class _Always extends Module with CustomSystemVerilog { // Get all Receivers final allReceivers = - conditionals.map((e) => e.receivers).expand((e) => e).toList(); + conditionals.map((e) => e.receivers).expand((e) => e).toSet(); // This will reset the conditionals on setting the `reset` flag if (reset != null) { + final allResetCondAssigns = []; + final signalsBeingReset = {}; + + if (resetValues != null) { + final toConsiderForElementsReset = [ + ...resetValues.keys, + ]; + + for (var i = 0; i < toConsiderForElementsReset.length; i++) { + final toConsider = toConsiderForElementsReset[i]; + + // if it's a structure, we need to consider its elements + if (toConsider is LogicStructure) { + toConsiderForElementsReset.addAll(toConsider.elements); + } + + // if we're already resetting this signal, flag an issue + if (signalsBeingReset.contains(toConsider)) { + throw SignalRedrivenException([toConsider], + 'Signal is already being reset by another reset value: '); + } + + if (resetValues.containsKey(toConsider)) { + // should only be true for top-level structures referenced + allResetCondAssigns.add(toConsider < resetValues[toConsider]); + } + + // always add the signal, even if this is a sub-element + signalsBeingReset.add(toConsider); + } + } + + // now add the reset to 0 for all the remaining ones + for (final receiver in allReceivers.toList()) { + if (!signalsBeingReset.contains(receiver)) { + allResetCondAssigns.add(receiver < 0); + } + } + _conditionals = [ // If resetValue for a receiver is defined, If( reset, // then use it for assigning receiver - then: [ - ...allReceivers.map((rec) { - final driver = resetValues?[rec] ?? 0; - return rec < driver; - }) - ], + then: allResetCondAssigns, // else assign zero as resetValue orElse: conditionals, ), @@ -344,6 +379,14 @@ class Sequential extends _Always { final List _clks = []; /// Constructs a [Sequential] single-triggered by [clk]. + /// + /// If `reset` is provided, then all signals driven by this block will be + /// conditionally reset when the signal is high. + /// The default reset value is to `0`, but if `resetValues` is provided then + /// the corresponding value associated with the driven signal will be set to + /// that value instead upon reset. If a signal is in `resetValues` but not + /// driven by any other [Conditional] in this block, it will be driven to the + /// specified reset value. Sequential(Logic clk, List conditionals, {Logic? reset, Map? resetValues, @@ -352,6 +395,14 @@ class Sequential extends _Always { name: name, reset: reset, resetValues: resetValues); /// Constructs a [Sequential] multi-triggered by any of [clks]. + /// + /// If `reset` is provided, then all signals driven by this block will be + /// conditionally reset when the signal is high. + /// The default reset value is to `0`, but if `resetValues` is provided then + /// the corresponding value associated with the driven signal will be set to + /// that value instead upon reset. If a signal is in `resetValues` but not + /// driven by any other [Conditional] in this block, it will be driven to the + /// specified reset value. Sequential.multi(List clks, super.conditionals, {super.reset, super.resetValues, super.name = 'sequential'}) { for (var i = 0; i < clks.length; i++) { @@ -497,7 +548,7 @@ class Sequential extends _Always { element.execute(allDrivenSignals, null); } if (allDrivenSignals.hasDuplicates) { - throw SignalRedrivenException(allDrivenSignals.duplicates.toString()); + throw SignalRedrivenException(allDrivenSignals.duplicates); } } @@ -667,7 +718,7 @@ abstract class Conditional { } } - return foundSsaLogics.toList(); + return foundSsaLogics.toList(growable: false); } /// Given existing [currentMappings], connects [drivers] and [receivers] @@ -680,6 +731,55 @@ abstract class Conditional { {required int context}); } +/// Represents a group of [Conditional]s to be executed. +class ConditionalGroup extends Conditional { + @override + final List conditionals; + + /// Creates a group of [conditionals] to be executed in order and bundles + /// them into a single [Conditional]. + ConditionalGroup(this.conditionals); + + @override + Map _processSsa(Map currentMappings, + {required int context}) { + var mappings = currentMappings; + for (final conditional in conditionals) { + mappings = conditional._processSsa(mappings, context: context); + } + return mappings; + } + + @override + late final List drivers = [ + for (final conditional in conditionals) ...conditional.drivers + ]; + + @override + late final List receivers = [ + for (final conditional in conditionals) ...conditional.receivers + ]; + + @override + void execute(Set drivenSignals, void Function(Logic toGuard)? guard) { + for (final conditional in conditionals) { + conditional.execute(drivenSignals, guard); + } + } + + @override + String verilogContents(int indent, Map inputsNameMap, + Map outputsNameMap, String assignOperator) => + conditionals + .map((c) => c.verilogContents( + indent, + inputsNameMap, + outputsNameMap, + assignOperator, + )) + .join('\n'); +} + /// An assignment that only happens under certain conditions. /// /// [Logic] has a short-hand for creating [ConditionalAssign] via the @@ -694,7 +794,7 @@ class ConditionalAssign extends Conditional { /// Conditionally assigns [receiver] to the value of [driver]. ConditionalAssign(this.receiver, this.driver) { if (driver.width != receiver.width) { - throw Exception('Width for $receiver and $driver must match but do not.'); + throw PortWidthMismatchException.equalWidth(receiver, driver); } } @@ -891,7 +991,8 @@ class Case extends Conditional { } @override - late final List conditionals = _getConditionals(); + late final List conditionals = + UnmodifiableListView(_getConditionals()); /// Calculates the set of conditionals directly within this. List _getConditionals() => [ @@ -911,13 +1012,13 @@ class Case extends Conditional { ..addAll(item.then .map((conditional) => conditional.drivers) .expand((driver) => driver) - .toList()); + .toList(growable: false)); } if (defaultItem != null) { drivers.addAll(defaultItem! .map((conditional) => conditional.drivers) .expand((driver) => driver) - .toList()); + .toList(growable: false)); } return drivers; } @@ -932,13 +1033,13 @@ class Case extends Conditional { receivers.addAll(item.then .map((conditional) => conditional.receivers) .expand((receiver) => receiver) - .toList()); + .toList(growable: false)); } if (defaultItem != null) { receivers.addAll(defaultItem! .map((conditional) => conditional.receivers) .expand((receiver) => receiver) - .toList()); + .toList(growable: false)); } return receivers; } @@ -1170,11 +1271,14 @@ class If extends Conditional { } @override - late final List conditionals = _getConditionals(); + late final List conditionals = + UnmodifiableListView(_getConditionals()); /// Calculates the set of conditionals directly within this. - List _getConditionals() => - iffs.map((iff) => iff.then).expand((conditional) => conditional).toList(); + List _getConditionals() => iffs + .map((iff) => iff.then) + .expand((conditional) => conditional) + .toList(growable: false); @override late final List drivers = _getDrivers(); @@ -1188,7 +1292,7 @@ class If extends Conditional { ..addAll(iff.then .map((conditional) => conditional.drivers) .expand((driver) => driver) - .toList()); + .toList(growable: false)); } return drivers; } @@ -1203,7 +1307,7 @@ class If extends Conditional { receivers.addAll(iff.then .map((conditional) => conditional.receivers) .expand((receiver) => receiver) - .toList()); + .toList(growable: false)); } return receivers; } diff --git a/lib/src/modules/pipeline.dart b/lib/src/modules/pipeline.dart index a0d9bf2ca..8fff663aa 100644 --- a/lib/src/modules/pipeline.dart +++ b/lib/src/modules/pipeline.dart @@ -219,7 +219,7 @@ class Pipeline { ffAssigns.map((conditional) { conditional as ConditionalAssign; return conditional.receiver < (resetValue ?? 0); - }).toList(), + }).toList(growable: false), ), Else(ffAssignsWithStall) ]) diff --git a/lib/src/signals/const.dart b/lib/src/signals/const.dart new file mode 100644 index 000000000..05efee902 --- /dev/null +++ b/lib/src/signals/const.dart @@ -0,0 +1,25 @@ +// Copyright (C) 2021-2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// const.dart +// Definition of signals with constant values. +// +// 2023 May 26 +// Author: Max Korbel + +part of signals; + +/// Represents a [Logic] that never changes value. +class Const extends Logic { + /// Constructs a [Const] with the specified value. + /// + /// [val] should be processable by [LogicValue.of]. + Const(dynamic val, {int? width, bool fill = false}) + : super( + // ignore: invalid_use_of_protected_member + name: Module.unpreferredName('const_$val'), + width: val is LogicValue ? val.width : width ?? 1) { + put(val, fill: fill); + _unassignable = true; + } +} diff --git a/lib/src/logic.dart b/lib/src/signals/logic.dart similarity index 63% rename from lib/src/logic.dart rename to lib/src/signals/logic.dart index 2d0f725a5..8f2b79127 100644 --- a/lib/src/logic.dart +++ b/lib/src/signals/logic.dart @@ -1,291 +1,14 @@ -/// Copyright (C) 2021-2023 Intel Corporation -/// SPDX-License-Identifier: BSD-3-Clause -/// -/// logic.dart -/// Definition of basic signals, like Logic, and their behavior in the -/// simulator, as well as basic operations on them -/// -/// 2021 August 2 -/// Author: Max Korbel -/// -import 'dart:async'; - -import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; -import 'package:rohd/rohd.dart'; -import 'package:rohd/src/exceptions/logic/logic_exceptions.dart'; -import 'package:rohd/src/utilities/sanitizer.dart'; -import 'package:rohd/src/utilities/synchronous_propagator.dart'; - -/// Represents the event of a [Logic] changing value. -class LogicValueChanged { - /// The newly updated value of the [Logic]. - final LogicValue newValue; - - /// The previous value of the [Logic]. - final LogicValue previousValue; - - /// Represents the event of a [Logic] changing value from [previousValue] - /// to [newValue]. - const LogicValueChanged(this.newValue, this.previousValue); - - @override - String toString() => '$previousValue --> $newValue'; -} - -/// Represents a [Logic] that never changes value. -class Const extends Logic { - /// Constructs a [Const] with the specified value. - /// - /// If [val] is a [LogicValue], the [width] is inferred from it. - /// Otherwise, if [width] is not specified, the default [width] is 1. - /// If [fill] is set to `true`, the value is extended across - /// [width] (like `'` in SystemVerilog). - Const(dynamic val, {int? width, bool fill = false}) - : super( - name: 'const_$val', - width: val is LogicValue ? val.width : width ?? 1) { - put(val, fill: fill); - _unassignable = true; - } -} - -/// Represents a physical wire which shares a common value with one or -/// more [Logic]s. -class _Wire { - _Wire({required this.width}) - : _currentValue = LogicValue.filled(width, LogicValue.z); - - /// The current active value of this signal. - LogicValue get value => _currentValue; - - /// The number of bits in this signal. - final int width; - - /// The current active value of this signal. - LogicValue _currentValue; - - /// The last value of this signal before the [Simulator] tick. - /// - /// This is useful for detecting when to trigger an edge. - LogicValue? _preTickValue; - - /// A stream of [LogicValueChanged] events for every time the signal - /// transitions at any time during a [Simulator] tick. - /// - /// This event can occur more than once per edge, or even if there is no edge. - SynchronousEmitter get glitch => _glitchController.emitter; - final SynchronousPropagator _glitchController = - SynchronousPropagator(); - - /// Controller for stable events that can be safely consumed at the - /// end of a [Simulator] tick. - final StreamController _changedController = - StreamController.broadcast(sync: true); - - /// Tracks whether is being subscribed to by anything/anyone. - bool _changedBeingWatched = false; - - /// A [Stream] of [LogicValueChanged] events which triggers at most once - /// per [Simulator] tick, iff the value of the [Logic] has changed. - Stream get changed { - if (!_changedBeingWatched) { - // only do these simulator subscriptions if someone has asked for - // them! saves performance! - _changedBeingWatched = true; - - _preTickSubscription = Simulator.preTick.listen((event) { - _preTickValue = value; - }); - _postTickSubscription = Simulator.postTick.listen((event) { - if (value != _preTickValue && _preTickValue != null) { - _changedController.add(LogicValueChanged(value, _preTickValue!)); - } - }); - } - return _changedController.stream; - } - - /// The subscription to the [Simulator]'s `preTick`. - /// - /// Only non-null if [_changedBeingWatched] is true. - late final StreamSubscription _preTickSubscription; - - /// The subscription to the [Simulator]'s `postTick`. - /// - /// Only non-null if [_changedBeingWatched] is true. - late final StreamSubscription _postTickSubscription; - - /// Cancels all [Simulator] subscriptions and uses [newChanged] as the - /// source to replace all [changed] events for this [_Wire]. - void _migrateChangedTriggers(Stream newChanged) { - if (_changedBeingWatched) { - unawaited(_preTickSubscription.cancel()); - unawaited(_postTickSubscription.cancel()); - newChanged.listen(_changedController.add); - _changedBeingWatched = false; - } - } - - /// Tells this [_Wire] to adopt all the behavior of [other] so that - /// it can replace [other]. - void _adopt(_Wire other) { - _glitchController.emitter.adopt(other._glitchController.emitter); - other._migrateChangedTriggers(changed); - } - - /// Store the [negedge] stream to avoid creating multiple copies - /// of streams. - Stream? _negedge; - - /// Store the [posedge] stream to avoid creating multiple copies - /// of streams. - Stream? _posedge; - - /// A [Stream] of [LogicValueChanged] events which triggers at most once - /// per [Simulator] tick, iff the value of the [Logic] has changed - /// from `1` to `0`. - /// - /// Throws an exception if [width] is not `1`. - Stream get negedge { - if (width != 1) { - throw Exception( - 'Can only detect negedge when width is 1, but was $width'); - } - - _negedge ??= changed.where((args) => LogicValue.isNegedge( - args.previousValue, - args.newValue, - ignoreInvalid: true, - )); - - return _negedge!; - } - - /// A [Stream] of [LogicValueChanged] events which triggers at most once - /// per [Simulator] tick, iff the value of the [Logic] has changed - /// from `0` to `1`. - /// - /// Throws an exception if [width] is not `1`. - Stream get posedge { - if (width != 1) { - throw Exception( - 'Can only detect posedge when width is 1, but was $width'); - } - - _posedge ??= changed.where((args) => LogicValue.isPosedge( - args.previousValue, - args.newValue, - ignoreInvalid: true, - )); - - return _posedge!; - } - - /// Triggers at most once, the next time that this [Logic] changes - /// value at the end of a [Simulator] tick. - Future get nextChanged => changed.first; - - /// Triggers at most once, the next time that this [Logic] changes - /// value at the end of a [Simulator] tick from `0` to `1`. - /// - /// Throws an exception if [width] is not `1`. - Future get nextPosedge => posedge.first; - - /// Triggers at most once, the next time that this [Logic] changes - /// value at the end of a [Simulator] tick from `1` to `0`. - /// - /// Throws an exception if [width] is not `1`. - Future get nextNegedge => negedge.first; - - /// Injects a value onto this signal in the current [Simulator] tick. - /// - /// This function calls [put()] in [Simulator.injectAction()]. - void inject(dynamic val, {required String signalName, bool fill = false}) { - Simulator.injectAction(() => put(val, signalName: signalName, fill: fill)); - } - - /// Keeps track of whether there is an active put, to detect reentrance. - bool _isPutting = false; - - /// Puts a value [val] onto this signal, which may or may not be picked up - /// for [changed] in this [Simulator] tick. - /// - /// The type of [val] should be an `int`, [BigInt], `bool`, or [LogicValue]. - /// - /// This function is used for propogating glitches through connected signals. - /// Use this function for custom definitions of [Module] behavior. - /// - /// If [fill] is set, all bits of the signal gets set to [val], similar - /// to `'` in SystemVerilog. - void put(dynamic val, {required String signalName, bool fill = false}) { - LogicValue newValue; - if (val is int) { - if (fill) { - newValue = LogicValue.filled( - width, - val == 0 - ? LogicValue.zero - : val == 1 - ? LogicValue.one - : throw PutException( - signalName, 'Only can fill 0 or 1, but saw $val.')); - } else { - newValue = LogicValue.ofInt(val, width); - } - } else if (val is BigInt) { - if (fill) { - newValue = LogicValue.filled( - width, - val == BigInt.zero - ? LogicValue.zero - : val == BigInt.one - ? LogicValue.one - : throw PutException( - signalName, 'Only can fill 0 or 1, but saw $val.')); - } else { - newValue = LogicValue.ofBigInt(val, width); - } - } else if (val is bool) { - newValue = LogicValue.ofInt(val ? 1 : 0, width); - } else if (val is LogicValue) { - if (val.width == 1 && - (val == LogicValue.x || val == LogicValue.z || fill)) { - newValue = LogicValue.filled(width, val); - } else if (fill) { - throw PutException(signalName, - 'Failed to fill value with $val. To fill, it should be 1 bit.'); - } else { - newValue = val; - } - } else { - throw PutException( - signalName, - 'Unrecognized value "$val" to deposit on this signal. ' - 'Unknown type ${val.runtimeType} cannot be deposited.'); - } - - if (newValue.width != width) { - throw PutException(signalName, - 'Updated value width mismatch. The width of $val should be $width.'); - } - - if (_isPutting) { - // if this is the result of a cycle, then contention! - newValue = LogicValue.filled(width, LogicValue.x); - } - - final prevValue = _currentValue; - _currentValue = newValue; - - // sends out a glitch if the value deposited has changed - if (_currentValue != prevValue) { - _isPutting = true; - _glitchController.add(LogicValueChanged(_currentValue, prevValue)); - _isPutting = false; - } - } -} +// Copyright (C) 2021-2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic.dart +// Definition of basic signals, like Logic, and their behavior in the +// simulator, as well as basic operations on them +// +// 2021 August 2 +// Author: Max Korbel + +part of signals; /// Represents a logical signal of any width which can change values. class Logic { @@ -382,27 +105,60 @@ class Logic { /// Triggers at most once, the next time that this [Logic] changes /// value at the end of a [Simulator] tick. - Future get nextChanged => _wire.changed.first; + Future get nextChanged => _wire.nextChanged; /// Triggers at most once, the next time that this [Logic] changes /// value at the end of a [Simulator] tick from `0` to `1`. /// /// Throws an exception if [width] is not `1`. - Future get nextPosedge => _wire.posedge.first; + Future get nextPosedge => _wire.nextPosedge; /// Triggers at most once, the next time that this [Logic] changes /// value at the end of a [Simulator] tick from `1` to `0`. /// /// Throws an exception if [width] is not `1`. - Future get nextNegedge => _wire.negedge.first; + Future get nextNegedge => _wire.nextNegedge; /// The [Module] that this [Logic] exists within. /// - /// This only gets populated after its parent [Module], if it exists, - /// has been built. + /// For internal signals, this only gets populated after its parent [Module], + /// if it exists, has been built. Ports (both input and output) have this + /// populated at the time of creation. Module? get parentModule => _parentModule; Module? _parentModule; + /// If this is a part of a [LogicStructure], the structure which this is + /// a part of. Otherwise, `null`. + LogicStructure? get parentStructure => _parentStructure; + LogicStructure? _parentStructure; + + /// True if this is a member of a [LogicArray]. + bool get isArrayMember => parentStructure is LogicArray; + + /// Returns the name relative to the [parentStructure]-defined hierarchy, if + /// one exists. Otherwise, this is the same as [name]. + /// + /// This is useful for finding the name of a signal as an element of a root + /// [LogicArray] or [LogicStructure]. + String get structureName { + if (parentStructure != null) { + if (parentStructure is LogicArray) { + return '${parentStructure!.structureName}[${arrayIndex!}]'; + } else { + return '${parentStructure!.structureName}.$name'; + } + } else { + return name; + } + } + + /// If this is a part of a [LogicArray], the index within that array. + /// Othwerise, returns `null`. + /// + /// If [isArrayMember] is true, this will be non-`null`. + int? get arrayIndex => _arrayIndex; + int? _arrayIndex; + /// Sets the value of [parentModule] to [newParentModule]. /// /// This should *only* be called by [Module.build()]. It is used to @@ -414,13 +170,13 @@ class Logic { /// /// Note: [parentModule] is not populated until after its parent [Module], /// if it exists, has been built. If no parent [Module] exists, returns false. - bool get isInput => _parentModule?.isInput(this) ?? false; + bool get isInput => parentModule?.isInput(this) ?? false; /// Returns true iff this signal is an output of its parent [Module]. /// /// Note: [parentModule] is not populated until after its parent [Module], /// if it exists, has been built. If no parent [Module] exists, returns false. - bool get isOutput => _parentModule?.isOutput(this) ?? false; + bool get isOutput => parentModule?.isOutput(this) ?? false; /// Returns true iff this signal is an input or output of its parent [Module]. /// @@ -452,11 +208,16 @@ class Logic { 'This signal "$this" is already connected to "$srcConnection",' ' so it cannot be connected to "$other".'); } + if (_unassignable) { throw Exception('This signal "$this" has been marked as unassignable. ' 'It may be a constant expression or otherwise should' ' not be assigned.'); } + + if (other.width != width) { + throw SignalWidthMismatchException(other, width); + } } /// Injects a value onto this signal in the current [Simulator] tick. @@ -485,6 +246,13 @@ class Logic { void gets(Logic other) { _assertConnectable(other); + // If we are connecting a `LogicStructure` to this simple `Logic`, + // then pack it first. + if (other is LogicStructure) { + // ignore: parameter_assignments + other = other.packed; + } + _connect(other); _srcConnection = other; @@ -496,6 +264,7 @@ class Logic { if (_wire == other._wire) { throw SelfConnectingLogicException(this, other); } + _unassignable = true; _updateWire(other._wire); } @@ -534,7 +303,7 @@ class Logic { Logic operator ^(Logic other) => Xor2Gate(this, other).out; /// Power operation - Logic pow(dynamic other) => Power(this, other).out; + Logic pow(dynamic exponent) => Power(this, exponent).out; /// Addition. /// @@ -601,11 +370,6 @@ class Logic { /// Greater-than-or-equal-to. Logic operator >=(dynamic other) => GreaterThanOrEqual(this, other).out; - /// A function that returns whatever [Logic] is provided. - /// - /// Used as a default no-op for short-hands. - static Logic _nopS(Logic x) => x; - /// Shorthand for a [Conditional] which increments this by [val]. /// /// By default for a [Logic] variable, if no [val] is provided then the @@ -626,8 +390,8 @@ class Logic { /// ]); /// /// ``` - ConditionalAssign incr({Logic Function(Logic) s = _nopS, dynamic val = 1}) => - s(this) < s(this) + val; + Conditional incr({Logic Function(Logic)? s, dynamic val = 1}) => + s == null ? (this < this + val) : (s(this) < s(this) + val); /// Shorthand for a [Conditional] which decrements this by [val]. /// @@ -649,8 +413,8 @@ class Logic { /// ]); /// /// ``` - ConditionalAssign decr({Logic Function(Logic) s = _nopS, dynamic val = 1}) => - s(this) < s(this) - val; + Conditional decr({Logic Function(Logic)? s, dynamic val = 1}) => + s == null ? (this < this - val) : (s(this) < s(this) - val); /// Shorthand for a [Conditional] which increments this by [val]. /// @@ -671,8 +435,8 @@ class Logic { /// ]); /// /// ``` - ConditionalAssign mulAssign({Logic Function(Logic) s = _nopS, dynamic val}) => - s(this) < s(this) * val; + Conditional mulAssign(dynamic val, {Logic Function(Logic)? s}) => + s == null ? (this < this * val) : (s(this) < s(this) * val); /// Shorthand for a [Conditional] which increments this by [val]. /// @@ -693,15 +457,15 @@ class Logic { /// ]); /// /// ``` - ConditionalAssign divAssign({Logic Function(Logic) s = _nopS, dynamic val}) => - s(this) < s(this) / val; + Conditional divAssign(dynamic val, {Logic Function(Logic)? s}) => + s == null ? (this < this / val) : (s(this) < s(this) / val); /// Conditional assignment operator. /// /// Represents conditionally asigning the value of another signal to this. /// Returns an instance of [ConditionalAssign] to be be passed to a /// [Conditional]. - ConditionalAssign operator <(dynamic other) { + Conditional operator <(dynamic other) { if (_unassignable) { throw Exception('This signal "$this" has been marked as unassignable. ' 'It may be a constant expression or otherwise' @@ -753,6 +517,14 @@ class Logic { throw Exception('Expected `int` or `Logic`'); } + /// Provides a list of logical elements within this signal. + /// + /// For a normal [Logic], this will always be a list of 1-bit signals. + /// However, for derivatives of [Logic] like [LogicStructure] or [LogicArray], + /// each element may be any positive number of bits. + late final List elements = UnmodifiableListView( + List.generate(width, (index) => this[index], growable: false)); + /// Accesses a subset of this signal from [startIndex] to [endIndex], /// both inclusive. /// @@ -774,13 +546,10 @@ class Logic { Logic slice(int endIndex, int startIndex) { // Given start and end index, if either of them are seen to be -ve index // value(s) then convert them to a +ve index value(s) - final modifiedStartIndex = - (startIndex < 0) ? width + startIndex : startIndex; - final modifiedEndIndex = (endIndex < 0) ? width + endIndex : endIndex; + final modifiedStartIndex = IndexUtilities.wrapIndex(startIndex, width); + final modifiedEndIndex = IndexUtilities.wrapIndex(endIndex, width); - if (width == 1 && - modifiedEndIndex == 0 && - modifiedEndIndex == modifiedStartIndex) { + if (modifiedStartIndex == 0 && modifiedEndIndex == width - 1) { // ignore: avoid_returning_this return this; } @@ -824,13 +593,12 @@ class Logic { // Given start and end index, if either of them are seen to be -ve index // value(s) then conver them to a +ve index value(s) final modifiedStartIndex = - (startIndex < 0) ? width + startIndex : startIndex; - final modifiedEndIndex = (endIndex < 0) ? width + endIndex : endIndex; - if (modifiedEndIndex < modifiedStartIndex) { - throw Exception( - 'End $modifiedEndIndex(=$endIndex) cannot be less than start' - ' $modifiedStartIndex(=$startIndex).'); - } + IndexUtilities.wrapIndex(startIndex, width, allowWidth: true); + final modifiedEndIndex = + IndexUtilities.wrapIndex(endIndex, width, allowWidth: true); + + IndexUtilities.validateRange(modifiedStartIndex, modifiedEndIndex); + return slice(modifiedEndIndex - 1, modifiedStartIndex); } @@ -882,11 +650,20 @@ class Logic { /// if the position of the [update] would cause an overrun past the [width]. Logic withSet(int startIndex, Logic update) { if (startIndex + update.width > width) { - throw Exception( - 'Width of updatedValue $update at startIndex $startIndex would' + throw RangeError('Width of update $update at startIndex $startIndex would' ' overrun the width of the original ($width).'); } + if (startIndex < 0) { + throw RangeError( + 'Start index must be greater than zero but was $startIndex'); + } + + if (startIndex == 0 && update.width == width) { + // special case, setting whole thing, just return the same thing + return update; + } + return [ getRange(startIndex + update.width, width), update, diff --git a/lib/src/signals/logic_array.dart b/lib/src/signals/logic_array.dart new file mode 100644 index 000000000..1de2a4605 --- /dev/null +++ b/lib/src/signals/logic_array.dart @@ -0,0 +1,121 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_array.dart +// Definition of an array of `Logic`s. +// +// 2023 May 1 +// Author: Max Korbel + +part of signals; + +/// Represents a multi-dimensional array structure of independent [Logic]s. +class LogicArray extends LogicStructure { + /// The number of elements at each level of the array, starting from the most + /// significant outermost level. + /// + /// For example `[3, 2]` would indicate a 2-dimensional array, where it is + /// an array with 3 arrays, each containing 2 arrays. + final List dimensions; + + /// The width of leaf elements in this array. + /// + /// If the array has no leaf elements and/or the [width] is 0, then the + /// [elementWidth] is always 0. + final int elementWidth; + + @override + String toString() => 'LogicArray($dimensions, $elementWidth): $name'; + + /// The number of [dimensions] which should be treated as "unpacked", starting + /// from the outermost (first) elements of [dimensions]. + /// + /// This has no functional impact on simulation or behavior. It is only used + /// as a hint for [Synthesizer]s. + final int numUnpackedDimensions; + + /// Creates an array with specified [dimensions] and [elementWidth] named + /// [name]. + /// + /// Setting the [numUnpackedDimensions] gives a hint to [Synthesizer]s about + /// the intent for declaration of signals. By default, all dimensions are + /// packed, but if the value is set to more than `0`, then the outer-most + /// dimensions (first in [dimensions]) will become unpacked. It must be less + /// than or equal to the length of [dimensions]. Modifying it will have no + /// impact on simulation functionality or behavior. In SystemVerilog, there + /// are some differences in access patterns for packed vs. unpacked arrays. + factory LogicArray(List dimensions, int elementWidth, + {String? name, int numUnpackedDimensions = 0}) { + if (dimensions.isEmpty) { + throw LogicConstructionException( + 'Arrays must have at least 1 dimension.'); + } + + if (numUnpackedDimensions > dimensions.length) { + throw LogicConstructionException( + 'Cannot unpack more than all of the dimensions.'); + } + + // calculate the next layer's dimensions + final nextDimensions = dimensions.length == 1 + ? null + : UnmodifiableListView( + dimensions.getRange(1, dimensions.length).toList(growable: false)); + + // if the total width will eventually be 0, then force element width to 0 + if (elementWidth != 0 && dimensions.reduce((a, b) => a * b) == 0) { + elementWidth = 0; + } + + return LogicArray._( + List.generate( + dimensions.first, + (index) => (dimensions.length == 1 + ? Logic(width: elementWidth) + : LogicArray( + nextDimensions!, + elementWidth, + numUnpackedDimensions: max(0, numUnpackedDimensions - 1), + name: '${name}_$index', + )) + .._arrayIndex = index, + growable: false), + dimensions: UnmodifiableListView(dimensions), + elementWidth: elementWidth, + numUnpackedDimensions: numUnpackedDimensions, + name: name, + ); + } + + /// Creates a new [LogicArray] which has the same [dimensions], + /// [elementWidth], [numUnpackedDimensions] as `this`. + /// + /// If no new [name] is specified, then it will also have the same name. + @override + LogicArray clone({String? name}) => LogicArray(dimensions, elementWidth, + numUnpackedDimensions: numUnpackedDimensions, name: name ?? this.name); + + /// Private constructor for the factory [LogicArray] constructor. + LogicArray._( + super.elements, { + required this.dimensions, + required this.elementWidth, + required this.numUnpackedDimensions, + required super.name, + }); + + /// Constructs a new [LogicArray] with a more convenient constructor signature + /// for when many ports in an interface are declared together. Also performs + /// some basic checks on the legality of the array as a port of a [Module]. + factory LogicArray.port(String name, + [List dimensions = const [1], + int elementWidth = 1, + int numUnpackedDimensions = 0]) { + if (!Sanitizer.isSanitary(name)) { + throw InvalidPortNameException(name); + } + + return LogicArray(dimensions, elementWidth, + numUnpackedDimensions: numUnpackedDimensions, name: name); + } +} diff --git a/lib/src/signals/logic_structure.dart b/lib/src/signals/logic_structure.dart new file mode 100644 index 000000000..f339c01b5 --- /dev/null +++ b/lib/src/signals/logic_structure.dart @@ -0,0 +1,519 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_structure.dart +// Definition of a structure containing multiple `Logic`s. +// +// 2023 May 1 +// Author: Max Korbel + +part of signals; + +/// Collects a group of [Logic] signals into one entity which can be manipulated +/// in a similar way as an individual [Logic]. +class LogicStructure implements Logic { + /// All elements of this structure. + @override + late final List elements = UnmodifiableListView(_elements); + final List _elements = []; + + /// Packs all [elements] into one flattened bus. + late final Logic packed = elements + .map((e) { + if (e is LogicStructure) { + return e.packed; + } else { + return e; + } + }) + .toList(growable: false) + .rswizzle(); + + @override + final String name; + + /// An internal counter for encouraging unique naming of unnamed signals. + static int _structIdx = 0; + + /// Creates a new [LogicStructure] with [elements] as elements. + /// + /// None of the [elements] can already be members of another [LogicStructure]. + LogicStructure(Iterable elements, {String? name}) + : name = (name == null || name.isEmpty) + ? 'st${_structIdx++}' + : Sanitizer.sanitizeSV(name) { + _elements + ..addAll(elements) + ..forEach((element) { + if (element._parentStructure != null) { + throw LogicConstructionException( + '$element already is a member of a structure' + ' ${element._parentStructure}.'); + } + + element._parentStructure = this; + }); + } + + /// Creates a new [LogicStructure] with the same structure as `this`. + LogicStructure clone({String? name}) => LogicStructure( + elements.map((e) => e is LogicStructure + ? e.clone() + : Logic(name: e.name, width: e.width)), + name: name ?? this.name); + + @override + String get structureName { + if (parentStructure != null) { + if (isArrayMember) { + return '${parentStructure!.structureName}[${arrayIndex!}]'; + } else { + return '${parentStructure!.structureName}.$name'; + } + } else { + return name; + } + } + + @override + int? get arrayIndex => _arrayIndex; + + @override + int? _arrayIndex; + + @override + bool get isArrayMember => parentStructure is LogicArray; + + @override + void put(dynamic val, {bool fill = false}) { + final logicVal = LogicValue.of(val, fill: fill, width: width); + + var index = 0; + for (final element in leafElements) { + element.put(logicVal.getRange(index, index + element.width)); + index += element.width; + } + } + + @override + void inject(dynamic val, {bool fill = false}) { + Simulator.injectAction(() => put(val, fill: fill)); + } + + @override + void gets(Logic other) { + if (other.width != width) { + throw SignalWidthMismatchException(other, width); + } + + var index = 0; + for (final element in leafElements) { + element <= other.getRange(index, index + element.width); + + index += element.width; + } + } + + @override + Conditional operator <(dynamic other) { + final otherLogic = other is Logic ? other : Const(other, width: width); + + if (otherLogic.width != width) { + throw SignalWidthMismatchException(otherLogic, width); + } + + final conditionalAssigns = []; + + var index = 0; + for (final element in leafElements) { + conditionalAssigns + .add(element < otherLogic.getRange(index, index + element.width)); + index += element.width; + } + + return ConditionalGroup(conditionalAssigns); + } + + /// A list of all leaf-level elements at the deepest hierarchy of this + /// structure provided in index order. + late final List leafElements = + UnmodifiableListView(_calculateLeafElements()); + + /// Compute the list of all leaf elements, to be cached in [leafElements]. + List _calculateLeafElements() { + final leaves = []; + for (final element in elements) { + if (element is LogicStructure) { + leaves.addAll(element.leafElements); + } else { + leaves.add(element); + } + } + return leaves; + } + + @override + void operator <=(Logic other) => gets(other); + + @override + Logic operator [](dynamic index) => packed[index]; + + @override + Logic getRange(int startIndex, [int? endIndex]) { + endIndex ??= width; + + final modifiedStartIndex = + IndexUtilities.wrapIndex(startIndex, width, allowWidth: true); + final modifiedEndIndex = + IndexUtilities.wrapIndex(endIndex, width, allowWidth: true); + + IndexUtilities.validateRange(modifiedStartIndex, modifiedEndIndex); + + // grab all elements that fall in this range, keeping track of the offset + final matchingElements = []; + + final requestedWidth = modifiedEndIndex - modifiedStartIndex; + + var index = 0; + for (final element in leafElements) { + // if the *start* or *end* of `element` is within [startIndex, endIndex], + // then we have to include it in `matchingElements` + final elementStart = index; + final elementEnd = index + element.width; + + final elementInRange = ((elementStart >= modifiedStartIndex) && + (elementStart < modifiedEndIndex)) || + ((elementEnd > modifiedStartIndex) && + (elementEnd <= modifiedEndIndex)); + + if (elementInRange) { + // figure out the subset of `element` that needs to be included + final elementStartGrab = max(elementStart, modifiedStartIndex) - index; + final elementEndGrab = min(elementEnd, modifiedEndIndex) - index; + + matchingElements + .add(element.getRange(elementStartGrab, elementEndGrab)); + } + + index += element.width; + } + + assert(!(matchingElements.isEmpty && requestedWidth != 0), + 'If the requested width is not 0, expect to get some matches.'); + + return matchingElements.rswizzle(); + } + + @override + Logic slice(int endIndex, int startIndex) { + final modifiedStartIndex = IndexUtilities.wrapIndex(startIndex, width); + final modifiedEndIndex = IndexUtilities.wrapIndex(endIndex, width); + + if (modifiedStartIndex <= modifiedEndIndex) { + return getRange(modifiedStartIndex, modifiedEndIndex + 1); + } else { + return getRange(modifiedEndIndex, modifiedStartIndex + 1).reversed; + } + } + + /// Increments each element of [elements] using [Logic.incr]. + @override + Conditional incr({Logic Function(Logic p1)? s, dynamic val = 1}) => + s == null ? (this < this + val) : (s(this) < s(this) + val); + + /// Decrements each element of [elements] using [Logic.decr]. + @override + Conditional decr({Logic Function(Logic p1)? s, dynamic val = 1}) => + s == null ? (this < this - val) : (s(this) < s(this) - val); + + /// Divide-assigns each element of [elements] using [Logic.divAssign]. + @override + Conditional divAssign(dynamic val, {Logic Function(Logic p1)? s}) => + s == null ? (this < this / val) : (s(this) < s(this) / val); + + /// Multiply-assigns each element of [elements] using [Logic.mulAssign]. + @override + Conditional mulAssign(dynamic val, {Logic Function(Logic p1)? s}) => + s == null ? (this < this * val) : (s(this) < s(this) * val); + + @override + late final Iterable dstConnections = [ + for (final element in elements) ...element.dstConnections + ]; + + @override + Module? get parentModule => _parentModule; + @override + Module? _parentModule; + + @protected + @override + set parentModule(Module? newParentModule) { + _parentModule = newParentModule; + for (final element in elements) { + element.parentModule = newParentModule; + } + } + + @override + LogicStructure? get parentStructure => _parentStructure; + + @override + LogicStructure? _parentStructure; + + @override + bool get isInput => parentModule?.isInput(this) ?? false; + + @override + bool get isOutput => parentModule?.isOutput(this) ?? false; + + @override + bool get isPort => isInput || isOutput; + + @override + void makeUnassignable() { + for (final element in elements) { + element.makeUnassignable(); + } + } + + /// A [LogicStructure] never has a direct source driving it, only its + /// [elements] do, so always returns `null`. + @override + Logic? get srcConnection => null; + + @override + LogicValue get value => packed.value; + + @override + late final int width = elements.isEmpty + ? 0 + : elements.map((e) => e.width).reduce((w1, w2) => w1 + w2); + + @override + Logic withSet(int startIndex, Logic update) { + final endIndex = startIndex + update.width; + + if (endIndex > width) { + throw RangeError('Width of update $update at startIndex $startIndex would' + ' overrun the width of the original ($width).'); + } + + if (startIndex < 0) { + throw RangeError( + 'Start index must be greater than zero but was $startIndex'); + } + + final newWithSet = clone(); + + var index = 0; + for (var i = 0; i < leafElements.length; i++) { + final newElement = newWithSet.leafElements[i]; + final element = leafElements[i]; + + final elementWidth = element.width; + + // if the *start* or *end* of `element` is within [startIndex, endIndex], + // then we have to include it in `matchingElements` + final elementStart = index; + final elementEnd = index + elementWidth; + + final elementInRange = + ((elementStart >= startIndex) && (elementStart < endIndex)) || + ((elementEnd > startIndex) && (elementEnd <= endIndex)); + + if (elementInRange) { + newElement <= + element.withSet( + max(startIndex - index, 0), + update.getRange( + max(index - startIndex, 0), + min(index - startIndex + elementWidth, elementWidth), + )); + } else { + newElement <= element; + } + + index += element.width; + } + + return newWithSet; + } + + @override + Logic operator ~() => ~packed; + + @override + Logic operator &(Logic other) => packed & other; + + @override + Logic operator |(Logic other) => packed | other; + + @override + Logic operator ^(Logic other) => packed ^ other; + + @override + Logic operator *(dynamic other) => packed * other; + + @override + Logic operator +(dynamic other) => packed + other; + + @override + Logic operator -(dynamic other) => packed - other; + + @override + Logic operator /(dynamic other) => packed / other; + + @override + Logic operator %(dynamic other) => packed % other; + + @override + Logic pow(dynamic exponent) => packed.pow(exponent); + + @override + Logic operator <<(dynamic other) => packed << other; + + @override + Logic operator >(dynamic other) => packed > other; + + @override + Logic operator >=(dynamic other) => packed >= other; + + @override + Logic operator >>(dynamic other) => packed >> other; + + @override + Logic operator >>>(dynamic other) => packed >>> other; + + @override + Logic and() => packed.and(); + + @override + Logic or() => packed.or(); + + @override + Logic xor() => packed.xor(); + + @Deprecated('Use `value` instead.' + ' Check `width` separately to confirm single-bit.') + @override + LogicValue get bit => packed.bit; + + @override + Stream get changed => packed.changed; + + @override + Logic eq(dynamic other) => packed.eq(other); + + @override + Logic neq(dynamic other) => packed.neq(other); + + @override + SynchronousEmitter get glitch => packed.glitch; + + @override + Logic gt(dynamic other) => packed.gt(other); + + @override + Logic gte(dynamic other) => packed.gte(other); + + @override + Logic lt(dynamic other) => packed.lt(other); + + @override + Logic lte(dynamic other) => packed.lte(other); + + @Deprecated('Use value.isValid instead.') + @override + bool hasValidValue() => packed.hasValidValue(); + + @Deprecated('Use value.isFloating instead.') + @override + bool isFloating() => packed.isFloating(); + + @override + Logic isIn(List list) => packed.isIn(list); + + @override + Stream get negedge => packed.negedge; + + @override + Stream get posedge => packed.posedge; + + @override + Future get nextChanged => packed.nextChanged; + + @override + Future get nextNegedge => packed.nextNegedge; + + @override + Future get nextPosedge => packed.nextPosedge; + + @override + Logic replicate(int multiplier) => packed.replicate(multiplier); + + @override + Logic get reversed => packed.reversed; + + @override + Logic signExtend(int newWidth) => packed.signExtend(newWidth); + + @override + Logic zeroExtend(int newWidth) => packed.zeroExtend(newWidth); + + @override + // ignore: deprecated_member_use_from_same_package + BigInt get valueBigInt => packed.valueBigInt; + + @override + // ignore: deprecated_member_use_from_same_package + int get valueInt => packed.valueInt; + + @override + Logic? get _srcConnection => throw UnsupportedError('Delegated to elements'); + + @override + set _srcConnection(Logic? srcConnection) { + throw UnsupportedError('Delegated to elements'); + } + + @override + bool get _unassignable => throw UnsupportedError('Delegated to elements'); + + @override + set _unassignable(bool unassignable) { + throw UnsupportedError('Delegated to elements'); + } + + @override + _Wire get _wire => throw UnsupportedError('Delegated to elements'); + + @override + set _wire(_Wire wire) { + throw UnsupportedError('Delegated to elements'); + } + + @override + void _assertConnectable(Logic other) { + throw UnsupportedError('Delegated to elements'); + } + + @override + void _connect(Logic other) { + throw UnsupportedError('Delegated to elements'); + } + + @override + Set get _dstConnections => + throw UnsupportedError('Delegated to elements'); + + @override + void _registerConnection(Logic dstConnection) { + throw UnsupportedError('Delegated to elements'); + } + + @override + void _updateWire(_Wire newWire) { + throw UnsupportedError('Delegated to elements'); + } +} diff --git a/lib/src/signals/logic_value_changed.dart b/lib/src/signals/logic_value_changed.dart new file mode 100644 index 000000000..ccf0fb2e5 --- /dev/null +++ b/lib/src/signals/logic_value_changed.dart @@ -0,0 +1,26 @@ +// Copyright (C) 2021-2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_value_changed.dart +// Definition of an event when a signal value changes. +// +// 2023 May 26 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; + +/// Represents the event of a [Logic] changing value. +class LogicValueChanged { + /// The newly updated value of the [Logic]. + final LogicValue newValue; + + /// The previous value of the [Logic]. + final LogicValue previousValue; + + /// Represents the event of a [Logic] changing value from [previousValue] + /// to [newValue]. + const LogicValueChanged(this.newValue, this.previousValue); + + @override + String toString() => '$previousValue --> $newValue'; +} diff --git a/lib/src/signals/port.dart b/lib/src/signals/port.dart new file mode 100644 index 000000000..74b8fc3ee --- /dev/null +++ b/lib/src/signals/port.dart @@ -0,0 +1,25 @@ +// Copyright (C) 2021-2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// port.dart +// Definition of Port. +// +// 2023 May 30 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/name/invalid_portname_exceptions.dart'; +import 'package:rohd/src/utilities/sanitizer.dart'; + +/// An extension of [Logic] which performs some additional validation for +/// inputs and outputs of [Module]s. +/// +/// Useful for [Interface] definitions. +class Port extends Logic { + /// Constructs a [Logic] intended to be used for ports in an [Interface]. + Port(String name, [int width = 1]) : super(name: name, width: width) { + if (!Sanitizer.isSanitary(name)) { + throw InvalidPortNameException(name); + } + } +} diff --git a/lib/src/signals/signals.dart b/lib/src/signals/signals.dart new file mode 100644 index 000000000..ac60e8da6 --- /dev/null +++ b/lib/src/signals/signals.dart @@ -0,0 +1,25 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +library signals; + +import 'dart:async'; +import 'dart:collection'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/exceptions.dart'; +import 'package:rohd/src/utilities/index_utilities.dart'; +import 'package:rohd/src/utilities/sanitizer.dart'; +import 'package:rohd/src/utilities/synchronous_propagator.dart'; + +export 'logic_value_changed.dart'; +export 'port.dart'; + +part 'const.dart'; +part 'logic.dart'; +part 'wire.dart'; +part 'logic_structure.dart'; +part 'logic_array.dart'; diff --git a/lib/src/signals/wire.dart b/lib/src/signals/wire.dart new file mode 100644 index 000000000..dd4b4e31d --- /dev/null +++ b/lib/src/signals/wire.dart @@ -0,0 +1,201 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// wire.dart +// Definition of underlying structure for storing information on a signal. +// +// 2023 May 26 +// Author: Max Korbel + +part of signals; + +/// Represents a physical wire which shares a common value with one or +/// more [Logic]s. +class _Wire { + _Wire({required this.width}) + : _currentValue = LogicValue.filled(width, LogicValue.z); + + /// The current active value of this signal. + LogicValue get value => _currentValue; + + /// The number of bits in this signal. + final int width; + + /// The current active value of this signal. + LogicValue _currentValue; + + /// The last value of this signal before the [Simulator] tick. + /// + /// This is useful for detecting when to trigger an edge. + LogicValue? _preTickValue; + + /// A stream of [LogicValueChanged] events for every time the signal + /// transitions at any time during a [Simulator] tick. + /// + /// This event can occur more than once per edge, or even if there is no edge. + SynchronousEmitter get glitch => _glitchController.emitter; + final SynchronousPropagator _glitchController = + SynchronousPropagator(); + + /// Controller for stable events that can be safely consumed at the + /// end of a [Simulator] tick. + final StreamController _changedController = + StreamController.broadcast(sync: true); + + /// Tracks whether is being subscribed to by anything/anyone. + bool _changedBeingWatched = false; + + /// A [Stream] of [LogicValueChanged] events which triggers at most once + /// per [Simulator] tick, iff the value of the [Logic] has changed. + Stream get changed { + if (!_changedBeingWatched) { + // only do these simulator subscriptions if someone has asked for + // them! saves performance! + _changedBeingWatched = true; + + _preTickSubscription = Simulator.preTick.listen((event) { + _preTickValue = value; + }); + _postTickSubscription = Simulator.postTick.listen((event) { + if (value != _preTickValue && _preTickValue != null) { + _changedController.add(LogicValueChanged(value, _preTickValue!)); + } + }); + } + return _changedController.stream; + } + + /// The subscription to the [Simulator]'s `preTick`. + /// + /// Only non-null if [_changedBeingWatched] is true. + late final StreamSubscription _preTickSubscription; + + /// The subscription to the [Simulator]'s `postTick`. + /// + /// Only non-null if [_changedBeingWatched] is true. + late final StreamSubscription _postTickSubscription; + + /// Cancels all [Simulator] subscriptions and uses [newChanged] as the + /// source to replace all [changed] events for this [_Wire]. + void _migrateChangedTriggers(Stream newChanged) { + if (_changedBeingWatched) { + unawaited(_preTickSubscription.cancel()); + unawaited(_postTickSubscription.cancel()); + newChanged.listen(_changedController.add); + _changedBeingWatched = false; + } + } + + /// Tells this [_Wire] to adopt all the behavior of [other] so that + /// it can replace [other]. + void _adopt(_Wire other) { + _glitchController.emitter.adopt(other._glitchController.emitter); + other._migrateChangedTriggers(changed); + } + + /// Store the [negedge] stream to avoid creating multiple copies + /// of streams. + Stream? _negedge; + + /// Store the [posedge] stream to avoid creating multiple copies + /// of streams. + Stream? _posedge; + + /// A [Stream] of [LogicValueChanged] events which triggers at most once + /// per [Simulator] tick, iff the value of the [Logic] has changed + /// from `1` to `0`. + /// + /// Throws an exception if [width] is not `1`. + Stream get negedge { + if (width != 1) { + throw Exception( + 'Can only detect negedge when width is 1, but was $width'); + } + + _negedge ??= changed.where((args) => LogicValue.isNegedge( + args.previousValue, + args.newValue, + ignoreInvalid: true, + )); + + return _negedge!; + } + + /// A [Stream] of [LogicValueChanged] events which triggers at most once + /// per [Simulator] tick, iff the value of the [Logic] has changed + /// from `0` to `1`. + /// + /// Throws an exception if [width] is not `1`. + Stream get posedge { + if (width != 1) { + throw Exception( + 'Can only detect posedge when width is 1, but was $width'); + } + + _posedge ??= changed.where((args) => LogicValue.isPosedge( + args.previousValue, + args.newValue, + ignoreInvalid: true, + )); + + return _posedge!; + } + + /// Triggers at most once, the next time that this [Logic] changes + /// value at the end of a [Simulator] tick. + Future get nextChanged => changed.first; + + /// Triggers at most once, the next time that this [Logic] changes + /// value at the end of a [Simulator] tick from `0` to `1`. + /// + /// Throws an exception if [width] is not `1`. + Future get nextPosedge => posedge.first; + + /// Triggers at most once, the next time that this [Logic] changes + /// value at the end of a [Simulator] tick from `1` to `0`. + /// + /// Throws an exception if [width] is not `1`. + Future get nextNegedge => negedge.first; + + /// Injects a value onto this signal in the current [Simulator] tick. + /// + /// This function calls [put()] in [Simulator.injectAction()]. + void inject(dynamic val, {required String signalName, bool fill = false}) { + Simulator.injectAction(() => put(val, signalName: signalName, fill: fill)); + } + + /// Keeps track of whether there is an active put, to detect reentrance. + bool _isPutting = false; + + /// Puts a value [val] onto this signal, which may or may not be picked up + /// for [changed] in this [Simulator] tick. + /// + /// The type of [val] and usage of [fill] should be supported by + /// [LogicValue.of]. + /// + /// This function is used for propogating glitches through connected signals. + /// Use this function for custom definitions of [Module] behavior. + void put(dynamic val, {required String signalName, bool fill = false}) { + var newValue = LogicValue.of(val, fill: fill, width: width); + + if (newValue.width != width) { + throw PutException(signalName, + 'Updated value width mismatch. The width of $val should be $width.'); + } + + if (_isPutting) { + // if this is the result of a cycle, then contention! + newValue = LogicValue.filled(width, LogicValue.x); + } + + final prevValue = _currentValue; + _currentValue = newValue; + + // sends out a glitch if the value deposited has changed + if (_currentValue != prevValue) { + _isPutting = true; + _glitchController.add(LogicValueChanged(_currentValue, prevValue)); + _isPutting = false; + } + } +} diff --git a/lib/src/state_machine.dart b/lib/src/state_machine.dart index c8282c97c..d805a42ee 100644 --- a/lib/src/state_machine.dart +++ b/lib/src/state_machine.dart @@ -85,11 +85,11 @@ class StateMachine { _stateValueLookup[ _stateLookup[entry.value]] ])) - .toList(), + .toList(growable: false), conditionalType: ConditionalType.unique, defaultItem: [nextState < currentState]) ])) - .toList(), + .toList(growable: false), conditionalType: ConditionalType.unique, defaultItem: [nextState < currentState]) ]); diff --git a/lib/src/swizzle.dart b/lib/src/swizzle.dart index 4bd86872f..c11bf9a3e 100644 --- a/lib/src/swizzle.dart +++ b/lib/src/swizzle.dart @@ -21,7 +21,7 @@ extension LogicSwizzle on List { /// significant (highest) bits. /// /// If you want the opposite, check out [rswizzle]. - Logic swizzle() => Swizzle(this).out; + Logic swizzle() => length == 1 ? first : Swizzle(this).out; /// Performs a concatenation operation on the list of signals, where index 0 /// of this list is the *least* significant bit(s). @@ -32,7 +32,8 @@ extension LogicSwizzle on List { /// significant (lowest) bits. /// /// If you want the opposite, check out [swizzle]. - Logic rswizzle() => Swizzle(reversed.toList()).out; + Logic rswizzle() => + length == 1 ? first : Swizzle(reversed.toList(growable: false)).out; } /// Allows lists of [LogicValue]s to be swizzled. @@ -46,7 +47,7 @@ extension LogicValueSwizzle on List { /// most significant (highest) bits. /// /// If you want the opposite, check out [rswizzle]. - LogicValue swizzle() => LogicValue.of(reversed); + LogicValue swizzle() => length == 1 ? first : LogicValue.ofIterable(reversed); /// Performs a concatenation operation on the list of signals, where index 0 /// of this list is the *least* significant bit. @@ -57,7 +58,7 @@ extension LogicValueSwizzle on List { /// least significant (lowest) bits. /// /// If you want the opposite, check out [swizzle]. - LogicValue rswizzle() => LogicValue.of(this); + LogicValue rswizzle() => length == 1 ? first : LogicValue.ofIterable(this); } /// Performs a concatenation operation on the list of signals, where index 0 of diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index ef953f5d1..2f8ba03d3 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -65,7 +65,7 @@ class SynthBuilder { /// the [synthesizer]. List getFileContents() => synthesisResults .map((synthesisResult) => synthesisResult.toFileContents()) - .toList(); + .toList(growable: false); /// Provides an instance type name for [module]. /// diff --git a/lib/src/synthesizers/systemverilog.dart b/lib/src/synthesizers/systemverilog.dart index addf18f43..0544acb8e 100644 --- a/lib/src/synthesizers/systemverilog.dart +++ b/lib/src/synthesizers/systemverilog.dart @@ -150,14 +150,14 @@ class _SystemVerilogSynthesisResult extends SynthesisResult { List _verilogInputs() { final declarations = _synthModuleDefinition.inputs .map((sig) => 'input logic ${sig.definitionName()}') - .toList(); + .toList(growable: false); return declarations; } List _verilogOutputs() { final declarations = _synthModuleDefinition.outputs .map((sig) => 'output logic ${sig.definitionName()}') - .toList(); + .toList(growable: false); return declarations; } @@ -321,9 +321,25 @@ class _SynthModuleDefinition { } else if (logicToSynthMap.containsKey(logic)) { return logicToSynthMap[logic]!; } else { - final newSynth = _SynthLogic( - logic, _getUniqueSynthLogicName(logic.name, allowPortName), - renameable: !allowPortName); + _SynthLogic newSynth; + if (logic.isArrayMember) { + // grab the parent array (potentially recursively) + final parentArraySynthLogic = + // ignore: unnecessary_null_checks + _getSynthLogic(logic.parentStructure!, allowPortName); + + newSynth = _SynthLogicArrayElement(logic, parentArraySynthLogic!); + } else { + final synthLogicName = + _getUniqueSynthLogicName(logic.name, allowPortName); + + newSynth = _SynthLogic( + logic, + synthLogicName, + renameable: !allowPortName, + ); + } + logicToSynthMap[logic] = newSynth; return newSynth; } @@ -357,10 +373,22 @@ class _SynthModuleDefinition { for (var i = 0; i < logicsToTraverse.length; i++) { final receiver = logicsToTraverse[i]; + if (receiver is LogicArray) { + logicsToTraverse.addAll(receiver.elements); + } + + if (receiver.isArrayMember) { + logicsToTraverse.add(receiver.parentStructure!); + } + final driver = receiver.srcConnection; - final receiverIsModuleInput = module.isInput(receiver); - final receiverIsModuleOutput = module.isOutput(receiver); + final receiverIsConstant = driver == null && receiver is Const; + + final receiverIsModuleInput = + module.isInput(receiver) && !receiver.isArrayMember; + final receiverIsModuleOutput = + module.isOutput(receiver) && !receiver.isArrayMember; final driverIsModuleInput = driver != null && module.isInput(driver); final driverIsModuleOutput = driver != null && module.isOutput(driver); @@ -385,16 +413,16 @@ class _SynthModuleDefinition { _getSynthSubModuleInstantiation(subModule); subModuleInstantiation.outputMapping[synthReceiver] = receiver; - subModule.inputs.values.forEach(logicsToTraverse.add); + logicsToTraverse.addAll(subModule.inputs.values); } else if (driver != null) { if (!module.isInput(receiver)) { // stop at the input to this module logicsToTraverse.add(driver); assignments.add(_SynthAssignment(synthDriver, synthReceiver)); } - } else if (driver == null && receiver.value.isValid) { + } else if (receiverIsConstant && receiver.value.isValid) { assignments.add(_SynthAssignment(receiver.value, synthReceiver)); - } else if (driver == null && !receiver.value.isFloating) { + } else if (receiverIsConstant && !receiver.value.isFloating) { // this is a signal that is *partially* invalid (e.g. 0b1z1x0) assignments.add(_SynthAssignment(receiver.value, synthReceiver)); } @@ -519,8 +547,9 @@ class _SynthModuleDefinition { } else { reducedAssignments.add(assignment); } - } else if (dst.renameable) { + } else if (dst.renameable && Module.isUnpreferred(dst.name)) { // src is a constant, feed that string directly in + // but only if this isn't a preferred signal (e.g. bus subset) dst.mergeConst(assignment.srcName()); } else { // nothing can be done here, keep it as-is @@ -535,6 +564,22 @@ class _SynthModuleDefinition { } } +class _SynthLogicArrayElement extends _SynthLogic { + /// The [_SynthLogic] tracking the name of the direct parent array. + final _SynthLogic parentArray; + + @override + bool get _needsDeclaration => false; + + @override + String get name => '${parentArray.name}[${logic.arrayIndex!}]'; + + _SynthLogicArrayElement(Logic logic, this.parentArray) + : assert(logic.isArrayMember, + 'Should only be used for elements in a LogicArray'), + super(logic, '**ARRAY_ELEMENT**', renameable: false); +} + /// Represents a logic signal in the generated code within a module. class _SynthLogic { final Logic logic; @@ -548,7 +593,9 @@ class _SynthLogic { String get name => _mergedNameSynthLogic?.name ?? _mergedConst ?? _name; _SynthLogic(this.logic, this._name, {bool renameable = true}) - : _renameable = renameable; + : _renameable = renameable && + // don't rename arrays since its elements would need updating too + logic is! LogicArray; @override String toString() => "'$name', logic name: '${logic.name}'"; @@ -583,12 +630,49 @@ class _SynthLogic { _needsDeclaration = false; } + static String _widthToRangeDef(int width, {bool forceRange = false}) { + if (width > 1 || forceRange) { + return '[${width - 1}:0]'; + } else { + return ''; + } + } + + /// Computes the name of the signal at declaration time with appropriate + /// dimensions included. String definitionName() { - if (logic.width > 1) { - return '[${logic.width - 1}:0] $name'; + String packedDims; + String unpackedDims; + + if (logic is LogicArray) { + final logicArr = logic as LogicArray; + + final packedDimsBuf = StringBuffer(); + final unpackedDimsBuf = StringBuffer(); + + final dims = logicArr.dimensions; + for (var i = 0; i < dims.length; i++) { + final dim = dims[i]; + final dimStr = _widthToRangeDef(dim, forceRange: true); + if (i < logicArr.numUnpackedDimensions) { + unpackedDimsBuf.write(dimStr); + } else { + packedDimsBuf.write(dimStr); + } + } + + packedDimsBuf.write(_widthToRangeDef(logicArr.elementWidth)); + + packedDims = packedDimsBuf.toString(); + unpackedDims = unpackedDimsBuf.toString(); } else { - return name; + packedDims = _widthToRangeDef(logic.width); + unpackedDims = ''; } + + return [packedDims, name, unpackedDims] + .where((e) => e.isNotEmpty) + .join(' '); } } diff --git a/lib/src/utilities/index_utilities.dart b/lib/src/utilities/index_utilities.dart new file mode 100644 index 000000000..f17d1f4db --- /dev/null +++ b/lib/src/utilities/index_utilities.dart @@ -0,0 +1,51 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// index_utilities.dart +// Code for modifying and validating indices. +// +// 2023 June 1 +// Author: Max Korbel + +/// A utility class for modifying and validating indices. +abstract class IndexUtilities { + /// Computes a modified version of an index into an array that allows for + /// negative values to wrap around from the end. + /// + /// Guaranteed to either return an index in `[0, width)` or else throw + /// an exception. + /// + /// If [allowWidth], then the range is `[0, width]` instead. + static int wrapIndex(int originalIndex, int width, + {bool allowWidth = false}) { + final modifiedIndex = + (originalIndex < 0) ? width + originalIndex : originalIndex; + + // check that it meets indexing requirements + if (modifiedIndex < 0 || + modifiedIndex > width || + (!allowWidth && modifiedIndex == width)) { + // The suggestion in the deprecation for this constructor is not available + // before 2.19, so keep it in here for now. Eventually, switch to the + // new one. + // ignore: deprecated_member_use + throw IndexError( + originalIndex, + width, + 'IndexOutOfRange', + 'Index out of range:' + ' $modifiedIndex(=$originalIndex) for width $width.', + width); + } + + return modifiedIndex; + } + + /// Validates that the range is legal. + static void validateRange(int startIndex, int endIndex, + {bool allowEqual = true}) { + if (endIndex < startIndex || (!allowEqual && endIndex == startIndex)) { + throw RangeError('End $endIndex cannot be less than start $startIndex.'); + } + } +} diff --git a/lib/src/utilities/simcompare.dart b/lib/src/utilities/simcompare.dart index 0db5655b4..b3e6a4554 100644 --- a/lib/src/utilities/simcompare.dart +++ b/lib/src/utilities/simcompare.dart @@ -46,14 +46,12 @@ class Vector { /// Computes a SystemVerilog code string that checks in a SystemVerilog /// simulation whether a signal [sigName] has the [expected] value given /// the [inputValues]. - String _errorCheckString( - String sigName, dynamic expected, String 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 = (expected is LogicValue && expected.width == 1) - ? "'${expected.toString(includeWidth: false)}" - : expected.toString(); + final expectedValStr = expectedVal.toString(); if (expected is! int && expected is! LogicValue) { throw Exception( @@ -66,14 +64,53 @@ class Vector { } /// Converts this vector into a SystemVerilog check. - String toTbVerilog() { - final assignments = inputValues.keys - .map((signalName) => '$signalName = ${inputValues[signalName]};') - .join('\n'); - final checks = expectedOutputValues.keys - .map((signalName) => _errorCheckString(signalName, - expectedOutputValues[signalName], inputValues.toString())) - .join('\n'); + String toTbVerilog(Module module) { + final assignments = inputValues.keys.map((signalName) { + // ignore: invalid_use_of_protected_member + final signal = module.input(signalName); + + if (signal is LogicArray) { + final arrAssigns = StringBuffer(); + var index = 0; + final fullVal = + LogicValue.of(inputValues[signalName], width: signal.width); + for (final leaf in signal.leafElements) { + final subVal = fullVal.getRange(index, index + leaf.width); + arrAssigns.writeln('${leaf.structureName} = $subVal;'); + index += leaf.width; + } + return arrAssigns.toString(); + } else { + return '$signalName = ${inputValues[signalName]};'; + } + }).join('\n'); + + final checksList = []; + for (final expectedOutput in expectedOutputValues.entries) { + final outputName = expectedOutput.key; + final outputPort = module.output(outputName); + final expected = expectedOutput.value; + final expectedValue = LogicValue.of( + expected, + width: outputPort.width, + ); + final inputStimulus = inputValues.toString(); + + if (outputPort is LogicArray) { + var index = 0; + for (final leaf in outputPort.leafElements) { + final subVal = expectedValue.getRange(index, index + leaf.width); + checksList.add(_errorCheckString( + leaf.structureName, subVal, subVal, inputStimulus)); + index += leaf.width; + } + } else { + checksList.add(_errorCheckString( + outputName, expected, expectedValue, inputStimulus)); + } + } + final checks = checksList.join('\n'); + final tbVerilog = [ assignments, '#$_offset', @@ -123,7 +160,7 @@ abstract class SimCompare { expect(oBit, equals(value), reason: errorReason); } } else { - expect(o.value, equals(value)); + expect(o.value, equals(value), reason: errorReason); } } else { throw NonSupportedTypeException(value.runtimeType.toString()); @@ -166,6 +203,7 @@ abstract class SimCompare { bool allowWarnings = false, bool maskKnownWarnings = true, bool enableChecking = true, + bool buildOnly = false, }) { final result = iverilogVector(module, vectors, moduleName: moduleName, @@ -173,7 +211,8 @@ abstract class SimCompare { dumpWaves: dumpWaves, iverilogExtraArgs: iverilogExtraArgs, allowWarnings: allowWarnings, - maskKnownWarnings: maskKnownWarnings); + maskKnownWarnings: maskKnownWarnings, + buildOnly: buildOnly); if (enableChecking) { expect(result, true); } @@ -189,10 +228,21 @@ abstract class SimCompare { List iverilogExtraArgs = const [], bool allowWarnings = false, bool maskKnownWarnings = true, + bool buildOnly = false, }) { String signalDeclaration(String signalName) { final signal = module.signals.firstWhere((e) => e.name == signalName); - if (signal.width != 1) { + + if (signal is LogicArray) { + final unpackedDims = + signal.dimensions.getRange(0, signal.numUnpackedDimensions); + final packedDims = signal.dimensions + .getRange(signal.numUnpackedDimensions, signal.dimensions.length); + // ignore: parameter_assignments, prefer_interpolation_to_compose_strings + return packedDims.map((d) => '[${d - 1}:0]').join() + + ' [${signal.elementWidth - 1}:0] $signalName' + + unpackedDims.map((d) => '[${d - 1}:0]').join(); + } else if (signal.width != 1) { return '[${signal.width - 1}:0] $signalName'; } else { return signalName; @@ -201,14 +251,14 @@ abstract class SimCompare { final topModule = moduleName ?? module.definitionName; final allSignals = { - for (final e in vectors) ...e.inputValues.keys, - for (final e in vectors) ...e.expectedOutputValues.keys, + for (final v in vectors) ...v.inputValues.keys, + for (final v in vectors) ...v.expectedOutputValues.keys, }; final localDeclarations = allSignals.map((e) => 'logic ${signalDeclaration(e)};').join('\n'); final moduleConnections = allSignals.map((e) => '.$e($e)').join(', '); final moduleInstance = '$topModule dut($moduleConnections);'; - final stimulus = vectors.map((e) => e.toTbVerilog()).join('\n'); + final stimulus = vectors.map((e) => e.toTbVerilog(module)).join('\n'); final generatedVerilog = module.generateSynth(); // so that when they run in parallel, they dont step on each other @@ -258,7 +308,7 @@ abstract class SimCompare { return line; }) .whereNotNull() - .join(); + .join('\n'); if (maskedOutput.isNotEmpty) { print(maskedOutput); } @@ -279,12 +329,14 @@ abstract class SimCompare { return false; } - final simResult = Process.runSync('vvp', [tmpOutput]); - if (printIfContentsAndCheckError(simResult.stdout)) { - return false; - } - if (printIfContentsAndCheckError(simResult.stderr)) { - return false; + if (!buildOnly) { + final simResult = Process.runSync('vvp', [tmpOutput]); + if (printIfContentsAndCheckError(simResult.stdout)) { + return false; + } + if (printIfContentsAndCheckError(simResult.stderr)) { + return false; + } } if (!dontDeleteTmpFiles) { diff --git a/lib/src/values/big_logic_value.dart b/lib/src/values/big_logic_value.dart index 18d1f6c7f..43845638a 100644 --- a/lib/src/values/big_logic_value.dart +++ b/lib/src/values/big_logic_value.dart @@ -110,7 +110,7 @@ class _BigLogicValue extends LogicValue { } @override - LogicValue get reversed => LogicValue.of(toList().reversed); + LogicValue get reversed => LogicValue.ofIterable(toList().reversed); @override bool get isValid => _invalid.sign == 0; diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart index 4cc34876a..6dc4f5de9 100644 --- a/lib/src/values/logic_value.dart +++ b/lib/src/values/logic_value.dart @@ -94,7 +94,7 @@ abstract class LogicValue { /// Constructs a [LogicValue] with the [width] number of bits, where every /// bit has the same value of [fill]. /// - /// [width] must be greater than or equal to 0. + /// [width] must be greater than or equal to 0. [fill] must be 1 bit. static LogicValue filled(int width, LogicValue fill) => _FilledLogicValue(fill._enum, width); @@ -114,6 +114,155 @@ abstract class LogicValue { : throw Exception('Failed to convert.'); } + // complete test suite for LogicValue.of + + /// Constructs a [LogicValue] from [val] which could be of a variety of types. + /// + /// Supported types include [String], [bool], [int], [BigInt], [LogicValue], + /// and [Iterable]. + /// + /// If [fill] is set, then all bits of the returned value will be set to + /// [val]. If the [val] is not representable as a single bit of information, + /// then setting [fill] will throw an [Exception]. + /// + /// If the [width] can be inferred from the type (e.g. [String], [LogicValue], + /// [Iterable]), then [width] does not need to be provided. + /// If [width] is provided and does not match an inferred width, then [width] + /// is used. + /// If a [width] cannot be inferred, then it is required, or else it will + /// throw an [Exception]. + /// If [val] does not fit in a specified [width], then the returned value will + /// be truncated. + /// [bool]s will infer a default width of `1`, but it can be overridden. + /// Invalid 1-bit [val]s will always be [fill]ed even if [fill] is `false`, + /// but will default to a width of 1 unless [width] is specified. + static LogicValue of(dynamic val, {bool fill = false, int? width}) { + if (val is int) { + if (width == null) { + throw LogicValueConstructionException( + '`width` must be provided for `int`.'); + } + + if (fill) { + return LogicValue.filled( + width, + val == 0 + ? LogicValue.zero + : val == 1 + ? LogicValue.one + : throw LogicValueConstructionException( + '`int` can only can fill 0 or 1, but saw $val.')); + } else { + return LogicValue.ofInt(val, width); + } + } else if (val is BigInt) { + if (width == null) { + throw LogicValueConstructionException( + '`width` must be provided for `BigInt`.'); + } + + if (fill) { + return LogicValue.filled( + width, + val == BigInt.zero + ? LogicValue.zero + : val == BigInt.one + ? LogicValue.one + : throw LogicValueConstructionException( + '`BigInt` can only fill 0 or 1, but saw $val.')); + } else { + return LogicValue.ofBigInt(val, width); + } + } else if (val is bool) { + width ??= 1; + + if (fill) { + return LogicValue.filled(width, val ? LogicValue.one : LogicValue.zero); + } + return LogicValue.ofInt(val ? 1 : 0, width); + } else if (val is LogicValue) { + if (fill && val.width != 1) { + throw LogicValueConstructionException( + 'Only 1-bit `LogicValue`s can be filled'); + } + + if (val.width == 1 && (!val.isValid || fill)) { + if (!val.isValid) { + // ignore: parameter_assignments + width ??= 1; + } + if (width == null) { + throw LogicValueConstructionException( + 'Filled `LogicValue` $val must have provided a width.'); + } + return LogicValue.filled(width, val); + } else { + if (val.width == width || width == null) { + return val; + } else if (width < val.width) { + return val.getRange(0, width); + } else { + return val.zeroExtend(width); + } + } + } else if (val is String) { + if (fill && val.length != 1) { + throw LogicValueConstructionException( + 'Only 1-bit values can be filled'); + } + + if (val.length == 1 && (val == 'x' || val == 'z' || fill)) { + if (val == 'x' || val == 'z') { + // ignore: parameter_assignments + width ??= 1; + } + if (width == null) { + throw LogicValueConstructionException( + 'Filled `String` $val must have provided a width.'); + } + return LogicValue.filled(width, LogicValue.ofString(val)); + } else { + if (val.length == width || width == null) { + return LogicValue.ofString(val); + } else if (width < val.length) { + return LogicValue.ofString(val.substring(0, width)); + } else { + return LogicValue.ofString(val).zeroExtend(width); + } + } + } else if (val is Iterable) { + if (fill && val.length != 1) { + throw LogicValueConstructionException( + 'Only 1-bit values can be filled'); + } + + if (val.length == 1 && + (val.first == LogicValue.x || val.first == LogicValue.z || fill)) { + if (!val.first.isValid) { + // ignore: parameter_assignments + width ??= 1; + } + if (width == null) { + throw LogicValueConstructionException( + 'Filled `Iterable` $val must have provided a width.'); + } + return LogicValue.filled(width, val.first); + } else { + if (val.length == width || width == null) { + return LogicValue.ofIterable(val); + } else if (width < val.length) { + return LogicValue.ofIterable(val).getRange(0, width); + } else { + return LogicValue.ofIterable(val).zeroExtend(width); + } + } + } else { + throw LogicValueConstructionException('Unrecognized value type "$val" - ' + 'Unknown type ${val.runtimeType}' + ' cannot be converted to `LogicValue.'); + } + } + /// Constructs a [LogicValue] from [it]. /// /// The order of the created [LogicValue] will be such that the `i`th entry in @@ -128,7 +277,7 @@ abstract class LogicValue { /// var lv = LogicValue.of(it); /// print(lv); // This prints `6'b01xzx0` /// ``` - static LogicValue of(Iterable it) { + static LogicValue ofIterable(Iterable it) { var smallBuffer = LogicValue.empty; var fullResult = LogicValue.empty; @@ -211,7 +360,7 @@ abstract class LogicValue { /// print(lv); // This prints `3b'1x0` /// ``` @Deprecated('Use `of` instead.') - static LogicValue from(Iterable it) => of(it); + static LogicValue from(Iterable it) => ofIterable(it); /// Returns true if bits in [x] are all 0 static bool _bigIntIs0(BigInt x, int width) => @@ -384,7 +533,8 @@ abstract class LogicValue { /// print(lv); // This prints `[1'h0, 1'bx, 1'h1]` /// ``` List toList() => - List.generate(width, (index) => this[index]).toList(); + List.generate(width, (index) => this[index]) + .toList(growable: false); /// Converts this [LogicValue] to a binary [String], including a decorator at /// the front in SystemVerilog style. @@ -401,8 +551,9 @@ abstract class LogicValue { @override String toString({bool includeWidth = true}) { if (isValid && includeWidth) { - final hexValue = width > _INT_BITS - ? toBigInt().toRadixString(16) + // for ==_INT_BITS, still use BigInt so we don't get negatives + final hexValue = width >= _INT_BITS + ? toBigInt().toUnsigned(width).toRadixString(16) : toInt().toRadixString(16); return "$width'h$hexValue"; } else { @@ -445,15 +596,8 @@ abstract class LogicValue { /// ``` /// LogicValue operator [](int index) { - final modifiedIndex = (index < 0) ? width + index : index; - if (modifiedIndex >= width || modifiedIndex < 0) { - // The suggestion in the deprecation for this constructor is not available - // before 2.19, so keep it in here for now. Eventually, switch to the - // new one. - // ignore: deprecated_member_use - throw IndexError(index, this, 'LogicValueIndexOutOfRange', - 'Index out of range: $modifiedIndex(=$index).', width); - } + final modifiedIndex = IndexUtilities.wrapIndex(index, width); + return _getIndex(modifiedIndex); } @@ -493,24 +637,14 @@ abstract class LogicValue { /// LogicValue getRange(int startIndex, [int? endIndex]) { endIndex ??= width; + final modifiedStartIndex = - (startIndex < 0) ? width + startIndex : startIndex; - final modifiedEndIndex = (endIndex < 0) ? width + endIndex : endIndex; - if (modifiedEndIndex < modifiedStartIndex) { - throw Exception( - 'End $modifiedEndIndex(=$endIndex) cannot be less than start ' - '$modifiedStartIndex(=$startIndex).'); - } - if (modifiedEndIndex > width) { - throw Exception( - 'End $modifiedEndIndex(=$endIndex) must be less than width' - ' ($width).'); - } - if (modifiedStartIndex < 0) { - throw Exception( - 'Start $modifiedStartIndex(=$startIndex) must be greater than or ' - 'equal to 0.'); - } + IndexUtilities.wrapIndex(startIndex, width, allowWidth: true); + final modifiedEndIndex = + IndexUtilities.wrapIndex(endIndex, width, allowWidth: true); + + IndexUtilities.validateRange(modifiedStartIndex, modifiedEndIndex); + return _getRange(modifiedStartIndex, modifiedEndIndex); } @@ -534,9 +668,9 @@ abstract class LogicValue { /// LogicValue.ofString('xz01').slice(-2, -2); // LogicValue.ofString('z') /// ``` LogicValue slice(int endIndex, int startIndex) { - final modifiedStartIndex = - (startIndex < 0) ? width + startIndex : startIndex; - final modifiedEndIndex = (endIndex < 0) ? width + endIndex : endIndex; + final modifiedStartIndex = IndexUtilities.wrapIndex(startIndex, width); + final modifiedEndIndex = IndexUtilities.wrapIndex(endIndex, width); + if (modifiedStartIndex <= modifiedEndIndex) { return getRange(modifiedStartIndex, modifiedEndIndex + 1); } else { @@ -1007,14 +1141,15 @@ abstract class LogicValue { } } - /// Returns new [LogicValue] replicated [multiplier] times. An exception will - /// be thrown in case the multiplier is <1 + /// 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)); + return LogicValue.ofIterable(List.filled(multiplier, this)); } } diff --git a/lib/src/values/small_logic_value.dart b/lib/src/values/small_logic_value.dart index d461ff72a..b8c133579 100644 --- a/lib/src/values/small_logic_value.dart +++ b/lib/src/values/small_logic_value.dart @@ -94,7 +94,7 @@ class _SmallLogicValue extends LogicValue { } @override - LogicValue get reversed => LogicValue.of(toList().reversed); + LogicValue get reversed => LogicValue.ofIterable(toList().reversed); @override bool get isValid => _invalid == 0; diff --git a/lib/src/values/values.dart b/lib/src/values/values.dart index a38f6a213..e9494af87 100644 --- a/lib/src/values/values.dart +++ b/lib/src/values/values.dart @@ -1,5 +1,5 @@ -/// Copyright (C) 2021-2022 Intel Corporation -/// SPDX-License-Identifier: BSD-3-Clause +// Copyright (C) 2021-2022 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause library values; @@ -8,6 +8,7 @@ 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/utilities/index_utilities.dart'; part 'logic_value.dart'; part 'small_logic_value.dart'; diff --git a/test/bus_test.dart b/test/bus_test.dart index 52c51fbca..6fd5bd6e1 100644 --- a/test/bus_test.dart +++ b/test/bus_test.dart @@ -9,7 +9,7 @@ /// import 'package:rohd/rohd.dart'; -import 'package:rohd/src/exceptions/logic/logic_exceptions.dart'; +import 'package:rohd/src/exceptions/exceptions.dart'; import 'package:rohd/src/utilities/simcompare.dart'; import 'package:test/test.dart'; @@ -140,7 +140,7 @@ class BusTestModule extends Module { aNegativeRange3 <= a.getRange(-1, 8); aNegativeRange4 <= a.getRange(-3); - aOperatorIndexing1 <= a[0]; + aOperatorIndexing1 <= a.elements[0]; aOperatorIndexing2 <= a[a.width - 1]; aOperatorIndexing3 <= a[4]; aOperatorNegIndexing1 <= a[-a.width]; @@ -154,6 +154,14 @@ class BusTestModule extends Module { } } +class ConstBusModule extends Module { + ConstBusModule(int c, {required bool subset}) { + final outWidth = subset ? 8 : 16; + addOutput('const_subset', width: outWidth) <= + Const(c, width: 16).getRange(0, outWidth); + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -260,13 +268,16 @@ void main() { expect(out.value.toInt(), equals(0x55aa)); }); - group('put exceptions', () { - test('width mismatch', () { - expect( - () => Logic(name: 'byteSignal', width: 8) - .put(LogicValue.ofString('1010')), - throwsA(const TypeMatcher()), - ); + group('put width mismatches', () { + test('width too small', () { + final smallVal = LogicValue.ofString('1010'); + final byteSignal = Logic(name: 'byteSignal', width: 8)..put(smallVal); + expect(byteSignal.value, smallVal.zeroExtend(8)); + }); + test('width too big', () { + final bigVal = LogicValue.ofString('1010101010'); + final byteSignal = Logic(name: 'byteSignal', width: 8)..put(bigVal); + expect(byteSignal.value, bigVal.getRange(0, 8)); }); }); @@ -294,6 +305,33 @@ void main() { }); group('simcompare', () { + group('const sv gen', () { + test('Subset of a const', () async { + final mod = ConstBusModule(0xabcd, subset: true); + await mod.build(); + final vectors = [ + Vector({}, {'const_subset': 0xcd}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + + test('Assignment of a const', () async { + final mod = ConstBusModule(0xabcd, subset: false); + await mod.build(); + final vectors = [ + Vector({}, {'const_subset': 0xabcd}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + + final sv = mod.generateSynth(); + expect(sv.contains("assign const_subset = 16'habcd;"), true); + }); + }); + test('NotGate bus', () async { final gtm = BusTestModule(Logic(width: 8), Logic(width: 8)); await gtm.build(); @@ -337,8 +375,7 @@ void main() { ]; await SimCompare.checkFunctionalVector(gtm, vectors); - final simResult = SimCompare.iverilogVector(gtm, vectors); - expect(simResult, equals(true)); + SimCompare.checkIverilogVector(gtm, vectors); }); test('Bus shrink', () async { diff --git a/test/changed_test.dart b/test/changed_test.dart index 28f434e62..4c1ffc58a 100644 --- a/test/changed_test.dart +++ b/test/changed_test.dart @@ -78,42 +78,55 @@ void main() { expect(numPosedges, equals(1)); }); - test('injection triggers flop', () async { - final baseClk = SimpleClockGenerator(10).clk; + group('injection triggers flop', () { + Future injectionTriggersFlop({required bool useArrays}) async { + final baseClk = SimpleClockGenerator(10).clk; - final clk = Logic(); - final d = Logic(); + Logic genSignal() => useArrays ? LogicArray([1], 1) : Logic(); - final q = FlipFlop(clk, d).q; + final clk = genSignal(); + final d = genSignal(); - var qHadPosedge = false; + final q = genSignal()..gets(FlipFlop(clk, d).q); - Simulator.setMaxSimTime(100); + var qHadPosedge = false; - unawaited(q.nextPosedge.then((value) { - qHadPosedge = true; - })); + Simulator.setMaxSimTime(100); - unawaited(Simulator.run()); + unawaited(q.nextPosedge.then((value) { + qHadPosedge = true; + })); - await baseClk.nextPosedge; - clk.inject(0); - d.inject(0); - await baseClk.nextPosedge; - clk.inject(1); - await baseClk.nextPosedge; - expect(q.value, equals(LogicValue.zero)); - clk.inject(0); - d.inject(1); - await baseClk.nextPosedge; - clk.inject(1); - await baseClk.nextPosedge; - expect(q.value, equals(LogicValue.one)); + unawaited(Simulator.run()); - await Simulator.simulationEnded; + await baseClk.nextPosedge; + clk.inject(0); + d.inject(0); + await baseClk.nextPosedge; + clk.inject(1); + await baseClk.nextPosedge; + expect(q.value, equals(LogicValue.zero)); + clk.inject(0); + d.inject(1); + await baseClk.nextPosedge; + clk.inject(1); + await baseClk.nextPosedge; + expect(q.value, equals(LogicValue.one)); - expect(qHadPosedge, equals(true)); + await Simulator.simulationEnded; + + expect(qHadPosedge, equals(true)); + } + + test('normal logic', () async { + await injectionTriggersFlop(useArrays: false); + }); + + test('arrays', () async { + await injectionTriggersFlop(useArrays: true); + }); }); + test('injection triggers flop with enable logicvalue 1', () async { final baseClk = SimpleClockGenerator(10).clk; diff --git a/test/conditionals_test.dart b/test/conditionals_test.dart index b1f143a90..2deba13e0 100644 --- a/test/conditionals_test.dart +++ b/test/conditionals_test.dart @@ -16,8 +16,33 @@ import 'package:rohd/src/utilities/simcompare.dart'; import 'package:test/test.dart'; class ShorthandAssignModule extends Module { + final bool useArrays; + + @override + Logic addInput(String name, Logic x, {int width = 1}) { + assert(width.isEven, 'if arrays, split width in 2'); + if (useArrays) { + return super + .addInputArray(name, x, dimensions: [2], elementWidth: width ~/ 2); + } else { + return super.addInput(name, x, width: width); + } + } + + @override + Logic addOutput(String name, {int width = 1}) { + assert(width.isEven, 'if arrays, split width in 2'); + if (useArrays) { + return super + .addOutputArray(name, dimensions: [2], elementWidth: width ~/ 2); + } else { + return super.addOutput(name, width: width); + } + } + ShorthandAssignModule( - Logic preIncr, Logic preDecr, Logic mulAssign, Logic divAssign, Logic b) + Logic preIncr, Logic preDecr, Logic mulAssign, Logic divAssign, Logic b, + {this.useArrays = false}) : super(name: 'shorthandmodule') { preIncr = addInput('preIncr', preIncr, width: 8); preDecr = addInput('preDecr', preDecr, width: 8); @@ -44,8 +69,8 @@ class ShorthandAssignModule extends Module { pdOut.decr(s: s), piOutWithB.incr(s: s, val: b), pdOutWithB.decr(s: s, val: b), - maOut.mulAssign(s: s, val: b), - daOut.divAssign(s: s, val: b), + maOut.mulAssign(b, s: s), + daOut.divAssign(b, s: s), ]); } } @@ -366,7 +391,7 @@ class MultipleConditionalModule extends Module { b = addInput('b', b); final c = addOutput('c'); - final Conditional condOne = c < 1; + final condOne = c < 1; Combinational([ If.block([ElseIf.s(a, condOne), ElseIf.s(b, condOne)]) @@ -634,56 +659,74 @@ void main() { } }); - test('shorthand operations', () async { - final mod = ShorthandAssignModule(Logic(width: 8), Logic(width: 8), - Logic(width: 8), Logic(width: 8), Logic(width: 8)); - await mod.build(); - final vectors = [ - Vector({ - 'preIncr': 5, - 'preDecr': 5, - 'mulAssign': 5, - 'divAssign': 5, - 'b': 5 - }, { - 'piOutWithB': 10, - 'pdOutWithB': 0, - 'piOut': 6, - 'pdOut': 4, - 'maOut': 25, - 'daOut': 1, - }), - Vector({ - 'preIncr': 5, - 'preDecr': 5, - 'mulAssign': 5, - 'divAssign': 5, - 'b': 0 - }, { - 'piOutWithB': 5, - 'pdOutWithB': 5, - 'piOut': 6, - 'pdOut': 4, - 'maOut': 0, - 'daOut': LogicValue.x, - }), - Vector({ - 'preIncr': 0, - 'preDecr': 0, - 'mulAssign': 0, - 'divAssign': 0, - 'b': 5 - }, { - 'piOutWithB': 5, - 'pdOutWithB': 0xfb, - 'piOut': 1, - 'pdOut': 0xff, - 'maOut': 0, - 'daOut': 0, - }) - ]; - await SimCompare.checkFunctionalVector(mod, vectors); - final simResult = SimCompare.iverilogVector(mod, vectors); - expect(simResult, equals(true)); + group('shorthand operations', () { + Future testShorthand( + {required bool useArrays, required bool useSequential}) async { + final mod = ShorthandAssignModule( + Logic(width: 8), + Logic(width: 8), + Logic(width: 8), + Logic(width: 8), + Logic(width: 8), + useArrays: useArrays, + ); + await mod.build(); + + final vectors = [ + Vector({ + 'preIncr': 5, + 'preDecr': 5, + 'mulAssign': 5, + 'divAssign': 5, + 'b': 5 + }, { + 'piOutWithB': 10, + 'pdOutWithB': 0, + 'piOut': 6, + 'pdOut': 4, + 'maOut': 25, + 'daOut': 1, + }), + Vector({ + 'preIncr': 5, + 'preDecr': 5, + 'mulAssign': 5, + 'divAssign': 5, + 'b': 0 + }, { + 'piOutWithB': 5, + 'pdOutWithB': 5, + 'piOut': 6, + 'pdOut': 4, + 'maOut': 0, + 'daOut': LogicValue.x, + }), + Vector({ + 'preIncr': 0, + 'preDecr': 0, + 'mulAssign': 0, + 'divAssign': 0, + 'b': 5 + }, { + 'piOutWithB': 5, + 'pdOutWithB': 0xfb, + 'piOut': 1, + 'pdOut': 0xff, + 'maOut': 0, + 'daOut': 0, + }) + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + } + + test('normal logic', () async { + await testShorthand(useArrays: false, useSequential: false); + }); + + test('arrays', () async { + await testShorthand(useArrays: true, useSequential: false); + }); }); } diff --git a/test/extend_test.dart b/test/extend_test.dart index 35431a173..b5b95e266 100644 --- a/test/extend_test.dart +++ b/test/extend_test.dart @@ -121,10 +121,10 @@ void main() { } test('setting with bigger number throws exception', () async { - expect(() => withSetVectors([], 0, 9), throwsException); + expect(() => withSetVectors([], 0, 9), throwsRangeError); }); test('setting with number in middle overrun throws exception', () async { - expect(() => withSetVectors([], 4, 5), throwsException); + expect(() => withSetVectors([], 4, 5), throwsRangeError); }); test('setting same width returns only new', () async { await withSetVectors([ diff --git a/test/gate_test.dart b/test/gate_test.dart index 10eedc45d..3ea6163e1 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -440,10 +440,10 @@ void main() { expect(testLogicZero[-1].value.toInt(), 0); expect(testLogicInvalid[-1].value, LogicValue.x); - expect(() => testLogic[10], throwsException); - expect(() => testLogicOne[1], throwsException); - expect(() => testLogicZero[1], throwsException); - expect(() => testLogicInvalid[1], throwsException); + expect(() => testLogic[10], throwsA(isA())); + expect(() => testLogicOne[1], throwsA(isA())); + expect(() => testLogicZero[1], throwsA(isA())); + expect(() => testLogicInvalid[1], throwsA(isA())); }); test('index Logic(1bit) by Logic index out of bounds test', () { @@ -465,10 +465,10 @@ void main() { expect(testLogicZero.slice(0, 0), equals(testLogicZero)); expect(testLogicInvalid.slice(0, 0), equals(testLogicInvalid)); - expect(() => testLogic.slice(0, 10), throwsException); - expect(() => testLogicOne.slice(0, 1), throwsException); - expect(() => testLogicZero.slice(0, 1), throwsException); - expect(() => testLogicInvalid.slice(0, 1), throwsException); + expect(() => testLogic.slice(0, 10), throwsA(isA())); + expect(() => testLogicOne.slice(0, 1), throwsA(isA())); + expect(() => testLogicZero.slice(0, 1), throwsA(isA())); + expect(() => testLogicInvalid.slice(0, 1), throwsA(isA())); }); test('Index Logic by does not accept input other than int or Logic', () { diff --git a/test/logic_array_test.dart b/test/logic_array_test.dart new file mode 100644 index 000000000..9e1ac23f9 --- /dev/null +++ b/test/logic_array_test.dart @@ -0,0 +1,920 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_array_test.dart +// Tests for LogicArray +// +// 2023 May 2 +// Author: Max Korbel + +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/exceptions.dart'; +import 'package:rohd/src/utilities/simcompare.dart'; +import 'package:test/test.dart'; + +class SimpleLAPassthrough extends Module { + Logic get laOut => output('laOut'); + SimpleLAPassthrough( + LogicArray laIn, { + List? dimOverride, + int? elemWidthOverride, + int? numUnpackedOverride, + }) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: numUnpackedOverride ?? laIn.numUnpackedDimensions, + ); + + addOutputArray( + 'laOut', + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: + numUnpackedOverride ?? laIn.numUnpackedDimensions, + ) <= + laIn; + } +} + +class RangeAndSliceArrModule extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + RangeAndSliceArrModule(LogicArray laIn) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: [3, 3, 3], + elementWidth: 8, + ); + + addOutputArray( + 'laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions, + ); + + laOut.elements[0] <= + [ + laIn.elements[0].getRange(16), + laIn.elements[0].getRange(0, 16), + ].swizzle(); + + laOut.elements[1] <= + [ + laIn.elements[1].slice(16, 3 * 3 * 8 - 1).reversed, + laIn.elements[1].slice(15, 0), + ].swizzle(); + + laOut.elements[2] <= + [ + laIn.elements[2].slice(-1, 0).getRange(3 * 3 * 8 ~/ 2), + laIn.elements[2].getRange(-3 * 3 * 8).getRange(0, 3 * 3 * 8 ~/ 2), + ].swizzle(); + } +} + +class WithSetArrayModule extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + WithSetArrayModule(LogicArray laIn) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: [2, 2], + elementWidth: 8, + ); + + addOutputArray( + 'laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions, + ); + + laOut <= laIn.withSet(8, laIn.elements[0].elements[1]); + } +} + +class WithSetArrayOffsetModule extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + WithSetArrayOffsetModule(LogicArray laIn) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: [2, 2], + elementWidth: 8, + ); + + addOutputArray( + 'laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions, + ); + + laOut <= laIn.withSet(3 + 16, laIn.elements[1].getRange(3, 3 + 9)); + } +} + +enum LADir { laIn, laOut } + +class LAPassthroughIntf extends Interface { + final List dimensions; + final int elementWidth; + final int numUnpackedDimensions; + + Logic get laIn => port('laIn'); + Logic get laOut => port('laOut'); + + LAPassthroughIntf({ + required this.dimensions, + required this.elementWidth, + required this.numUnpackedDimensions, + }) { + setPorts([ + LogicArray.port('laIn', dimensions, elementWidth, numUnpackedDimensions) + ], [ + LADir.laIn + ]); + + setPorts([ + LogicArray.port('laOut', dimensions, elementWidth, numUnpackedDimensions) + ], [ + LADir.laOut + ]); + } + + LAPassthroughIntf.clone(LAPassthroughIntf other) + : this( + dimensions: other.dimensions, + elementWidth: other.elementWidth, + numUnpackedDimensions: other.numUnpackedDimensions, + ); +} + +class LAPassthroughWithIntf extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + LAPassthroughWithIntf( + LAPassthroughIntf intf, + ) { + intf = LAPassthroughIntf.clone(intf) + ..connectIO(this, intf, + inputTags: {LADir.laIn}, outputTags: {LADir.laOut}); + + intf.laOut <= intf.laIn; + } +} + +class SimpleLAPassthroughLogic extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + SimpleLAPassthroughLogic( + Logic laIn, { + required List dimensions, + required int elementWidth, + required int numUnpackedDimensions, + }) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: dimensions, + elementWidth: elementWidth, + numUnpackedDimensions: numUnpackedDimensions, + ); + + addOutputArray( + 'laOut', + dimensions: dimensions, + elementWidth: elementWidth, + numUnpackedDimensions: numUnpackedDimensions, + ) <= + laIn; + } +} + +class PackAndUnpackPassthrough extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + PackAndUnpackPassthrough(LogicArray laIn) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + final intermediate = Logic(name: 'intermediate', width: laIn.width); + + intermediate <= laIn; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + intermediate; + } +} + +class PackAndUnpackWithArraysPassthrough extends Module + implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + PackAndUnpackWithArraysPassthrough(LogicArray laIn, + {int intermediateUnpacked = 0}) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + final intermediate1 = Logic(name: 'intermediate1', width: laIn.width); + final intermediate3 = Logic(name: 'intermediate2', width: laIn.width); + + // unpack with reversed dimensions + final intermediate2 = LogicArray( + laIn.dimensions.reversed.toList(), laIn.elementWidth, + name: 'intermediate2', numUnpackedDimensions: intermediateUnpacked); + + intermediate1 <= laIn; + intermediate2 <= intermediate1; + intermediate3 <= intermediate2; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + intermediate3; + } +} + +class RearrangeArraysPassthrough extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + RearrangeArraysPassthrough(LogicArray laIn, {int intermediateUnpacked = 0}) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + // rearrange with reversed dimensions + final intermediate = LogicArray( + laIn.dimensions.reversed.toList(), laIn.elementWidth, + name: 'intermediate', numUnpackedDimensions: intermediateUnpacked); + + intermediate <= laIn; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + intermediate; + } +} + +class ArrayNameConflicts extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + ArrayNameConflicts(LogicArray laIn, {int intermediateUnpacked = 0}) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + final intermediate1 = Logic(name: 'intermediate', width: laIn.width); + final intermediate3 = Logic(name: 'intermediate', width: laIn.width); + final intermediate5 = Logic(name: 'intermediate', width: laIn.width); + + // unpack with reversed dimensions + final intermediate2 = LogicArray( + laIn.dimensions.reversed.toList(), laIn.elementWidth, + name: 'intermediate', numUnpackedDimensions: intermediateUnpacked); + + final intermediate4 = LogicArray( + laIn.dimensions.reversed.toList(), laIn.elementWidth, + name: 'intermediate', numUnpackedDimensions: intermediateUnpacked); + + intermediate1 <= laIn; + intermediate2 <= intermediate1; + intermediate3 <= intermediate2; + intermediate4 <= intermediate3; + intermediate5 <= intermediate4; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + intermediate5; + } +} + +class SimpleArraysAndHierarchy extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + SimpleArraysAndHierarchy(LogicArray laIn) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + final intermediate = SimpleLAPassthrough(laIn).laOut; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + intermediate; + } +} + +class FancyArraysAndHierarchy extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + + FancyArraysAndHierarchy(LogicArray laIn, {int intermediateUnpacked = 0}) { + laIn = addInputArray('laIn', laIn, + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions); + + final invertedLaIn = LogicArray(laIn.dimensions, laIn.elementWidth, + numUnpackedDimensions: intermediateUnpacked) + ..gets(~laIn); + + final x1 = SimpleLAPassthrough(laIn).laOut; + final x2 = SimpleLAPassthrough(laIn).laOut; + final x3 = SimpleLAPassthrough(invertedLaIn).laOut; + final x4 = SimpleLAPassthrough(invertedLaIn).laOut; + + final y1 = ~(x1 ^ x3); + final y2 = ~(x2 ^ x4); + + final z1 = laIn ^ y1; + final z2 = y2 ^ laIn; + + final same = z1 & z2; + + addOutputArray('laOut', + dimensions: laIn.dimensions, + elementWidth: laIn.elementWidth, + numUnpackedDimensions: laIn.numUnpackedDimensions) <= + same; + } +} + +class ConstantAssignmentArrayModule extends Module { + Logic get laOut => output('laOut'); + + ConstantAssignmentArrayModule(LogicArray laIn) { + laIn = addInputArray('laIn', laIn, + dimensions: [3, 3, 3, 3], + numUnpackedDimensions: laIn.numUnpackedDimensions, + elementWidth: 8); + + addOutputArray('laOut', + dimensions: laIn.dimensions, + numUnpackedDimensions: laIn.numUnpackedDimensions, + elementWidth: laIn.elementWidth); + + laOut.elements[1] <= + Const([for (var i = 0; i < 3 * 3 * 3; i++) LogicValue.ofInt(i, 8)] + .rswizzle()); + laOut.elements[2].elements[1] <= + (Logic(width: 3 * 3 * 8)..gets(Const(0, width: 3 * 3 * 8))); + laOut.elements[2].elements[2].elements[1] <= + Const(1, width: 3 * 8, fill: true); + laOut.elements[2].elements[2].elements[2].elements[1] <= Const(0, width: 8); + } +} + +class CondAssignArray extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + CondAssignArray( + LogicArray laIn, { + List? dimOverride, + int? elemWidthOverride, + int? numUnpackedOverride, + }) { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: numUnpackedOverride ?? laIn.numUnpackedDimensions, + ); + + final laOut = addOutputArray( + 'laOut', + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: numUnpackedOverride ?? laIn.numUnpackedDimensions, + ); + + Combinational([laOut < laIn]); + } +} + +class CondCompArray extends Module implements SimpleLAPassthrough { + @override + Logic get laOut => output('laOut'); + CondCompArray( + LogicArray laIn, { + List? dimOverride, + int? elemWidthOverride, + int? numUnpackedOverride, + }) : assert(laIn.dimensions.length == 1, 'test assumes 1x1 array'), + assert(laIn.width == 1, 'test assumes 1x1 array') { + laIn = addInputArray( + 'laIn', + laIn, + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: numUnpackedOverride ?? laIn.numUnpackedDimensions, + ); + + final laOut = addOutputArray( + 'laOut', + dimensions: dimOverride ?? laIn.dimensions, + elementWidth: elemWidthOverride ?? laIn.elementWidth, + numUnpackedDimensions: numUnpackedOverride ?? laIn.numUnpackedDimensions, + ); + + Combinational([ + If( + laIn, + then: [laOut < laIn], + orElse: [ + Case(laIn, [ + CaseItem(Const(0), [laOut < laIn]), + CaseItem(Const(1), [laOut < ~laIn]), + ]) + ], + ), + ]); + } +} + +class IndexBitOfArrayModule extends Module { + IndexBitOfArrayModule() { + final o = LogicArray([2, 2, 2], 8); + o <= Const(LogicValue.ofString('10').replicate(2 * 2 * 8)); + addOutput('o0') <= o[0]; + addOutput('o3') <= o[3]; + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('construct LogicArray', () { + final listEq = const ListEquality().equals; + + test('empty array', () { + final arr = LogicArray([0], 20); + expect(arr.width, 0); + expect(arr.elements.isEmpty, true); + expect(arr.elementWidth, 0); + }); + + test('empty multi-dim array', () { + final arr = LogicArray([5, 2, 0, 3], 6); + expect(arr.width, 0); + expect(arr.elementWidth, 0); + }); + + test('single-dim array', () { + final dim = [5]; + const w = 16; + final arr = LogicArray(dim, w); + + expect(listEq(arr.dimensions, dim), true); + + for (final element in arr.elements) { + expect(element.width, w); + } + + expect(arr.width, w * dim[0]); + expect(arr.elementWidth, w); + }); + + test('many-dim array', () { + final dim = [5, 8, 3]; + const w = 32; + final arr = LogicArray(dim, w); + + expect(listEq(arr.dimensions, dim), true); + + // make sure we can access elements + arr.elements[0].elements[2].elements[1]; + + for (final element0 in arr.elements) { + for (final element1 in element0.elements) { + for (final element2 in element1.elements) { + expect(element2.width, w); + } + } + } + expect(arr.width, w * dim.reduce((a, b) => a * b)); + expect( + listEq((arr.elements[0] as LogicArray).dimensions, + dim.getRange(1, dim.length).toList()), + true); + expect(arr.elementWidth, w); + }); + + test('no dim exception', () { + expect( + () => LogicArray([], 3), throwsA(isA())); + }); + + test('overly unpacking exception', () { + expect(() => LogicArray([1, 2, 3], 4, numUnpackedDimensions: 4), + throwsA(isA())); + }); + + test('unpacked dims get passed down', () { + final arr = LogicArray([1, 2, 3], 4, numUnpackedDimensions: 2); + expect(arr.numUnpackedDimensions, 2); + expect((arr.elements[0] as LogicArray).numUnpackedDimensions, 1); + expect( + (arr.elements[0].elements[0] as LogicArray).numUnpackedDimensions, 0); + }); + }); + + group('logicarray passthrough', () { + Future testArrayPassthrough(SimpleLAPassthrough mod, + {bool checkNoSwizzle = true, + bool noSvSim = false, + bool noIverilog = false, + bool dontDeleteTmpFiles = false}) async { + await mod.build(); + + const randWidth = 23; + final rand = Random(1234); + final values = List.generate( + 10, + (index) => LogicValue.ofInt(rand.nextInt(1 << randWidth), randWidth) + .replicate(mod.laOut.width ~/ randWidth + 1) + .getRange(0, mod.laOut.width)); + + final vectors = [ + for (final value in values) Vector({'laIn': value}, {'laOut': value}) + ]; + + if (checkNoSwizzle) { + expect(mod.generateSynth().contains('swizzle'), false, + reason: 'Expected no swizzles but found one.'); + } + + await SimCompare.checkFunctionalVector(mod, vectors); + if (!noIverilog) { + SimCompare.checkIverilogVector(mod, vectors, + buildOnly: noSvSim, dontDeleteTmpFiles: dontDeleteTmpFiles); + } + } + + group('simple', () { + test('single dimension', () async { + final mod = SimpleLAPassthrough(LogicArray([3], 8)); + await testArrayPassthrough(mod); + }); + + test('single element', () async { + final mod = SimpleLAPassthrough(LogicArray([1], 8)); + await testArrayPassthrough(mod); + }); + + test('array of bits', () async { + final mod = SimpleLAPassthrough(LogicArray([8], 1)); + await testArrayPassthrough(mod); + }); + + test('2 dimensions', () async { + final mod = SimpleLAPassthrough(LogicArray([3, 2], 8)); + await testArrayPassthrough(mod); + }); + + test('3 dimensions', () async { + final mod = SimpleLAPassthrough(LogicArray([3, 2, 3], 8)); + await testArrayPassthrough(mod); + }); + + test('4 dimensions', () async { + final mod = SimpleLAPassthrough(LogicArray([5, 4, 3, 2], 8)); + await testArrayPassthrough(mod); + }); + + test('1d, unpacked', () async { + final mod = + SimpleLAPassthrough(LogicArray([3], 8, numUnpackedDimensions: 1)); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, noSvSim: true); + + final sv = mod.generateSynth(); + expect(sv.contains(RegExp(r'\[7:0\]\s*laIn\s*\[2:0\]')), true); + expect(sv.contains(RegExp(r'\[7:0\]\s*laOut\s*\[2:0\]')), true); + }); + + test('single element, unpacked', () async { + final mod = + SimpleLAPassthrough(LogicArray([1], 8, numUnpackedDimensions: 1)); + await testArrayPassthrough(mod, noSvSim: true, noIverilog: true); + }); + + test('4d, half packed', () async { + final mod = SimpleLAPassthrough( + LogicArray([5, 4, 3, 2], 8, numUnpackedDimensions: 2)); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, noSvSim: true); + + final sv = mod.generateSynth(); + expect( + sv.contains(RegExp( + r'\[2:0\]\s*\[1:0\]\s*\[7:0\]\s*laIn\s*\[4:0\]\s*\[3:0\]')), + true); + expect( + sv.contains(RegExp( + r'\[2:0\]\s*\[1:0\]\s*\[7:0\]\s*laOut\s*\[4:0\]\s*\[3:0\]')), + true); + }); + + test('sub-array', () async { + final superArray = LogicArray([4, 3, 2], 8); + final subArray = superArray.elements[0] as LogicArray; + final mod = SimpleLAPassthrough(subArray); + await testArrayPassthrough(mod); + }); + + test('3 dimensions with interface', () async { + final mod = LAPassthroughWithIntf(LAPassthroughIntf( + dimensions: [3, 2, 3], + elementWidth: 8, + numUnpackedDimensions: 0, + )); + await testArrayPassthrough(mod); + }); + }); + + group('pack and unpack', () { + test('1d', () async { + final mod = PackAndUnpackPassthrough(LogicArray([3], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('3d', () async { + final mod = PackAndUnpackPassthrough(LogicArray([5, 3, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('3d unpacked', () async { + final mod = PackAndUnpackPassthrough( + LogicArray([5, 3, 2], 8, numUnpackedDimensions: 2)); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, checkNoSwizzle: false, noSvSim: true); + }); + }); + + group('pack and unpack with arrays', () { + test('1d', () async { + final mod = PackAndUnpackWithArraysPassthrough(LogicArray([3], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('2d', () async { + final mod = PackAndUnpackWithArraysPassthrough(LogicArray([3, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('3d', () async { + final mod = + PackAndUnpackWithArraysPassthrough(LogicArray([4, 3, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('3d unpacked', () async { + final mod = PackAndUnpackWithArraysPassthrough( + LogicArray([4, 3, 2], 8, numUnpackedDimensions: 2), + intermediateUnpacked: 1); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, checkNoSwizzle: false, noSvSim: true); + }); + }); + + group('change array dimensions around and back', () { + test('3d', () async { + final mod = RearrangeArraysPassthrough(LogicArray([4, 3, 2], 8)); + await testArrayPassthrough(mod); + }); + + test('3d unpacked', () async { + final mod = RearrangeArraysPassthrough( + LogicArray([4, 3, 2], 8, numUnpackedDimensions: 2), + intermediateUnpacked: 1); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, noSvSim: true); + + final sv = mod.generateSynth(); + expect(sv.contains('logic [2:0][3:0][7:0] intermediate [1:0]'), true); + }); + }); + + group('different port and input widths', () { + test('array param mismatch', () async { + final i = LogicArray([3, 2], 8, numUnpackedDimensions: 1); + final o = LogicArray([3, 2], 8, numUnpackedDimensions: 1); + final mod = SimpleLAPassthrough( + i, + dimOverride: [1, 3], + elemWidthOverride: 16, + numUnpackedOverride: 0, + ); + o <= mod.laOut; + await testArrayPassthrough(mod); + }); + + test('logic into array', () async { + final i = Logic(width: 3 * 2 * 8); + final o = Logic(width: 3 * 2 * 8); + final mod = SimpleLAPassthroughLogic( + i, + dimensions: [1, 3], + elementWidth: 16, + numUnpackedDimensions: 0, + ); + o <= mod.laOut; + await testArrayPassthrough(mod); + }); + }); + + group('name collisions', () { + test('3d', () async { + final mod = ArrayNameConflicts(LogicArray([4, 3, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('3d unpacked', () async { + final mod = ArrayNameConflicts( + LogicArray([4, 3, 2], 8, numUnpackedDimensions: 2), + intermediateUnpacked: 1); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, checkNoSwizzle: false, noSvSim: true); + }); + }); + + group('simple hierarchy', () { + test('3d', () async { + final mod = SimpleArraysAndHierarchy(LogicArray([2], 8)); + await testArrayPassthrough(mod); + + expect(mod.generateSynth(), + contains('SimpleLAPassthrough unnamed_module')); + }); + + test('3d unpacked', () async { + final mod = SimpleArraysAndHierarchy( + LogicArray([4, 3, 2], 8, numUnpackedDimensions: 2)); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, noSvSim: true); + + expect(mod.generateSynth(), contains('SimpleLAPassthrough')); + }); + }); + + group('fancy hierarchy', () { + test('3d', () async { + final mod = FancyArraysAndHierarchy(LogicArray([4, 3, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + + // make sure the 4th one is there (since we expect 4) + expect(mod.generateSynth(), + contains('SimpleLAPassthrough unnamed_module_2')); + }); + + test('3d unpacked', () async { + final mod = FancyArraysAndHierarchy( + LogicArray([4, 3, 2], 8, numUnpackedDimensions: 2), + intermediateUnpacked: 1); + + // unpacked array assignment not fully supported in iverilog + await testArrayPassthrough(mod, checkNoSwizzle: false, noSvSim: true); + }); + }); + + group('conditionals', () { + test('3 dimensions conditional assignment', () async { + final mod = CondAssignArray(LogicArray([3, 2, 3], 8)); + await testArrayPassthrough(mod); + }); + + test('1x1 expressions in if and case', () async { + final mod = CondCompArray(LogicArray([1], 1)); + await testArrayPassthrough(mod); + }); + }); + + test('slice and dice', () async { + final mod = RangeAndSliceArrModule(LogicArray([3, 3, 3], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + }); + + test('withset', () async { + final mod = WithSetArrayModule(LogicArray([2, 2], 8)); + await testArrayPassthrough(mod); + }); + + test('withset offset', () async { + final mod = WithSetArrayOffsetModule(LogicArray([2, 2], 8)); + await testArrayPassthrough(mod, checkNoSwizzle: false); + + // make sure we're reassigning both times it overlaps! + expect( + RegExp('assign laIn.*=.*swizzled') + .allMatches(mod.generateSynth()) + .length, + 2); + }); + }); + + group('array constant assignments', () { + Future testArrayConstantAssignments( + {required int numUnpackedDimensions, bool doSvSim = true}) async { + final mod = ConstantAssignmentArrayModule(LogicArray([3, 3, 3, 3], 8, + numUnpackedDimensions: numUnpackedDimensions)); + await mod.build(); + + final a = []; + var iIdx = 0; + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + for (var k = 0; k < 3; k++) { + for (var l = 0; l < 3; l++) { + if (i == 1) { + a.add(LogicValue.ofInt(iIdx, 8)); + iIdx++; + } else if (i == 2 && j == 1) { + a.add(LogicValue.filled(8, LogicValue.zero)); + } else if (i == 2 && j == 2 && k == 1) { + a.add(LogicValue.filled(8, LogicValue.one)); + } else if (i == 2 && j == 2 && k == 2 && l == 1) { + a.add(LogicValue.filled(8, LogicValue.zero)); + } else { + a.add(LogicValue.filled(8, LogicValue.z)); + } + } + } + } + } + final vectors = [ + Vector({'laIn': 0}, {'laOut': a.rswizzle()}) + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors, buildOnly: !doSvSim); + } + + test('with packed only', () async { + await testArrayConstantAssignments(numUnpackedDimensions: 0); + }); + + test('with unpacked also', () async { + // unpacked array assignment not fully supported in iverilog + await testArrayConstantAssignments( + numUnpackedDimensions: 2, doSvSim: false); + }); + + test('indexing single bit of array', () async { + final mod = IndexBitOfArrayModule(); + await mod.build(); + + final vectors = [ + Vector({}, {'o0': 0, 'o3': 1}) + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + }); +} diff --git a/test/logic_name_test.dart b/test/logic_name_test.dart index 670b7fc35..de9b17169 100644 --- a/test/logic_name_test.dart +++ b/test/logic_name_test.dart @@ -19,6 +19,19 @@ class LogicTestModule extends Module { } } +class LogicWithInternalSignalModule extends Module { + LogicWithInternalSignalModule(Logic i) { + i = addInput('i', i); + + final o = addOutput('o'); + + // this `put` should *not* impact the name of x + final x = Logic(name: 'shouldExist')..put(1); + + o <= i & x; + } +} + void main() { test( 'GIVEN logic name is valid ' @@ -59,4 +72,13 @@ void main() { }, throwsA((dynamic e) => e is InvalidPortNameException)); }); }); + + test( + 'non-synthesizable signal deposition should not impact generated verilog', + () async { + final mod = LogicWithInternalSignalModule(Logic()); + await mod.build(); + + expect(mod.generateSynth(), contains('shouldExist')); + }); } diff --git a/test/logic_structure_test.dart b/test/logic_structure_test.dart new file mode 100644 index 000000000..bddd4265f --- /dev/null +++ b/test/logic_structure_test.dart @@ -0,0 +1,172 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// logic_structure_test.dart +// Tests for LogicStructure +// +// 2023 May 5 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/exceptions.dart'; +import 'package:rohd/src/utilities/simcompare.dart'; +import 'package:test/test.dart'; + +class MyStruct extends LogicStructure { + final Logic ready; + final Logic valid; + + factory MyStruct() => MyStruct._( + Logic(name: 'ready'), + Logic(name: 'valid'), + ); + + MyStruct._(this.ready, this.valid) : super([ready, valid], name: 'myStruct'); + + @override + LogicStructure clone({String? name}) => MyStruct(); +} + +class MyFancyStruct extends LogicStructure { + final LogicArray arr; + final Logic bus; + final LogicStructure subStruct; + + factory MyFancyStruct({int busWidth = 12}) => MyFancyStruct._( + LogicArray([3, 3], 8, name: 'arr'), + Logic(name: 'bus', width: busWidth), + MyStruct(), + ); + + MyFancyStruct._(this.arr, this.bus, this.subStruct) + : super([arr, bus, subStruct], name: 'myFancyStruct'); +} + +class StructPortModule extends Module { + StructPortModule(MyStruct struct) { + final ready = addInput('ready', struct.ready); + final valid = addOutput('valid'); + struct.valid <= valid; + + valid <= ready; + } +} + +class ModStructPassthrough extends Module { + MyStruct get sOut => MyStruct()..gets(output('sOut')); + + ModStructPassthrough(MyStruct struct) { + struct = MyStruct()..gets(addInput('sIn', struct, width: struct.width)); + addOutput('sOut', width: struct.width) <= struct; + } +} + +class FancyStructInverter extends Module { + MyFancyStruct get sOut => MyFancyStruct()..gets(output('sOut')); + + FancyStructInverter(MyFancyStruct struct) { + struct = MyFancyStruct() + ..gets(addInput('sIn', struct, width: struct.width)); + addOutput('sOut', width: struct.width) <= ~struct; + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('LogicStructure construction', () { + test('simple construction', () { + final s = LogicStructure([ + Logic(), + Logic(), + ], name: 'structure'); + + expect(s.name, 'structure'); + }); + + test('sub logic in two structures throws exception', () { + final s = LogicStructure([ + Logic(), + ], name: 'structure'); + + expect(() => LogicStructure([s.elements.first]), + throwsA(isA())); + }); + + test('sub structure in two structures throws exception', () { + final subS = LogicStructure([Logic()]); + + LogicStructure([ + subS, + ], name: 'structure'); + + expect(() => LogicStructure([subS]), + throwsA(isA())); + }); + + test('structure clone', () { + final orig = MyFancyStruct(); + final copy = orig.clone(); + + expect(copy.name, orig.name); + expect(copy.width, orig.width); + expect(copy.elements[0], isA()); + expect(copy.elements[2], isA()); + }); + }); + + group('LogicStructures with modules', () { + test('simple struct bi-directional', () async { + final struct = MyStruct(); + final mod = StructPortModule(struct); + await mod.build(); + + final vectors = [ + Vector({'ready': 0}, {'valid': 0}), + Vector({'ready': 1}, {'valid': 1}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + + test('simple passthrough struct', () async { + final struct = MyStruct(); + final mod = ModStructPassthrough(struct); + await mod.build(); + + final vectors = [ + Vector({'sIn': 0}, {'sOut': 0}), + Vector({'sIn': LogicValue.ofString('10')}, + {'sOut': LogicValue.ofString('10')}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + + test('fancy struct inverter', () async { + final struct = MyFancyStruct(); + final mod = FancyStructInverter(struct); + await mod.build(); + + struct.arr.elements[2].elements[1].put(0x55); + expect(mod.sOut.arr.elements[2].elements[1].value.toInt(), 0xaa); + + struct.bus.put(0x0f0); + expect(mod.sOut.bus.value.toInt(), 0xf0f); + + final vectors = [ + Vector({'sIn': 0}, + {'sOut': LogicValue.filled(struct.width, LogicValue.one)}), + Vector({'sIn': LogicValue.ofString('10' * (struct.width ~/ 2))}, + {'sOut': LogicValue.ofString('01' * (struct.width ~/ 2))}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + }); +} diff --git a/test/logic_value_test.dart b/test/logic_value_test.dart index 162bd4a4d..c88fe949c 100644 --- a/test/logic_value_test.dart +++ b/test/logic_value_test.dart @@ -263,18 +263,64 @@ void main() { equals(LogicValue.ofString('01xx10xxxxxxxxxx'))); expect( // test from Iterable - LogicValue.of([LogicValue.one, LogicValue.zero]) ^ - LogicValue.of([LogicValue.one, LogicValue.zero]), - equals(LogicValue.of([LogicValue.zero, LogicValue.zero]))); + LogicValue.ofIterable([LogicValue.one, LogicValue.zero]) ^ + LogicValue.ofIterable([LogicValue.one, LogicValue.zero]), + equals(LogicValue.ofIterable([LogicValue.zero, LogicValue.zero]))); }); }); test('LogicValue.of example', () { final it = [LogicValue.zero, LogicValue.x, LogicValue.ofString('01xz')]; - final lv = LogicValue.of(it); + final lv = LogicValue.ofIterable(it); expect(lv.toString(), equals("6'b01xzx0")); }); + group('LogicValue toString', () { + test('1 bit', () { + expect(LogicValue.one.toString(), "1'h1"); + }); + + test('1 bit invalid', () { + expect(LogicValue.x.toString(), "1'bx"); + }); + + test('<64-bit positive', () { + expect(LogicValue.ofInt(0x1234, 60).toString(), "60'h1234"); + }); + + test('<64-bit negative', () { + expect(LogicValue.ofInt(-1, 60).toString(), "60'hfffffffffffffff"); + }); + + test('64-bit positive', () { + expect(LogicValue.ofInt(0x1234, 64).toString(), "64'h1234"); + }); + + test('64-bit negative', () { + expect(LogicValue.ofInt(0xfaaaaaaa00000005, 64).toString(), + "64'hfaaaaaaa00000005"); + }); + + test('>64-bit positive', () { + expect( + LogicValue.ofBigInt(BigInt.parse('0x5faaaaaaa00000005'), 68) + .toString(), + "68'h5faaaaaaa00000005"); + }); + + test('>64-bit negative', () { + expect( + LogicValue.ofBigInt(BigInt.parse('0xffaaaaaaa00000005'), 68) + .toString(), + "68'hffaaaaaaa00000005"); + }); + + test('include width', () { + expect( + LogicValue.ofInt(0x55, 8).toString(includeWidth: false), '01010101'); + }); + }); + group('unary operations (including "to")', () { test('toMethods', () { expect( @@ -368,7 +414,7 @@ void main() { // getRange - negative end index and start > end - error! start must // be less than end () => LogicValue.ofString('0101').getRange(-1, -2), - throwsA(isA())); + throwsA(isA())); expect( // getRange - same index results zero width value LogicValue.ofString('0101').getRange(-1, -1), @@ -376,11 +422,11 @@ void main() { expect( // getRange - bad inputs start > end () => LogicValue.ofString('0101').getRange(2, 1), - throwsA(isA())); + throwsA(isA())); expect( // getRange - bad inputs end > length-1 () => LogicValue.ofString('0101').getRange(0, 7), - throwsA(isA())); + throwsA(isA())); expect(LogicValue.ofString('xz01').slice(2, 1), equals(LogicValue.ofString('z0'))); expect(LogicValue.ofString('xz01').slice(-2, -3), diff --git a/test/sequential_test.dart b/test/sequential_test.dart index 2cdb93191..2e97920df 100644 --- a/test/sequential_test.dart +++ b/test/sequential_test.dart @@ -28,7 +28,7 @@ class DelaySignal extends Module { final out = addOutput('out', width: bitWidth); - final zList = [z[0] < inputVal]; + final zList = [z[0] < inputVal]; for (var i = 0; i < z.length; i++) { if (i == z.length - 1) { zList.add(out < z[i]); @@ -48,6 +48,54 @@ class DelaySignal extends Module { } } +class ShorthandSeqModule extends Module { + final bool useArrays; + + @override + Logic addOutput(String name, {int width = 1}) { + assert(width.isEven, 'if arrays, split width in 2'); + if (useArrays) { + return super + .addOutputArray(name, dimensions: [2], elementWidth: width ~/ 2); + } else { + return super.addOutput(name, width: width); + } + } + + ShorthandSeqModule(Logic reset, + {this.useArrays = false, + int initialVal = 16, + bool doubleResetError = false}) + : super(name: 'shorthandmodule') { + reset = addInput('reset', reset); + + final piOut = addOutput('piOut', width: 8); + final pdOut = addOutput('pdOut', width: 8); + final maOut = addOutput('maOut', width: 8); + final daOut = addOutput('daOut', width: 8); + + final clk = SimpleClockGenerator(10).clk; + + Sequential( + clk, + [ + piOut.incr(), + pdOut.decr(), + maOut.mulAssign(2), + daOut.divAssign(2), + ], + reset: reset, + resetValues: { + piOut: initialVal, + pdOut: initialVal, + maOut: initialVal, + daOut: initialVal, + if (useArrays && doubleResetError) daOut.elements[0]: initialVal, + }, + ); + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -78,4 +126,39 @@ void main() { final simResult = SimCompare.iverilogVector(dut, vectors); expect(simResult, equals(true)); }); + + group('shorthand with sequential', () { + Future testShorthand( + {required bool useArrays, bool doubleResetError = false}) async { + final mod = ShorthandSeqModule(Logic(), + useArrays: useArrays, doubleResetError: doubleResetError); + await mod.build(); + + final vectors = [ + Vector({'reset': 1}, {}), + Vector( + {'reset': 1}, {'piOut': 16, 'pdOut': 16, 'maOut': 16, 'daOut': 16}), + Vector( + {'reset': 0}, {'piOut': 16, 'pdOut': 16, 'maOut': 16, 'daOut': 16}), + Vector( + {'reset': 0}, {'piOut': 17, 'pdOut': 15, 'maOut': 32, 'daOut': 8}), + ]; + + // await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + } + + test('normal logic', () async { + await testShorthand(useArrays: false); + }); + + test('arrays', () async { + await testShorthand(useArrays: true); + }); + + test('arrays with double reset error', () async { + expect(testShorthand(useArrays: true, doubleResetError: true), + throwsException); + }); + }); } diff --git a/test/swizzle_test.dart b/test/swizzle_test.dart index 556dc36fd..13c167620 100644 --- a/test/swizzle_test.dart +++ b/test/swizzle_test.dart @@ -20,6 +20,15 @@ class SwizzlyModule extends Module { } } +class SwizzlyEmpty extends Module { + SwizzlyEmpty(Logic a) { + a = addInput('a', a, width: a.width); + addOutput('b', width: a.width) <= + [a, [].swizzle()].swizzle() + + [].swizzle().zeroExtend(a.width); + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -83,7 +92,7 @@ void main() { (index) => bits[index % bits.length] * (index % 17) + bits[(index + 1) % bits.length] * (index % 2)); - expect(LogicValue.of(swizzleStrings.map(LogicValue.ofString)), + expect(LogicValue.ofIterable(swizzleStrings.map(LogicValue.ofString)), equals(LogicValue.ofString(swizzleStrings.reversed.join()))); }); @@ -94,7 +103,7 @@ void main() { (index) => bits[index % bits.length] * (index % 71) + bits[(index + 1) % bits.length] * (index % 2)); - expect(LogicValue.of(swizzleStrings.map(LogicValue.ofString)), + expect(LogicValue.ofIterable(swizzleStrings.map(LogicValue.ofString)), equals(LogicValue.ofString(swizzleStrings.reversed.join()))); }); }); @@ -165,4 +174,20 @@ void main() { expect(simResult, equals(true)); }); }); + + test('zero-width swizzle', () { + [].swizzle(); + }); + + test('zero-width swizzle module', () async { + final mod = SwizzlyEmpty(Logic()); + await mod.build(); + final vectors = [ + Vector({'a': 0}, {'b': bin('0')}), + Vector({'a': 1}, {'b': bin('1')}), + ]; + await SimCompare.checkFunctionalVector(mod, vectors); + final simResult = SimCompare.iverilogVector(mod, vectors); + expect(simResult, equals(true)); + }); }