diff --git a/lib/src/exceptions/conditionals/signal_redriven_exception.dart b/lib/src/exceptions/conditionals/signal_redriven_exception.dart index 6d27598c0..465fa7598 100644 --- a/lib/src/exceptions/conditionals/signal_redriven_exception.dart +++ b/lib/src/exceptions/conditionals/signal_redriven_exception.dart @@ -9,20 +9,16 @@ /// Author: Yao Jing Quek import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/rohd_exception.dart'; /// An exception that thrown when a [Logic] signal is /// operated multiple times. -class SignalRedrivenException implements Exception { - late final String _message; - +class SignalRedrivenException extends RohdException { /// Displays [signals] that are driven multiple times /// with default error [message]. /// /// Creates a [SignalRedrivenException] with an optional error [message]. SignalRedrivenException(String signals, [String message = 'Sequential drove the same signal(s) multiple times: ']) - : _message = message + signals; - - @override - String toString() => _message; + : super(message + signals); } diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart index 2be96bd65..a13fbf74d 100644 --- a/lib/src/exceptions/exceptions.dart +++ b/lib/src/exceptions/exceptions.dart @@ -2,5 +2,6 @@ /// SPDX-License-Identifier: BSD-3-Clause export './conditionals/conditional_exceptions.dart'; +export './module/module_exceptions.dart'; export './name/name_exceptions.dart'; export './sim_compare/sim_compare_exceptions.dart'; diff --git a/lib/src/exceptions/module/module_exceptions.dart b/lib/src/exceptions/module/module_exceptions.dart new file mode 100644 index 000000000..e6b8b314d --- /dev/null +++ b/lib/src/exceptions/module/module_exceptions.dart @@ -0,0 +1,4 @@ +/// Copyright (C) 2022 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause + +export 'module_not_built_exception.dart'; diff --git a/lib/src/exceptions/module/module_not_built_exception.dart b/lib/src/exceptions/module/module_not_built_exception.dart new file mode 100644 index 000000000..230f97bcf --- /dev/null +++ b/lib/src/exceptions/module/module_not_built_exception.dart @@ -0,0 +1,21 @@ +/// Copyright (C) 2022 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause +/// +/// module_not_build_exception.dart +/// Definition for exception when module is not built +/// +/// 2022 December 30 +/// Author: Max Korbel +/// + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/exceptions/rohd_exception.dart'; + +/// An [Exception] thrown when a [Module] was used in a way that required it +/// to be built first, but it was not yet built. +class ModuleNotBuiltException extends RohdException { + /// Constructs a new [Exception] for when a [Module] should have been built + /// before some action was taken. + ModuleNotBuiltException( + [super.message = 'Module has not yet built! Must call build() first.']); +} diff --git a/lib/src/exceptions/name/invalid_reserved_name_exception.dart b/lib/src/exceptions/name/invalid_reserved_name_exception.dart index 294343d57..c9b019929 100644 --- a/lib/src/exceptions/name/invalid_reserved_name_exception.dart +++ b/lib/src/exceptions/name/invalid_reserved_name_exception.dart @@ -8,19 +8,15 @@ /// Author: Yao Jing Quek /// -/// An exception that thrown when a reserved name is invalid. -class InvalidReservedNameException implements Exception { - late final String _message; +import 'package:rohd/src/exceptions/rohd_exception.dart'; +/// An exception that thrown when a reserved name is invalid. +class InvalidReservedNameException extends RohdException { /// Display error [message] on invalid reserved name. /// /// Creates a [InvalidReservedNameException] with an optional error [message]. InvalidReservedNameException( - [String message = 'Reserved Name need to follow proper naming ' + [super.message = 'Reserved Name need to follow proper naming ' 'convention if reserved' - ' name set to true']) - : _message = message; - - @override - String toString() => _message; + ' name set to true']); } diff --git a/lib/src/exceptions/name/null_reserved_name_exception.dart b/lib/src/exceptions/name/null_reserved_name_exception.dart index cfe61678e..11ef97983 100644 --- a/lib/src/exceptions/name/null_reserved_name_exception.dart +++ b/lib/src/exceptions/name/null_reserved_name_exception.dart @@ -8,18 +8,14 @@ /// Author: Yao Jing Quek /// -/// An exception that thrown when a reserved name is `null`. -class NullReservedNameException implements Exception { - late final String _message; +import 'package:rohd/src/exceptions/rohd_exception.dart'; +/// An exception that thrown when a reserved name is `null`. +class NullReservedNameException extends RohdException { /// Display error [message] on `null` reserved name. /// /// Creates a [NullReservedNameException] with an optional error [message]. NullReservedNameException( - [String message = 'Reserved Name cannot be null ' - 'if reserved name set to true']) - : _message = message; - - @override - String toString() => _message; + [super.message = 'Reserved Name cannot be null ' + 'if reserved name set to true']); } diff --git a/lib/src/exceptions/rohd_exception.dart b/lib/src/exceptions/rohd_exception.dart new file mode 100644 index 000000000..b9f28e363 --- /dev/null +++ b/lib/src/exceptions/rohd_exception.dart @@ -0,0 +1,21 @@ +/// Copyright (C) 2022 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause +/// +/// rohd_exception.dart +/// Base class for all ROHD exceptions +/// +/// 2022 December 30 +/// Author: Max Korbel +/// + +/// A base type of exception that ROHD-specific exceptions inherit from. +abstract class RohdException implements Exception { + /// A description of what this exception means. + final String message; + + /// Creates a new exception with description [message]. + RohdException(this.message); + + @override + String toString() => message; +} diff --git a/lib/src/exceptions/sim_compare/non_supported_type_exception.dart b/lib/src/exceptions/sim_compare/non_supported_type_exception.dart index f4e6887f1..68dedf5c3 100644 --- a/lib/src/exceptions/sim_compare/non_supported_type_exception.dart +++ b/lib/src/exceptions/sim_compare/non_supported_type_exception.dart @@ -9,21 +9,17 @@ /// Author: Yao Jing Quek /// +import 'package:rohd/src/exceptions/rohd_exception.dart'; import 'package:rohd/src/utilities/simcompare.dart'; /// An exception that thrown when `runtimeType` of expected vector /// output from [SimCompare] is invalid or unsupported. -class NonSupportedTypeException implements Exception { - late final String _message; - +class NonSupportedTypeException extends RohdException { /// Displays [vector] which have invalid or unsupported `runtimeType` /// with default error [message]. /// /// Creates a [NonSupportedTypeException] with an optional error [message]. NonSupportedTypeException(String vector, [String message = 'The runtimetype of expected vector is unsupported: ']) - : _message = message + vector.runtimeType.toString(); - - @override - String toString() => _message; + : super(message + vector.runtimeType.toString()); } diff --git a/lib/src/module.dart b/lib/src/module.dart index 7bddfc91c..c04be4c16 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -14,6 +14,8 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; +import 'package:rohd/src/collections/traverseable_collection.dart'; +import 'package:rohd/src/exceptions/module/module_exceptions.dart'; import 'package:rohd/src/exceptions/name/name_exceptions.dart'; import 'package:rohd/src/utilities/config.dart'; import 'package:rohd/src/utilities/sanitizer.dart'; @@ -121,7 +123,8 @@ abstract class Module { /// unique name within its scope. String get uniqueInstanceName => hasBuilt || reserveName ? _uniqueInstanceName - : throw Exception('Module must be built to access uniquified name.' + : throw ModuleNotBuiltException( + 'Module must be built to access uniquified name.' ' Call build() before accessing this.'); String _uniqueInstanceName; @@ -188,7 +191,8 @@ abstract class Module { /// Only returns valid information after [build]. Iterable hierarchy() { if (!hasBuilt) { - throw Exception('Module must be built before accessing hierarchy.' + throw ModuleNotBuiltException( + 'Module must be built before accessing hierarchy.' ' Call build() before executing this.'); } Module? pModule = this; @@ -224,6 +228,9 @@ abstract class Module { /// starting up interactions with independent processes (e.g. cosimulation). /// /// This function should only be called one time per [Module]. + /// + /// The hierarchy is built "bottom-up", so leaf-level [Module]s are built + /// before the [Module]s which contain them. @mustCallSuper Future build() async { if (hasBuilt) { @@ -252,6 +259,120 @@ abstract class Module { _hasBuilt = true; } + /// A mapping of purely combinational paths from each input port to all + /// downstream output ports. + /// + /// Each key of the returned [Map] is an [input] of this [Module]. Each + /// value of the [Map] is a [List] of [output]s of this [Module] which may + /// change combinationally (no sequential logic in-between) as a result + /// of the corresponding key [input] changing. + /// + /// This is the stored result from calling [getCombinationalPaths] at [build] + /// time. The module should be built before calling this (or call it itself) + /// or else it may cache an incomplete picture. + Map> get combinationalPaths => + _combinationalPaths ??= _getCombinationalPaths(); + + /// Internal cache storage of [combinationalPaths]. + Map>? _combinationalPaths; + + /// Returns a mapping of purely combinational paths from each input port + /// to all downstream output ports. + /// + /// Each key of the returned [Map] is an [input] of this [Module]. Each + /// value of the [Map] is a [List] of [output]s of this [Module] which may + /// change combinationally (no sequential logic in-between) as a result + /// of the corresponding key [input] changing. + /// + /// The default behavior of this function is to search through from all + /// inputs to all potential outputs. If a [Module] implements custom behavior + /// internally (e.g. a custom gate or a cosimulated module), then it makes + /// sense to override this function to give an accurate picture. If the + /// default behavior doesn't work (because no visible connectivity exists + /// inside the [Module]), then the return value will end up with all empty + /// [List]s in the values of the [Map]. + /// + /// The result of this function is intended to be stored at [build] time, and + /// it should be called at [build] time. The result is primarily used for + /// calculating valid and complete sensitivity lists for [Combinational] + /// execution. + @protected + Map> getCombinationalPaths() { + final comboPaths = >{}; + for (final inputPort in inputs.values) { + final comboOutputs = []; + final searchList = TraverseableCollection()..add(inputPort); + for (var i = 0; i < searchList.length; i++) { + for (final dstConnection in inputPort.dstConnections) { + if (dstConnection.isInput && dstConnection.parentModule != this) { + // this is an input port of a sub-module, jump over it + searchList.addAll( + dstConnection.parentModule!.combinationalPaths[dstConnection]!); + } else if (isOutput(dstConnection)) { + // this is an output port of this module, store it! + comboOutputs.add(dstConnection); + } else { + // this is a wire within this module, keep tracing + searchList.addAll(dstConnection.dstConnections); + } + } + } + comboPaths[inputPort] = comboOutputs; + } + return comboPaths; + } + + /// Returns the value of [getCombinationalPaths] wrapped safely with + /// unmodifiable views for caching. + Map> _getCombinationalPaths() { + final initialComboPaths = getCombinationalPaths(); + return UnmodifiableMapView( + Map.fromEntries(inputs.values.map((inputPort) => MapEntry( + inputPort, + initialComboPaths.containsKey(inputPort) + ? UnmodifiableListView(initialComboPaths[inputPort]!) + : const [], + )))); + } + + /// The opposite of [combinationalPaths], where every key of the [Map] is an + /// output and the values are lists of inputs which could combinationally + /// affect that output. + /// + /// This module must be built before calling this. + Map> get reverseCombinationalPaths => + _reverseCombinationalPaths ??= _getReverseCombinationalPaths(); + + /// Internal storage of [reverseCombinationalPaths], cached. + Map>? _reverseCombinationalPaths; + + /// Calculates the opposite of [combinationalPaths]. + Map> _getReverseCombinationalPaths() { + if (!_hasBuilt) { + throw ModuleNotBuiltException(); + } + + assert(_reverseCombinationalPaths == null, + 'Should not recreate if already cached result.'); + + final reverseComboPaths = >{}; + for (final inputPort in combinationalPaths.keys) { + for (final outputPort in combinationalPaths[inputPort]!) { + reverseComboPaths + .putIfAbsent(outputPort, () => []) + .add(inputPort); + } + } + + return UnmodifiableMapView( + Map.fromEntries(outputs.values.map((outputPort) => MapEntry( + outputPort, + reverseComboPaths.containsKey(outputPort) + ? UnmodifiableListView(reverseComboPaths[outputPort]!) + : const [], + )))); + } + /// Adds a [Module] to this as a subModule. Future _addAndBuildModule(Module module) async { if (module.parent != null) { @@ -496,7 +617,7 @@ abstract class Module { /// may have other output formats, languages, files, etc. String generateSynth() { if (!_hasBuilt) { - throw Exception('Module has not yet built! Must call build() first.'); + throw ModuleNotBuiltException(); } final synthHeader = ''' diff --git a/lib/src/modules/bus.dart b/lib/src/modules/bus.dart index cfab75a8e..830e4e708 100644 --- a/lib/src/modules/bus.dart +++ b/lib/src/modules/bus.dart @@ -14,7 +14,7 @@ import 'package:rohd/rohd.dart'; /// /// The returned signal is inclusive of both the [startIndex] and [endIndex]. /// The output [subset] will have width equal to `|endIndex - startIndex| + 1`. -class BusSubset extends Module with InlineSystemVerilog { +class BusSubset extends Module with InlineSystemVerilog, FullyCombinational { /// Name for the input port of this module. late final String _original; @@ -127,7 +127,7 @@ class BusSubset extends Module with InlineSystemVerilog { /// /// You can use convenience functions [swizzle()] or [rswizzle()] to more easily /// use this [Module]. -class Swizzle extends Module with InlineSystemVerilog { +class Swizzle extends Module with InlineSystemVerilog, FullyCombinational { final String _out = Module.unpreferredName('swizzled'); /// The output port containing concatenated signals. diff --git a/lib/src/modules/conditional.dart b/lib/src/modules/conditional.dart index 95c932a43..36dc7d24c 100644 --- a/lib/src/modules/conditional.dart +++ b/lib/src/modules/conditional.dart @@ -104,7 +104,7 @@ abstract class _Always extends Module with CustomSystemVerilog { /// Note that it is necessary to build this module and any sensitive /// dependencies in order for sensitivity detection to work properly /// in all cases. -class Combinational extends _Always { +class Combinational extends _Always with FullyCombinational { /// Constructs a new [Combinational] which executes [conditionals] in order /// procedurally. Combinational(super.conditionals, {super.name = 'combinational'}) { @@ -118,11 +118,11 @@ class Combinational extends _Always { @override Future build() async { + await super.build(); + // any glitch on an input to an output's sensitivity should // trigger re-execution _listenToSensitivities(); - - await super.build(); } /// Sets up additional glitch listening for sensitive modules. @@ -157,15 +157,16 @@ class Combinational extends _Always { alreadyParsed.add(src); final dstConnections = src.dstConnections.toSet(); + if (src.isInput) { if (src.parentModule! is Sequential) { // sequential logic can't be a sensitivity, so ditch those return null; } - // we're at the input to another module, grab all the outputs of it and - // continue searching - dstConnections.addAll(src.parentModule!.outputs.values); + // we're at the input to another module, grab all the outputs of it which + // are combinationally connected and continue searching + dstConnections.addAll(src.parentModule!.combinationalPaths[src]!); } if (dstConnections.isEmpty) { @@ -194,7 +195,20 @@ class Combinational extends _Always { collection.addAll(subSensitivities); if (dst.isInput) { // collect all the inputs of this module too as sensitivities - collection.addAll(dst.parentModule!.inputs.values); + // but only ones which can affect outputs affected by this input! + + if (dst.parentModule! is FullyCombinational) { + // for efficiency, if purely combinational just go straight to all + collection.addAll(dst.parentModule!.inputs.values); + } else { + // default, add all inputs that may affect outputs affected + // by this input + for (final dstDependentOutput + in dst.parentModule!.combinationalPaths[dst]!) { + collection.addAll(dst.parentModule! + .reverseCombinationalPaths[dstDependentOutput]!); + } + } } } } diff --git a/lib/src/modules/gates.dart b/lib/src/modules/gates.dart index 52ff95f0a..e239dfdce 100644 --- a/lib/src/modules/gates.dart +++ b/lib/src/modules/gates.dart @@ -10,8 +10,23 @@ import 'package:rohd/rohd.dart'; +/// A [Module] which has only combinational logic within it and defines +/// custom functionality. +/// +/// This type of [Module] implies that any input port may combinationally +/// affect any output. +mixin FullyCombinational on Module { + @override + Map> getCombinationalPaths() { + // combinational gates are all combinational paths + final allOutputs = outputs.values.toList(); + return Map.fromEntries( + inputs.values.map((inputPort) => MapEntry(inputPort, allOutputs))); + } +} + /// A gate [Module] that performs bit-wise inversion. -class NotGate extends Module with InlineSystemVerilog { +class NotGate extends Module with InlineSystemVerilog, FullyCombinational { /// Name for the input of this inverter. late final String _inName; @@ -61,7 +76,8 @@ class NotGate extends Module with InlineSystemVerilog { /// A generic unary gate [Module]. /// /// It always takes one input, and the output width is always 1. -class _OneInputUnaryGate extends Module with InlineSystemVerilog { +class _OneInputUnaryGate extends Module + with InlineSystemVerilog, FullyCombinational { /// Name for the input port of this module. late final String _inName; @@ -125,7 +141,8 @@ class _OneInputUnaryGate extends Module with InlineSystemVerilog { /// /// It always takes two inputs and has one output. All ports have the /// same width. -abstract class _TwoInputBitwiseGate extends Module with InlineSystemVerilog { +abstract class _TwoInputBitwiseGate extends Module + with InlineSystemVerilog, FullyCombinational { /// Name for a first input port of this module. late final String _in0Name; @@ -221,7 +238,8 @@ abstract class _TwoInputBitwiseGate extends Module with InlineSystemVerilog { /// A generic two-input comparison gate [Module]. /// /// It always takes two inputs of the same width and has one 1-bit output. -abstract class _TwoInputComparisonGate extends Module with InlineSystemVerilog { +abstract class _TwoInputComparisonGate extends Module + with InlineSystemVerilog, FullyCombinational { /// Name for a first input port of this module. late final String _in0Name; @@ -311,7 +329,7 @@ abstract class _TwoInputComparisonGate extends Module with InlineSystemVerilog { /// /// It always takes two inputs and has one output of equal width to the primary /// of the input. -class _ShiftGate extends Module with InlineSystemVerilog { +class _ShiftGate extends Module with InlineSystemVerilog, FullyCombinational { /// Name for the main input port of this module. late final String _inName; @@ -564,7 +582,7 @@ Logic mux(Logic control, Logic d1, Logic d0) => Mux(control, d1, d0).out; /// /// If [_control] has value `1`, then [out] gets [_d1]. /// If [_control] has value `0`, then [out] gets [_d0]. -class Mux extends Module with InlineSystemVerilog { +class Mux extends Module with InlineSystemVerilog, FullyCombinational { /// Name for the control signal of this mux. late final String _controlName; @@ -659,7 +677,7 @@ class Mux extends Module with InlineSystemVerilog { /// A two-input bit index gate [Module]. /// /// It always takes two inputs and has one output of width 1. -class IndexGate extends Module with InlineSystemVerilog { +class IndexGate extends Module with InlineSystemVerilog, FullyCombinational { late final String _originalName; late final String _indexName; late final String _selectionName; @@ -737,7 +755,8 @@ class IndexGate extends Module with InlineSystemVerilog { /// /// It takes two inputs (bit and width) and outputs a [Logic] representing /// the input bit repeated over the input width -class ReplicationOp extends Module with InlineSystemVerilog { +class ReplicationOp extends Module + with InlineSystemVerilog, FullyCombinational { // input component name final String _inputName; // output component name diff --git a/test/comb_sensitivity_search_test.dart b/test/comb_sensitivity_search_test.dart new file mode 100644 index 000000000..724ea8889 --- /dev/null +++ b/test/comb_sensitivity_search_test.dart @@ -0,0 +1,45 @@ +/// Copyright (C) 2023 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause +/// +/// comb_sensitivity_search_test.dart +/// Unit tests related to Combinational sensitivity searching. +/// +/// 2023 January 5 +/// Author: Max Korbel +/// + +import 'package:rohd/rohd.dart'; +import 'package:test/test.dart'; + +class CombySubModule extends Module { + Logic get b => output('b'); + CombySubModule(Logic a) : super(name: 'combySubModule') { + a = addInput('a', a); + addOutput('b'); + + Combinational([b < a]); + } +} + +class ContainerModule extends Module { + ContainerModule(Logic a) : super(name: 'containerModule') { + a = addInput('a', a); + final b = addOutput('b'); + final bb = addOutput('bb'); + + final combySubMod = CombySubModule(a); + + // attach `b` output first so that the CombySubModule gets found by + // the output search before the inverter for `bb` + b <= combySubMod.b; + + bb <= ~combySubMod.b; + } +} + +void main() { + test('build runs properly for comb sensitivity search', () async { + final mod = ContainerModule(Logic()); + await mod.build(); + }); +} diff --git a/test/comb_sensitivity_test.dart b/test/comb_sensitivity_test.dart new file mode 100644 index 000000000..b9dc54486 --- /dev/null +++ b/test/comb_sensitivity_test.dart @@ -0,0 +1,67 @@ +/// Copyright (C) 2022 Intel Corporation +/// SPDX-License-Identifier: BSD-3-Clause +/// +/// comb_sensitivity_test.dart +/// Unit tests related to Combinational sensitivities. +/// +/// 2022 December 22 +/// Author: Max Korbel +/// + +import 'package:rohd/rohd.dart'; +import 'package:test/test.dart'; + +class SeqWrapper extends Module { + Logic get fromFlop => output('fromFlop'); + SeqWrapper(Logic toFlop, Logic toNothing) { + toFlop = addInput('toFlop', toFlop); + toNothing = addInput('toNothing', toNothing); + + addOutput('fromFlop'); + + fromFlop <= + FlipFlop( + SimpleClockGenerator(10).clk, + toFlop, + ).q; + } +} + +class TopMod extends Module { + Logic get muxToFlop => output('muxToFlop'); + TopMod(Logic theSource) { + theSource = addInput('theSource', theSource); + + final control = Logic(name: 'control'); + final toNothing = Logic(name: 'toNothing'); + + final toFlop = mux( + control, + Const(0), + theSource, + ); + + final seqWrapper = SeqWrapper(toFlop, toNothing); + + Combinational([ + control < theSource, + toNothing < seqWrapper.fromFlop, + ]); + + addOutput('muxToFlop') <= toFlop; + } +} + +void main() async { + test('false sensitivity does not cause false comb cycle', () async { + final theSource = Logic(name: 'theSource')..put(0); + + final mod = TopMod(theSource); + + await mod.build(); + + theSource.put(1); + + expect(mod.muxToFlop.value.isValid, isTrue); + }); +}