From e6cfe51ab637ca3eea7bd726db145cf3e25e147e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 26 Nov 2024 10:30:39 -0800 Subject: [PATCH 01/24] refactored synthesizers to be more modular, passing tests --- lib/src/synthesizers/synthesizers.dart | 2 +- lib/src/synthesizers/systemverilog.dart | 1796 ----------------- .../systemverilog/systemverilog.dart | 11 + .../systemverilog/systemverilog_mixins.dart | 182 ++ ...systemverilog_synth_module_definition.dart | 267 +++ ...erilog_synth_sub_module_instantiation.dart | 58 + .../systemverilog_synthesis_result.dart | 198 ++ .../systemverilog_synthesizer.dart | 144 ++ .../utilities/synth_assignment.dart | 36 + .../synthesizers/utilities/synth_logic.dart | 343 ++++ .../utilities/synth_module_definition.dart | 527 +++++ .../synth_sub_module_instantiation.dart | 98 + lib/src/synthesizers/utilities/utilities.dart | 7 + 13 files changed, 1872 insertions(+), 1797 deletions(-) delete mode 100644 lib/src/synthesizers/systemverilog.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog_mixins.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart create mode 100644 lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart create mode 100644 lib/src/synthesizers/utilities/synth_assignment.dart create mode 100644 lib/src/synthesizers/utilities/synth_logic.dart create mode 100644 lib/src/synthesizers/utilities/synth_module_definition.dart create mode 100644 lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart create mode 100644 lib/src/synthesizers/utilities/utilities.dart diff --git a/lib/src/synthesizers/synthesizers.dart b/lib/src/synthesizers/synthesizers.dart index 1cdadd3ad..528577bb4 100644 --- a/lib/src/synthesizers/synthesizers.dart +++ b/lib/src/synthesizers/synthesizers.dart @@ -4,4 +4,4 @@ export 'synth_builder.dart'; export 'synthesis_result.dart'; export 'synthesizer.dart'; -export 'systemverilog.dart'; +export 'systemverilog/systemverilog.dart'; diff --git a/lib/src/synthesizers/systemverilog.dart b/lib/src/synthesizers/systemverilog.dart deleted file mode 100644 index f12c7abd1..000000000 --- a/lib/src/synthesizers/systemverilog.dart +++ /dev/null @@ -1,1796 +0,0 @@ -// Copyright (C) 2021-2024 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// systemverilog.dart -// Definition for SystemVerilog Synthesizer -// -// 2021 August 26 -// Author: Max Korbel - -import 'dart:collection'; - -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/utilities/sanitizer.dart'; -import 'package:rohd/src/utilities/uniquifier.dart'; - -/// A [Synthesizer] which generates equivalent SystemVerilog as the -/// given [Module]. -/// -/// Attempts to maintain signal naming and structure as much as possible. -class SystemVerilogSynthesizer extends Synthesizer { - @override - bool generatesDefinition(Module module) => - // ignore: deprecated_member_use_from_same_package - !((module is CustomSystemVerilog) || - (module is SystemVerilog && - module.generatedDefinitionType == DefinitionGenerationType.none)); - - /// Creates a line of SystemVerilog that instantiates [module]. - /// - /// The instantiation will create it as type [instanceType] and name - /// [instanceName]. - /// - /// [ports] maps [module] input/output/inout names to a verilog signal name. - /// - /// For example: - /// To generate this SystemVerilog: `sig_c = sig_a & sig_b` - /// Based on this module definition: `c <= a & b` - /// The values for [ports] should be: - /// ports: `{ 'a' : 'sig_a', 'b' : 'sig_b', 'c' : 'sig_c'}` - /// - /// If [forceStandardInstantiation] is set, then the standard instantiation - /// for SystemVerilog modules will be used. - /// - /// If [parameters] is provided, then the module will be instantiated with - /// all of the keys as parameter names set to the corresponding values - /// provided. - static String instantiationVerilogFor( - {required Module module, - required String instanceType, - required String instanceName, - required Map ports, - Map? parameters, - bool forceStandardInstantiation = false}) { - if (!forceStandardInstantiation) { - if (module is SystemVerilog) { - return module.instantiationVerilog( - instanceType, - instanceName, - ports, - ) ?? - instantiationVerilogFor( - module: module, - instanceType: instanceType, - instanceName: instanceName, - ports: ports, - forceStandardInstantiation: true); - } - // ignore: deprecated_member_use_from_same_package - else if (module is CustomSystemVerilog) { - return module.instantiationVerilog( - instanceType, - instanceName, - Map.fromEntries(ports.entries - .where((element) => module.inputs.containsKey(element.key))), - Map.fromEntries(ports.entries - .where((element) => module.outputs.containsKey(element.key))), - ); - } - } - - //non-custom needs more details - final connections = []; - - for (final signalName in module.inputs.keys) { - connections.add('.$signalName(${ports[signalName]!})'); - } - - for (final signalName in module.outputs.keys) { - connections.add('.$signalName(${ports[signalName]!})'); - } - - for (final signalName in module.inOuts.keys) { - connections.add('.$signalName(${ports[signalName]!})'); - } - - final connectionsStr = connections.join(','); - - var parameterString = ''; - if (parameters != null && parameters.isNotEmpty) { - final parameterContents = - parameters.entries.map((e) => '.${e.key}(${e.value})').join(','); - parameterString = '#($parameterContents)'; - } - - return '$instanceType $parameterString $instanceName($connectionsStr);'; - } - - /// Creates a line of SystemVerilog that instantiates [module]. - /// - /// The instantiation will create it as type [instanceType] and name - /// [instanceName]. - /// - /// [inputs] and [outputs] map `module` input/output name to a verilog signal - /// name. - /// - /// For example: - /// To generate this SystemVerilog: `sig_c = sig_a & sig_b` - /// Based on this module definition: `c <= a & b` - /// The values for [inputs] and [outputs] should be: - /// inputs: `{ 'a' : 'sig_a', 'b' : 'sig_b'}` - /// outputs: `{ 'c' : 'sig_c' }` - @Deprecated('Use `instantiationVerilogFor` instead.') - static String instantiationVerilogWithParameters( - Module module, - String instanceType, - String instanceName, - Map inputs, - Map outputs, - {Map inOuts = const {}, - Map? parameters, - bool forceStandardInstantiation = false}) => - instantiationVerilogFor( - module: module, - instanceType: instanceType, - instanceName: instanceName, - ports: {...inputs, ...outputs, ...inOuts}, - parameters: parameters, - forceStandardInstantiation: forceStandardInstantiation, - ); - - @override - SynthesisResult synthesize( - Module module, String Function(Module module) getInstanceTypeOfModule) { - assert( - module is! SystemVerilog || - module.generatedDefinitionType != DefinitionGenerationType.none, - 'SystemVerilog modules synthesized must generate a definition.'); - - return module is SystemVerilog && - module.generatedDefinitionType == DefinitionGenerationType.custom - ? _SystemVerilogCustomDefinitionSynthesisResult( - module, getInstanceTypeOfModule) - : _SystemVerilogSynthesisResult(module, getInstanceTypeOfModule); - } -} - -/// Allows a [Module] to define a custom implementation of SystemVerilog to be -/// injected in generated output instead of instantiating a separate `module`. -@Deprecated('Use `SystemVerilog` instead') -mixin CustomSystemVerilog on Module { - /// Generates custom SystemVerilog to be injected in place of a `module` - /// instantiation. - /// - /// The [instanceType] and [instanceName] represent the type and name, - /// respectively of the module that would have been instantiated had it not - /// been overridden. The [Map]s [inputs] and [outputs] are a mapping from the - /// [Module]'s port names to the names of the signals that are passed into - /// those ports in the generated SystemVerilog. - String instantiationVerilog(String instanceType, String instanceName, - Map inputs, Map outputs); - - /// A list of names of [input]s which should not have any SystemVerilog - /// expressions (including constants) in-lined into them. Only signal names - /// will be fed into these. - @protected - final List expressionlessInputs = const []; -} - -/// Represents the definition of a SystemVerilog parameter at the time of -/// declaration of a module definition. -class SystemVerilogParameterDefinition { - /// The SystemVerilog type to use for declaring this parameter. - final String type; - - /// The default value for this parameter. - final String defaultValue; - - /// The name of the parameter. - final String name; - - /// Creates a new SystemVerilog parameter definition with [name] of the - /// provided [type] with the [defaultValue]. - const SystemVerilogParameterDefinition(this.name, - {required this.type, required this.defaultValue}); -} - -/// Allows a [Module] to control the instantiation and/or definition of -/// generated SystemVerilog for that module. -mixin SystemVerilog on Module { - /// Generates custom SystemVerilog to be injected in place of a `module` - /// instantiation. - /// - /// The [instanceType] and [instanceName] represent the type and name, - /// respectively of the module that would have been instantiated had it not - /// been overridden. [ports] is a mapping from the [Module]'s port names to - /// the names of the signals that are passed into those ports in the generated - /// SystemVerilog. - /// - /// If a standard instantiation is desired, either return `null` or use - /// [SystemVerilogSynthesizer.instantiationVerilogFor] with - /// `forceStandardInstantiation` set to `true`. By default, `null` is - /// returned and thus a standard instantiation is used. - String? instantiationVerilog( - String instanceType, - String instanceName, - Map ports, - ) => - null; - - /// A list of names of [input]s which should not have any SystemVerilog - /// expressions (including constants) in-lined into them. Only signal names - /// will be fed into these. - @protected - final List expressionlessInputs = const []; - - /// A custom SystemVerilog definition to be produced for this [Module]. - /// - /// If an empty string is returned (the default behavior), then no definition - /// will be generated. - /// - /// If `null` is returned, then a default definition will be generated. - /// - /// This function should have no side effects and always return the same thing - /// for the same inputs. - String? definitionVerilog(String definitionType) => ''; - - /// A collection of SystemVerilog [SystemVerilogParameterDefinition]s to be - /// declared on the definition when generating SystemVerilog for this [Module] - /// if [generatedDefinitionType] is [DefinitionGenerationType.standard]. - /// - /// If `null` is returned (the default), then no parameters will be generated. - /// Otherwise, this function should have no side effects and always return the - /// same thing for the same inputs. - List? get definitionParameters => null; - - /// What kind of SystemVerilog definition this [Module] generates, or whether - /// it does at all. - /// - /// By default, this is automatically calculated based on the return value of - /// [definitionVerilog]. - DefinitionGenerationType get generatedDefinitionType { - final def = definitionVerilog('*PLACEHOLDER*'); - if (def == null) { - return DefinitionGenerationType.standard; - } else if (def.isNotEmpty) { - return DefinitionGenerationType.custom; - } else { - return DefinitionGenerationType.none; - } - } -} - -/// A type of generation for generated outputs. -enum DefinitionGenerationType { - /// No definition will be generated. - none, - - /// A standard definition will be generated. - standard, - - /// A custom definition will be generated. - custom, -} - -/// Allows a [Module] to define a special type of [SystemVerilog] which can be -/// inlined within other SystemVerilog code. -/// -/// The inline SystemVerilog will get parentheses wrapped around it and then -/// dropped into other code in the same way a variable name is. -mixin InlineSystemVerilog on Module implements SystemVerilog { - /// Generates custom SystemVerilog to be injected in place of the output - /// port's corresponding signal name. - /// - /// The [inputs] are a mapping from the [Module]'s port names to the names of - /// the signals that are passed into those ports in the generated - /// SystemVerilog. It will only contain [input]s and [inOut]s, as there should - /// only be one [output] (named [resultSignalName]) which is driven by the - /// expression. - /// - /// The output will be appropriately wrapped with parentheses to guarantee - /// proper order of operations. - String inlineVerilog(Map inputs); - - /// The name of the [output] (or [inOut]) port which can be the in-lined - /// symbol. - /// - /// By default, this assumes one [output] port. This should be overridden in - /// classes which have an [inOut] port as the in-lined symbol. - String get resultSignalName { - if (outputs.keys.length != 1) { - throw Exception('Inline verilog expected to have exactly one output,' - ' but saw $outputs.'); - } - - return outputs.keys.first; - } - - @override - String instantiationVerilog( - String instanceType, - String instanceName, - Map ports, - ) { - final result = ports[resultSignalName]; - final inputPorts = Map.fromEntries( - ports.entries.where((element) => - inputs.containsKey(element.key) || - (inOuts.containsKey(element.key) && element.key != resultSignalName)), - ); - final inline = inlineVerilog(inputPorts); - return 'assign $result = $inline; // $instanceName'; - } - - @override - @protected - final List expressionlessInputs = const []; - - @override - String? definitionVerilog(String definitionType) => ''; - - @override - DefinitionGenerationType get generatedDefinitionType => - DefinitionGenerationType.none; - - @override - List? get definitionParameters => null; -} - -/// A [SynthesisResult] representing a [Module] that provides a custom -/// SystemVerilog definition. -class _SystemVerilogCustomDefinitionSynthesisResult extends SynthesisResult { - _SystemVerilogCustomDefinitionSynthesisResult( - super.module, super.getInstanceTypeOfModule) - : assert( - module is SystemVerilog && - module.generatedDefinitionType == - DefinitionGenerationType.custom, - 'This should only be used for custom system verilog definitions.'); - - @override - int get matchHashCode => - (module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!.hashCode; - - @override - bool matchesImplementation(SynthesisResult other) => - other is _SystemVerilogCustomDefinitionSynthesisResult && - (module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')! == - (other.module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!; - - @override - String toFileContents() => (module as SystemVerilog) - .definitionVerilog(getInstanceTypeOfModule(module))!; -} - -/// A [SynthesisResult] representing a conversion of a [Module] to -/// SystemVerilog. -class _SystemVerilogSynthesisResult extends SynthesisResult { - /// A cached copy of the generated ports. - late final String _portsString; - - /// A cached copy of the generated contents of the module. - late final String _moduleContentsString; - - /// A cached copy of the generated parameters. - late final String? _parameterString; - - /// The main [_SynthModuleDefinition] for this. - final _SynthModuleDefinition _synthModuleDefinition; - - @override - List get supportingModules => - _synthModuleDefinition.supportingModules; - - _SystemVerilogSynthesisResult(super.module, super.getInstanceTypeOfModule) - : _synthModuleDefinition = _SynthModuleDefinition(module) { - _portsString = _verilogPorts(); - _moduleContentsString = _verilogModuleContents(getInstanceTypeOfModule); - _parameterString = _verilogParameters(module); - } - - @override - bool matchesImplementation(SynthesisResult other) => - other is _SystemVerilogSynthesisResult && - other._portsString == _portsString && - other._parameterString == _parameterString && - other._moduleContentsString == _moduleContentsString; - - @override - int get matchHashCode => - _portsString.hashCode ^ - _moduleContentsString.hashCode ^ - _parameterString.hashCode; - - @override - String toFileContents() => _toVerilog(getInstanceTypeOfModule); - - /// Representation of all input port declarations in generated SV. - List _verilogInputs() { - final declarations = _synthModuleDefinition.inputs - .map((sig) => 'input ${sig.definitionType()} ${sig.definitionName()}') - .toList(growable: false); - return declarations; - } - - /// Representation of all output port declarations in generated SV. - List _verilogOutputs() { - final declarations = _synthModuleDefinition.outputs - .map((sig) => 'output ${sig.definitionType()} ${sig.definitionName()}') - .toList(growable: false); - return declarations; - } - - /// Representation of all inout port declarations in generated SV. - List _verilogInOuts() { - final declarations = _synthModuleDefinition.inOuts - .map((sig) => 'inout ${sig.definitionType()} ${sig.definitionName()}') - .toList(growable: false); - return declarations; - } - - /// Representation of all internal net declarations in generated SV. - String _verilogInternalSignals() { - final declarations = []; - for (final sig in _synthModuleDefinition.internalSignals - .sorted((a, b) => a.name.compareTo(b.name))) { - if (sig.needsDeclaration) { - declarations.add('${sig.definitionType()} ${sig.definitionName()};'); - } - } - return declarations.join('\n'); - } - - /// Representation of all assignments in generated SV. - String _verilogAssignments() { - final assignmentLines = []; - for (final assignment in _synthModuleDefinition.assignments) { - assert( - !(assignment.src.isNet && assignment.dst.isNet), - 'Net connections should have been implemented as' - ' bidirectional net connections.'); - - assignmentLines - .add('assign ${assignment.dst.name} = ${assignment.src.name};'); - } - return assignmentLines.join('\n'); - } - - /// Representation of all sub-module instantiations in generated SV. - String _verilogSubModuleInstantiations( - String Function(Module module) getInstanceTypeOfModule) { - final subModuleLines = []; - for (final subModuleInstantiation - in _synthModuleDefinition.moduleToSubModuleInstantiationMap.values) { - final instanceType = - getInstanceTypeOfModule(subModuleInstantiation.module); - - final instantiationVerilog = - subModuleInstantiation.instantiationVerilog(instanceType); - if (instantiationVerilog != null) { - subModuleLines.add(instantiationVerilog); - } - } - return subModuleLines.join('\n'); - } - - /// The contents of this module converted to SystemVerilog without module - /// declaration, ports, etc. - String _verilogModuleContents( - String Function(Module module) getInstanceTypeOfModule) => - [ - _verilogInternalSignals(), - _verilogAssignments(), // order matters! - _verilogSubModuleInstantiations(getInstanceTypeOfModule), - ].where((element) => element.isNotEmpty).join('\n'); - - /// The representation of all port declarations. - String _verilogPorts() => [ - ..._verilogInputs(), - ..._verilogOutputs(), - ..._verilogInOuts(), - ].join(',\n'); - - String? _verilogParameters(Module module) { - if (module is SystemVerilog) { - final defParams = module.definitionParameters; - if (defParams == null || defParams.isEmpty) { - return null; - } - - return [ - '#(', - defParams - .map((p) => 'parameter ${p.type} ${p.name} = ${p.defaultValue}') - .join(',\n'), - ')', - ].join('\n'); - } - - return null; - } - - /// The full SV representation of this module. - String _toVerilog(String Function(Module module) getInstanceTypeOfModule) { - final verilogModuleName = getInstanceTypeOfModule(module); - return [ - [ - 'module $verilogModuleName', - _parameterString, - '(', - ].nonNulls.join(' '), - _portsString, - ');', - _moduleContentsString, - 'endmodule : $verilogModuleName' - ].join('\n'); - } -} - -/// Represents an instantiation of a module within another module. -class _SynthSubModuleInstantiation { - /// The module represented. - final Module module; - - /// The name of this instance. - String? _name; - - /// Must call [pickName] before this is accessible. - String get name => _name!; - - /// Selects a name for this module instance. Must be called exactly once. - void pickName(Uniquifier uniquifier) { - assert(_name == null, 'Should only pick a name once.'); - - _name = uniquifier.getUniqueName( - initialName: module.uniqueInstanceName, - reserved: module.reserveName, - nullStarter: 'm', - ); - } - - /// A mapping of input port name to [_SynthLogic]. - late final Map inputMapping = - UnmodifiableMapView(_inputMapping); - final Map _inputMapping = {}; - - /// Adds an input mapping from [name] to [synthLogic]. - void setInputMapping(String name, _SynthLogic synthLogic, - {bool replace = false}) { - assert(module.inputs.containsKey(name), - 'Input $name not found in module ${module.name}.'); - assert( - (replace && _inputMapping.containsKey(name)) || - !_inputMapping.containsKey(name), - 'A mapping already exists to this input: $name.'); - - _inputMapping[name] = synthLogic; - } - - /// A mapping of output port name to [_SynthLogic]. - late final Map outputMapping = - UnmodifiableMapView(_outputMapping); - final Map _outputMapping = {}; - - /// Adds an output mapping from [name] to [synthLogic]. - void setOutputMapping(String name, _SynthLogic synthLogic, - {bool replace = false}) { - assert(module.outputs.containsKey(name), - 'Output $name not found in module ${module.name}.'); - assert( - (replace && _outputMapping.containsKey(name)) || - !_outputMapping.containsKey(name), - 'A mapping already exists to this output: $name.'); - - _outputMapping[name] = synthLogic; - } - - /// A mapping of output port name to [_SynthLogic]. - late final Map inOutMapping = - UnmodifiableMapView(_inOutMapping); - final Map _inOutMapping = {}; - - void setInOutMapping(String name, _SynthLogic synthLogic, - {bool replace = false}) { - assert(module.inOuts.containsKey(name), - 'InOut $name not found in module ${module.name}.'); - assert( - (replace && _inOutMapping.containsKey(name)) || - !_inOutMapping.containsKey(name), - 'A mapping already exists to this output: $name.'); - - _inOutMapping[name] = synthLogic; - } - - /// If [module] is [InlineSystemVerilog], this will be the [_SynthLogic] that - /// is the `result` of that module. Otherwise, `null`. - _SynthLogic? get inlineResultLogic => module is! InlineSystemVerilog - ? null - : (outputMapping[(module as InlineSystemVerilog).resultSignalName] ?? - inOutMapping[(module as InlineSystemVerilog).resultSignalName]); - - /// Indicates whether this module should be declared. - bool get needsDeclaration => _needsDeclaration; - bool _needsDeclaration = true; - - /// Removes the need for this module to be declared (via [needsDeclaration]). - void clearDeclaration() { - _needsDeclaration = false; - } - - /// Mapping from [_SynthLogic]s which are outputs of inlineable SV to those - /// inlineable modules. - Map<_SynthLogic, _SynthSubModuleInstantiation>? - _synthLogicToInlineableSynthSubmoduleMap; - - /// Creates an instantiation for [module]. - _SynthSubModuleInstantiation(this.module); - - @override - String toString() => - "_SynthSubModuleInstantiation ${_name == null ? 'null' : '"$name"'}, " - "module name:'${module.name}'"; - - /// Provides a mapping from ports of this module to a string that can be fed - /// into that port, which may include inline SV modules as well. - Map _modulePortsMapWithInline( - Map plainPorts) => - plainPorts.map((name, synthLogic) => MapEntry( - name, - _synthLogicToInlineableSynthSubmoduleMap?[synthLogic] - ?.inlineVerilog() ?? - synthLogic.name)); - - /// Provides the inline SV representation for this module. - /// - /// Should only be called if [module] is [InlineSystemVerilog]. - String inlineVerilog() { - final inlineSvRepresentation = - (module as InlineSystemVerilog).inlineVerilog( - _modulePortsMapWithInline({...inputMapping, ...inOutMapping} - ..remove((module as InlineSystemVerilog).resultSignalName)), - ); - - return '($inlineSvRepresentation)'; - } - - /// Provides the full SV instantiation for this module. - String? instantiationVerilog(String instanceType) { - if (!needsDeclaration) { - return null; - } - return SystemVerilogSynthesizer.instantiationVerilogFor( - module: module, - instanceType: instanceType, - instanceName: name, - ports: _modulePortsMapWithInline({ - ...inputMapping, - ...outputMapping, - ...inOutMapping, - })); - } -} - -/// A special [Module] for connecting or assigning two SystemVerilog nets -/// together bidirectionally. -/// -/// The `alias` keyword in SystemVerilog could alternatively work, but many -/// tools do not support it, so this `module` definition is a convenient trick -/// to accomplish the same thing in a tool-compatible way. -class _NetConnect extends Module with SystemVerilog { - static const String _definitionName = 'net_connect'; - - /// The width of the nets on this instance. - final int width; - - @override - bool get hasBuilt => - // we force it to say it has built since it is being generated post-build - true; - - /// The name of net 0. - static final String n0Name = Naming.unpreferredName('n0'); - - /// The name of net 1. - static final String n1Name = Naming.unpreferredName('n1'); - - _NetConnect(LogicNet n0, LogicNet n1) - : assert(n0.width == n1.width, 'Widths must be equal.'), - width = n0.width, - super( - definitionName: _definitionName, - name: _definitionName, - ) { - n0 = addInOut(n0Name, n0, width: width); - n1 = addInOut(n1Name, n1, width: width); - } - - @override - String instantiationVerilog( - String instanceType, String instanceName, Map ports) { - assert(instanceType == _definitionName, - 'Instance type selected should match the definition name.'); - return '$instanceType' - ' #(.WIDTH($width))' - ' $instanceName' - ' (${ports[n0Name]}, ${ports[n1Name]});'; - } - - @override - String? definitionVerilog(String definitionType) => ''' -// A special module for connecting two nets bidirectionally -module $definitionType #(parameter WIDTH=1) (w, w); -inout wire[WIDTH-1:0] w; -endmodule'''; -} - -/// Represents the definition of a module. -class _SynthModuleDefinition { - /// The [Module] being defined. - final Module module; - - final List<_SynthAssignment> assignments = []; - - /// All other internal signals that are not ports. - /// - /// This is the only collection that maye have mergeable items in it. - final Set<_SynthLogic> internalSignals = {}; - - /// All the input ports. - /// - /// This will *never* have any mergeable items in it. - final Set<_SynthLogic> inputs = {}; - - /// All the output ports. - /// - /// This will *never* have any mergeable items in it. - final Set<_SynthLogic> outputs = {}; - - /// All the output ports. - /// - /// This will *never* have any mergeable items in it. - final Set<_SynthLogic> inOuts = {}; - - /// A mapping from original [Logic]s to the [_SynthLogic]s that represent - /// them. - final Map logicToSynthMap = HashMap(); - - /// A mapping from the original [Module]s to the - /// [_SynthSubModuleInstantiation]s that represent them. - final Map - moduleToSubModuleInstantiationMap = {}; - - /// Either accesses a previously created [_SynthSubModuleInstantiation] - /// corresponding to [m], or else creates a new one and adds it to the - /// [moduleToSubModuleInstantiationMap]. - _SynthSubModuleInstantiation _getSynthSubModuleInstantiation(Module m) { - if (moduleToSubModuleInstantiationMap.containsKey(m)) { - return moduleToSubModuleInstantiationMap[m]!; - } else { - final newSSMI = _SynthSubModuleInstantiation(m); - moduleToSubModuleInstantiationMap[m] = newSSMI; - return newSSMI; - } - } - - @override - String toString() => "module name: '${module.name}'"; - - /// Used to uniquify any identifiers, including signal names - /// and module instances. - final Uniquifier _synthInstantiationNameUniquifier; - - /// Either accesses a previously created [_SynthLogic] corresponding to - /// [logic], or else creates a new one and adds it to the [logicToSynthMap]. - _SynthLogic? _getSynthLogic( - Logic? logic, - ) { - if (logic == null) { - return null; - } else if (logicToSynthMap.containsKey(logic)) { - return logicToSynthMap[logic]!; - } else { - _SynthLogic newSynth; - if (logic.isArrayMember) { - // grab the parent array (potentially recursively) - final parentArraySynthLogic = - // ignore: unnecessary_null_checks - _getSynthLogic(logic.parentStructure!); - - newSynth = _SynthLogicArrayElement(logic, parentArraySynthLogic!); - } else { - final disallowConstName = logic.isInput && - // ignore: deprecated_member_use_from_same_package - ((logic.parentModule is CustomSystemVerilog && - // ignore: deprecated_member_use_from_same_package - (logic.parentModule! as CustomSystemVerilog) - .expressionlessInputs - .contains(logic.name)) || - (logic.parentModule is SystemVerilog && - (logic.parentModule! as SystemVerilog) - .expressionlessInputs - .contains(logic.name))); - - newSynth = _SynthLogic( - logic, - namingOverride: (logic.isPort && logic.parentModule != module) - ? Naming.mergeable - : null, - constNameDisallowed: disallowConstName, - ); - } - - logicToSynthMap[logic] = newSynth; - return newSynth; - } - } - - /// A [List] of supporting modules that need to be instantiated within this - /// definition. - final List supportingModules = []; - - /// Creates a new definition representation for this [module]. - _SynthModuleDefinition(this.module) - : _synthInstantiationNameUniquifier = Uniquifier( - reservedNames: { - ...module.inputs.keys, - ...module.outputs.keys, - ...module.inOuts.keys, - }, - ), - assert( - !(module is SystemVerilog && - module.generatedDefinitionType == - DefinitionGenerationType.none), - 'Do not build a definition for a module' - ' which generates no definition!') { - // start by traversing output signals - final logicsToTraverse = TraverseableCollection() - ..addAll(module.outputs.values) - ..addAll(module.inOuts.values); - - for (final output in module.outputs.values) { - outputs.add(_getSynthLogic(output)!); - } - - // make sure disconnected inputs are included - for (final input in module.inputs.values) { - inputs.add(_getSynthLogic(input)!); - } - - // make sure disconnected inouts are included, also - for (final inOut in module.inOuts.values) { - inOuts.add(_getSynthLogic(inOut)!); - } - - // find any named signals sitting around that don't do anything - // this is not necessary for functionality, just nice naming inclusion - logicsToTraverse.addAll( - module.internalSignals - .where((element) => element.naming != Naming.unnamed), - ); - - // make sure floating modules are included - for (final subModule in module.subModules) { - _getSynthSubModuleInstantiation(subModule); - logicsToTraverse - ..addAll(subModule.inputs.values) - ..addAll(subModule.outputs.values) - ..addAll(subModule.inOuts.values); - } - - // search for other modules contained within this module - - for (var i = 0; i < logicsToTraverse.length; i++) { - final receiver = logicsToTraverse[i]; - - assert( - receiver.parentModule != null, - 'Any signal traced by this should have been detected by build,' - ' but $receiver was not.'); - - if (receiver.parentModule != module && - !module.subModules.contains(receiver.parentModule)) { - // This should never happen! - assert(false, 'Receiver is not in this module or a submodule.'); - continue; - } - - if (receiver is LogicArray) { - logicsToTraverse.addAll(receiver.elements); - } - - if (receiver.isArrayMember) { - logicsToTraverse.add(receiver.parentStructure!); - } - - final synthReceiver = _getSynthLogic(receiver)!; - - if (receiver is LogicNet) { - logicsToTraverse.addAll([ - ...receiver.srcConnections, - ...receiver.dstConnections - ].where((element) => element.parentModule == module)); - - for (final srcConnection in receiver.srcConnections) { - if (srcConnection.parentModule == module || - (srcConnection.isOutput && - srcConnection.parentModule!.parent == module)) { - final netSynthDriver = _getSynthLogic(srcConnection)!; - - assignments.add(_SynthAssignment( - netSynthDriver, - synthReceiver, - )); - } - } - } - - final driver = receiver.srcConnection; - - final receiverIsConstant = driver == null && receiver is Const; - - final receiverIsModuleInput = - module.isInput(receiver) && !receiver.isArrayMember; - final receiverIsModuleOutput = - module.isOutput(receiver) && !receiver.isArrayMember; - final receiverIsModuleInOut = - module.isInOut(receiver) && !receiver.isArrayMember; - - final synthDriver = _getSynthLogic(driver); - - if (receiverIsModuleInput) { - inputs.add(synthReceiver); - } else if (receiverIsModuleOutput) { - outputs.add(synthReceiver); - } else if (receiverIsModuleInOut) { - inOuts.add(synthReceiver); - } else { - internalSignals.add(synthReceiver); - } - - final receiverIsSubmoduleInOut = - receiver.isInOut && (receiver.parentModule?.parent == module); - if (receiverIsSubmoduleInOut) { - final subModule = receiver.parentModule!; - - if (synthReceiver is! _SynthLogicArrayElement) { - _getSynthSubModuleInstantiation(subModule) - .setInOutMapping(receiver.name, synthReceiver); - } - - logicsToTraverse.addAll(subModule.inOuts.values); - } - - final receiverIsSubModuleOutput = - receiver.isOutput && (receiver.parentModule?.parent == module); - if (receiverIsSubModuleOutput) { - final subModule = receiver.parentModule!; - - // array elements are not named ports, just contained in array - if (synthReceiver is! _SynthLogicArrayElement) { - _getSynthSubModuleInstantiation(subModule) - .setOutputMapping(receiver.name, synthReceiver); - } - - logicsToTraverse - ..addAll(subModule.inputs.values) - ..addAll(subModule.inOuts.values); - } else if (driver != null) { - if (!module.isInput(receiver) && !module.isInOut(receiver)) { - // stop at the input to this module - logicsToTraverse.add(driver); - assignments.add(_SynthAssignment(synthDriver!, synthReceiver)); - } - } else if (receiverIsConstant && !receiver.value.isFloating) { - // this is a const that is valid, *partially* invalid (e.g. 0b1z1x0), - // or anything that's not *entirely* floating (since those we can leave - // as completely undriven). - - // make a new const node, it will merge away if not needed - final newReceiverConst = _getSynthLogic(Const(receiver.value))!; - internalSignals.add(newReceiverConst); - assignments.add(_SynthAssignment(newReceiverConst, synthReceiver)); - } - - final receiverIsSubModuleInput = - receiver.isInput && (receiver.parentModule?.parent == module); - if (receiverIsSubModuleInput) { - final subModule = receiver.parentModule!; - - // array elements are not named ports, just contained in array - if (synthReceiver is! _SynthLogicArrayElement) { - _getSynthSubModuleInstantiation(subModule) - .setInputMapping(receiver.name, synthReceiver); - } - } - } - - // The order of these is important! - _collapseArrays(); - _collapseAssignments(); - _assignSubmodulePortMapping(); - _replaceNetConnections(); - _collapseChainableModules(); - _replaceInOutConnectionInlineableModules(); - _pickNames(); - } - - /// Creates a new [_NetConnect] module to synthesize assignment between two - /// [LogicNet]s. - _SynthSubModuleInstantiation _addNetConnect( - _SynthLogic dst, _SynthLogic src) { - // make an (unconnected) module representing the assignment - final netConnect = - _NetConnect(LogicNet(width: dst.width), LogicNet(width: src.width)); - - // instantiate the module within the definition - final netConnectSynthSubModInst = - _getSynthSubModuleInstantiation(netConnect) - - // map inouts to the appropriate `_SynthLogic`s - ..setInOutMapping(_NetConnect.n0Name, dst) - ..setInOutMapping(_NetConnect.n1Name, src); - - // notify the `SynthBuilder` that it needs declaration - supportingModules.add(netConnect); - - return netConnectSynthSubModInst; - } - - /// Replace all [assignments] between two [LogicNet]s with a [_NetConnect]. - void _replaceNetConnections() { - final reducedAssignments = <_SynthAssignment>[]; - - for (final assignment in assignments) { - if (assignment.src.isNet && assignment.dst.isNet) { - _addNetConnect(assignment.dst, assignment.src); - } else { - reducedAssignments.add(assignment); - } - } - - // only swap them if we actually did anything - if (assignments.length != reducedAssignments.length) { - assignments - ..clear() - ..addAll(reducedAssignments); - } - } - - /// Updates all sub-module instantiations with information about which - /// [_SynthLogic] should be used for their ports. - void _assignSubmodulePortMapping() { - for (final submoduleInstantiation - in moduleToSubModuleInstantiationMap.values) { - for (final inputName in submoduleInstantiation.module.inputs.keys) { - final orig = submoduleInstantiation.inputMapping[inputName]!; - submoduleInstantiation.setInputMapping( - inputName, orig.replacement ?? orig, - replace: true); - } - - for (final outputName in submoduleInstantiation.module.outputs.keys) { - final orig = submoduleInstantiation.outputMapping[outputName]!; - submoduleInstantiation.setOutputMapping( - outputName, orig.replacement ?? orig, - replace: true); - } - - for (final inOutName in submoduleInstantiation.module.inOuts.keys) { - final orig = submoduleInstantiation.inOutMapping[inOutName]!; - submoduleInstantiation.setInOutMapping( - inOutName, orig.replacement ?? orig, - replace: true); - } - } - } - - /// Picks names of signals and sub-modules. - void _pickNames() { - // first ports get priority - for (final input in inputs) { - input.pickName(_synthInstantiationNameUniquifier); - } - for (final output in outputs) { - output.pickName(_synthInstantiationNameUniquifier); - } - for (final inOut in inOuts) { - inOut.pickName(_synthInstantiationNameUniquifier); - } - - // pick names of *reserved* submodule instances - final nonReservedSubmodules = <_SynthSubModuleInstantiation>[]; - for (final submodule in moduleToSubModuleInstantiationMap.values) { - if (submodule.module.reserveName) { - submodule.pickName(_synthInstantiationNameUniquifier); - assert(submodule.module.name == submodule.name, - 'Expect reserved names to retain their name.'); - } else { - nonReservedSubmodules.add(submodule); - } - } - - // then *reserved* internal signals get priority - final nonReservedSignals = <_SynthLogic>[]; - for (final signal in internalSignals) { - if (signal.isReserved) { - signal.pickName(_synthInstantiationNameUniquifier); - } else { - nonReservedSignals.add(signal); - } - } - - // then submodule instances - for (final submodule - in nonReservedSubmodules.where((element) => element.needsDeclaration)) { - submodule.pickName(_synthInstantiationNameUniquifier); - } - - // then the rest of the internal signals - for (final signal in nonReservedSignals) { - signal.pickName(_synthInstantiationNameUniquifier); - } - } - - /// Collapses chainable, inlineable modules. - void _collapseChainableModules() { - // collapse multiple lines of in-line assignments into one where they are - // unnamed one-liners - // for example, be capable of creating lines like: - // assign x = a & b & c & _d_and_e - // assign _d_and_e = d & e - // assign y = _d_and_e - - // Also feed collapsed chained modules into other modules - // Need to consider order of operations in systemverilog or else add () - // everywhere! (for now add the parentheses) - - // Algorithm: - // - find submodule instantiations that are inlineable - // - filter to those who only output as input to one other module - // - pass an override to the submodule instantiation that the corresponding - // input should map to the output of another submodule instantiation - // do not collapse if signal feeds to multiple inputs of other modules - - final inlineableSubmoduleInstantiations = module.subModules - .whereType() - .map(_getSynthSubModuleInstantiation); - - // number of times each signal name is used by any module - final signalUsage = <_SynthLogic, int>{}; - - for (final subModuleInstantiation - in moduleToSubModuleInstantiationMap.values) { - for (final inSynthLogic in [ - ...subModuleInstantiation.inputMapping.values, - ...subModuleInstantiation.inOutMapping.values - ]) { - if (inputs.contains(inSynthLogic) || inOuts.contains(inSynthLogic)) { - // dont worry about inputs to THIS module - continue; - } - - if (subModuleInstantiation.inlineResultLogic == inSynthLogic) { - // don't worry about the result signal - continue; - } - - signalUsage.update( - inSynthLogic, - (value) => value + 1, - ifAbsent: () => 1, - ); - } - } - - final singleUseSignals = <_SynthLogic>{}; - signalUsage.forEach((signal, signalUsageCount) { - // don't collapse if: - // - used more than once - // - inline modules for preferred names - if (signalUsageCount == 1 && signal.mergeable) { - singleUseSignals.add(signal); - } - }); - - final singleUsageInlineableSubmoduleInstantiations = - inlineableSubmoduleInstantiations.where((submoduleInstantiation) { - // inlineable modules have only 1 result signal - final resultSynthLogic = submoduleInstantiation.inlineResultLogic!; - - return singleUseSignals.contains(resultSynthLogic); - }); - - // remove any inlineability for those that want no expressions - for (final MapEntry(key: subModule, value: instantiation) - in moduleToSubModuleInstantiationMap.entries) { - if (subModule is SystemVerilog) { - singleUseSignals.removeAll(subModule.expressionlessInputs.map((e) => - instantiation.inputMapping[e] ?? instantiation.inOutMapping[e])); - } - // ignore: deprecated_member_use_from_same_package - else if (subModule is CustomSystemVerilog) { - singleUseSignals.removeAll(subModule.expressionlessInputs.map((e) => - instantiation.inputMapping[e] ?? instantiation.inOutMapping[e])); - } - } - - final synthLogicToInlineableSynthSubmoduleMap = - <_SynthLogic, _SynthSubModuleInstantiation>{}; - for (final submoduleInstantiation - in singleUsageInlineableSubmoduleInstantiations) { - (submoduleInstantiation.module as InlineSystemVerilog).resultSignalName; - - // inlineable modules have only 1 result signal - final resultSynthLogic = submoduleInstantiation.inlineResultLogic!; - - // clear declaration of intermediate signal replaced by inline - internalSignals.remove(resultSynthLogic); - - // clear declaration of instantiation for inline module - submoduleInstantiation.clearDeclaration(); - - synthLogicToInlineableSynthSubmoduleMap[resultSynthLogic] = - submoduleInstantiation; - } - - for (final subModuleInstantiation - in moduleToSubModuleInstantiationMap.values) { - subModuleInstantiation._synthLogicToInlineableSynthSubmoduleMap = - synthLogicToInlineableSynthSubmoduleMap; - } - } - - /// Finds all [InlineSystemVerilog] modules where all ports are [LogicNet]s - /// and which have not had their declarations cleared and replaces them with a - /// [_NetConnect] assignment instead of a normal assignment. - void _replaceInOutConnectionInlineableModules() { - for (final subModuleInstantiation - in moduleToSubModuleInstantiationMap.values.toList().where((e) => - e.module is InlineSystemVerilog && - e.needsDeclaration && - e.outputMapping.isEmpty && - e.inOutMapping.isNotEmpty)) { - // algorithm: - // - mark module as not needing declaration - // - add a net_connect - // - update the net_connect's inlineablesynthsubmodulemap - - subModuleInstantiation.clearDeclaration(); - - final resultName = (subModuleInstantiation.module as InlineSystemVerilog) - .resultSignalName; - - final subModResult = subModuleInstantiation.inOutMapping[resultName]!; - - // use a dummy as a placeholder, it will not really be used since we are - // updating the inlineable map - final dummy = - _SynthLogic(LogicNet(name: 'DUMMY', width: subModResult.width)); - - final netConnectSynthSubmod = _addNetConnect(subModResult, dummy) - .._synthLogicToInlineableSynthSubmoduleMap ??= {}; - - netConnectSynthSubmod._synthLogicToInlineableSynthSubmoduleMap![dummy] = - subModuleInstantiation; - } - } - - /// Merges bit blasted array assignments into one single assignment when - /// it's full array-full array assignment - void _collapseArrays() { - final boringArrayPairs = <(_SynthLogic, _SynthLogic)>[]; - - var prevAssignmentCount = 0; - while (prevAssignmentCount != assignments.length) { - final reducedAssignments = <_SynthAssignment>[]; - - final groupedAssignments = - <(_SynthLogic, _SynthLogic), List<_SynthAssignment>>{}; - - for (final assignment in assignments) { - final src = assignment.src; - final dst = assignment.dst; - - if (src is _SynthLogicArrayElement && dst is _SynthLogicArrayElement) { - final srcArray = src.parentArray; - final dstArray = dst.parentArray; - - assert(srcArray.logics.length == 1, 'should be 1 name for the array'); - assert(dstArray.logics.length == 1, 'should be 1 name for the array'); - - if (srcArray.logics.first.elements.length != - dstArray.logics.first.elements.length || - boringArrayPairs.contains((srcArray, dstArray))) { - reducedAssignments.add(assignment); - } else { - groupedAssignments[(srcArray, dstArray)] ??= []; - groupedAssignments[(srcArray, dstArray)]!.add(assignment); - } - } else { - reducedAssignments.add(assignment); - } - } - - for (final MapEntry(key: (srcArray, dstArray), value: arrAssignments) - in groupedAssignments.entries) { - assert( - srcArray.logics.first.elements.length == - dstArray.logics.first.elements.length, - 'should be equal lengths of elements in both arrays by now'); - - // first requirement is that all elements have been assigned - var shouldMerge = - arrAssignments.length == srcArray.logics.first.elements.length; - - if (shouldMerge) { - // only check each element if the lengths match - for (final arrAssignment in arrAssignments) { - final arrAssignmentSrc = - (arrAssignment.src as _SynthLogicArrayElement).logic; - final arrAssignmentDst = - (arrAssignment.dst as _SynthLogicArrayElement).logic; - - if (arrAssignmentSrc.arrayIndex! != arrAssignmentDst.arrayIndex!) { - shouldMerge = false; - break; - } - } - } - - if (shouldMerge) { - reducedAssignments.add(_SynthAssignment(srcArray, dstArray)); - } else { - reducedAssignments.addAll(arrAssignments); - boringArrayPairs.add((srcArray, dstArray)); - } - } - - prevAssignmentCount = assignments.length; - assignments - ..clear() - ..addAll(reducedAssignments); - } - } - - /// Collapses assignments that don't need to remain present. - void _collapseAssignments() { - // there might be more assign statements than necessary, so let's ditch them - var prevAssignmentCount = 0; - - while (prevAssignmentCount != assignments.length) { - // keep looping until it stops shrinking - final reducedAssignments = <_SynthAssignment>[]; - for (final assignment in assignments) { - final dst = assignment.dst; - final src = assignment.src; - - assert(dst != src, - 'No circular assignment allowed between $dst and $src.'); - - final mergedAway = _SynthLogic.tryMerge(dst, src); - - if (mergedAway != null) { - final kept = mergedAway == dst ? src : dst; - - final foundInternal = internalSignals.remove(mergedAway); - if (!foundInternal) { - final foundKept = internalSignals.remove(kept); - assert(foundKept, - 'One of the two should be internal since we cant merge ports.'); - - if (inputs.contains(mergedAway)) { - inputs - ..remove(mergedAway) - ..add(kept); - } else if (outputs.contains(mergedAway)) { - outputs - ..remove(mergedAway) - ..add(kept); - } else if (inOuts.contains(mergedAway)) { - inOuts - ..remove(mergedAway) - ..add(kept); - } - } - } else if (assignment.src.isFloatingConstant) { - internalSignals.remove(assignment.src); - } else { - reducedAssignments.add(assignment); - } - } - prevAssignmentCount = assignments.length; - assignments - ..clear() - ..addAll(reducedAssignments); - } - - // update the look-up table post-merge - logicToSynthMap.clear(); - for (final synthLogic in [ - ...inputs, - ...outputs, - ...inOuts, - ...internalSignals - ]) { - for (final logic in synthLogic.logics) { - logicToSynthMap[logic] = synthLogic; - } - } - } -} - -/// Represents an element of a [LogicArray]. -/// -/// Does not fully override or properly implement all characteristics of -/// [_SynthLogic], so this should be used cautiously. -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 { - final parentArrayname = parentArray.replacement?.name ?? parentArray.name; - final n = '$parentArrayname[${logic.arrayIndex!}]'; - assert( - Sanitizer.isSanitary( - n.substring(0, n.contains('[') ? n.indexOf('[') : null)), - 'Array name should be sanitary, but found $n', - ); - return n; - } - - /// The element of the [parentArray]. - final Logic logic; - - /// Creates an instance of an element of a [LogicArray]. - _SynthLogicArrayElement(this.logic, this.parentArray) - : assert(logic.isArrayMember, - 'Should only be used for elements in a LogicArray'), - super(logic); - - @override - String toString() => '${_name == null ? 'null' : '"$name"'},' - ' parentArray=($parentArray), element ${logic.arrayIndex}, logic: $logic' - ' logics contained: ${logics.map((e) => e.name).toList()}'; -} - -/// Represents a logic signal in the generated code within a module. -class _SynthLogic { - /// All [Logic]s represented, regardless of type. - List get logics => UnmodifiableListView([ - if (_reservedLogic != null) _reservedLogic!, - if (_constLogic != null) _constLogic!, - if (_renameableLogic != null) _renameableLogic!, - ..._mergeableLogics, - ..._unnamedLogics, - ]); - - /// If this was merged and is now replaced by another, then this is non-null - /// and points to it. - _SynthLogic? get replacement => _replacement?.replacement ?? _replacement; - set replacement(_SynthLogic? newReplacement) { - _replacement?.replacement = newReplacement; - _replacement = newReplacement; - } - - /// The width of any/all of the [logics]. - int get width => logics.first.width; - - _SynthLogic? _replacement; - - /// Indicates that this has a reserved name. - bool get isReserved => _reservedLogic != null; - - /// The [Logic] whose name is reserved, if there is one. - Logic? _reservedLogic; - - /// The [Logic] whose name is renameable, if there is one. - Logic? _renameableLogic; - - /// [Logic]s that are marked mergeable. - final Set _mergeableLogics = {}; - - /// [Logic]s that are unnamed. - final Set _unnamedLogics = {}; - - /// The [Logic] whose value represents a constant, if there is one. - Const? _constLogic; - - /// Assignments should be eliminated rather than assign to `z`, so this - /// indicates if this [_SynthLogic] is actually pointing to a [Const] that - /// is floating. - bool get isFloatingConstant => _constLogic?.value.isFloating ?? false; - - /// Whether this represents a constant. - bool get isConstant => _constLogic != null; - - /// Whether this represents a net. - bool get isNet => - // can just look at the first since nets and non-nets cannot be merged - logics.first.isNet || (isArray && (logics.first as LogicArray).isNet); - - /// If set, then this should never pick the constant as the name. - bool get constNameDisallowed => _constNameDisallowed; - bool _constNameDisallowed; - - /// Whether this signal should be declared. - bool get needsDeclaration => !(isConstant && !_constNameDisallowed); - - /// Two [_SynthLogic]s that are not [mergeable] cannot be merged with each - /// other. If onlyt one of them is not [mergeable], it can adopt the elements - /// from the other. - bool get mergeable => - _reservedLogic == null && _constLogic == null && _renameableLogic == null; - - /// True only if this represents a [LogicArray]. - final bool isArray; - - /// The chosen name of this. - /// - /// Must call [pickName] before this is accessible. - String get name { - assert(_replacement == null, - 'If this has been replaced, then we should not be getting its name.'); - assert(isConstant || Sanitizer.isSanitary(_name!), - 'Signal names should be sanitary, but found $_name.'); - - return _name!; - } - - String? _name; - - /// Picks a [name]. - /// - /// Must be called exactly once. - void pickName(Uniquifier uniquifier) { - assert(_name == null, 'Should only pick a name once.'); - - _name = _findName(uniquifier); - } - - /// Finds the best name from the collection of [Logic]s. - String _findName(Uniquifier uniquifier) { - // check for const - if (_constLogic != null) { - if (!_constNameDisallowed) { - return _constLogic!.value.toString(); - } else { - assert( - logics.length > 1, - 'If there is a consant, but the const name is not allowed, ' - 'there needs to be another option'); - } - } - - // check for reserved - if (_reservedLogic != null) { - return uniquifier.getUniqueName( - initialName: _reservedLogic!.name, reserved: true); - } - - // check for renameable - if (_renameableLogic != null) { - return uniquifier.getUniqueName(initialName: _renameableLogic!.name); - } - - // pick a preferred, available, mergeable name, if one exists - final unpreferredMergeableLogics = []; - final uniquifiableMergeableLogics = []; - for (final mergeableLogic in _mergeableLogics) { - if (Naming.isUnpreferred(mergeableLogic.name)) { - unpreferredMergeableLogics.add(mergeableLogic); - } else if (!uniquifier.isAvailable(mergeableLogic.name)) { - uniquifiableMergeableLogics.add(mergeableLogic); - } else { - return uniquifier.getUniqueName(initialName: mergeableLogic.name); - } - } - - // uniquify a preferred, mergeable name, if one exists - if (uniquifiableMergeableLogics.isNotEmpty) { - return uniquifier.getUniqueName( - initialName: uniquifiableMergeableLogics.first.name); - } - - // pick an available unpreferred mergeable name, if one exists, otherwise - // uniquify an unpreferred mergeable name - if (unpreferredMergeableLogics.isNotEmpty) { - return uniquifier.getUniqueName( - initialName: unpreferredMergeableLogics - .firstWhereOrNull( - (element) => uniquifier.isAvailable(element.name)) - ?.name ?? - unpreferredMergeableLogics.first.name); - } - - // pick anything (unnamed) and uniquify as necessary (considering preferred) - // no need to prefer an available one here, since it's all unnamed - return uniquifier.getUniqueName( - initialName: _unnamedLogics - .firstWhereOrNull( - (element) => !Naming.isUnpreferred(element.name)) - ?.name ?? - _unnamedLogics.first.name); - } - - /// Creates an instance to represent [initialLogic] and any that merge - /// into it. - _SynthLogic(Logic initialLogic, - {Naming? namingOverride, bool constNameDisallowed = false}) - : isArray = initialLogic is LogicArray, - _constNameDisallowed = constNameDisallowed { - _addLogic(initialLogic, namingOverride: namingOverride); - } - - /// Returns the [_SynthLogic] that should be *removed*. - static _SynthLogic? tryMerge(_SynthLogic a, _SynthLogic b) { - if (_constantsMergeable(a, b)) { - // case to avoid things like a constant assigned to another constant - a.adopt(b); - return b; - } - - if (!a.mergeable && !b.mergeable) { - return null; - } - - if (a.isNet != b.isNet) { - // do not merge nets with non-nets - return null; - } - - if (b.mergeable) { - a.adopt(b); - return b; - } else { - b.adopt(a); - return a; - } - } - - /// Indicates whether two constants can be merged. - static bool _constantsMergeable(_SynthLogic a, _SynthLogic b) => - a.isConstant && - b.isConstant && - a._constLogic!.value == b._constLogic!.value && - !a._constNameDisallowed && - !b._constNameDisallowed; - - /// Merges [other] to be represented by `this` instead, and updates the - /// [other] that it has been replaced. - void adopt(_SynthLogic other) { - assert(other.mergeable || _constantsMergeable(this, other), - 'Cannot merge a non-mergeable into this.'); - assert(other.isArray == isArray, 'Cannot merge arrays and non-arrays'); - - _constNameDisallowed |= other._constNameDisallowed; - - // only take one of the other's items if we don't have it already - _constLogic ??= other._constLogic; - _reservedLogic ??= other._reservedLogic; - _renameableLogic ??= other._renameableLogic; - - // the rest, take them all - _mergeableLogics.addAll(other._mergeableLogics); - _unnamedLogics.addAll(other._unnamedLogics); - - // keep track that it was replaced by this - other.replacement = this; - } - - /// Adds a new [logic] to be represented by this. - void _addLogic(Logic logic, {Naming? namingOverride}) { - final naming = namingOverride ?? logic.naming; - if (logic is Const) { - _constLogic = logic; - } else { - switch (naming) { - case Naming.reserved: - _reservedLogic = logic; - case Naming.renameable: - _renameableLogic = logic; - case Naming.mergeable: - _mergeableLogics.add(logic); - case Naming.unnamed: - _unnamedLogics.add(logic); - } - } - } - - @override - String toString() => '${_name == null ? 'null' : '"$name"'}, ' - 'logics contained: ${logics.map((e) => e.name).toList()}'; - - /// Provides a definition for a range in SV from a width. - static String _widthToRangeDef(int width, {bool forceRange = false}) { - if (width > 1 || forceRange) { - return '[${width - 1}:0]'; - } else { - return ''; - } - } - - String definitionType() => isNet ? 'wire' : 'logic'; - - /// Computes the name of the signal at declaration time with appropriate - /// dimensions included. - String definitionName() { - String packedDims; - String unpackedDims; - - // we only use this for dimensions, so first is fine - final logic = logics.first; - - if (isArray) { - 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 { - packedDims = _widthToRangeDef(logic.width); - unpackedDims = ''; - } - - return [packedDims, name, unpackedDims] - .where((e) => e.isNotEmpty) - .join(' '); - } -} - -/// Represents an assignment between two signals. -class _SynthAssignment { - _SynthLogic _dst; - - /// The destination being driven by this assignment. - /// - /// Ensures it's always using the most up-to-date version. - _SynthLogic get dst { - if (_dst.replacement != null) { - _dst = _dst.replacement!; - assert(_dst.replacement == null, 'should not be a chain...'); - } - return _dst; - } - - _SynthLogic _src; - - /// The source driving in this assignment. - /// - /// Ensures it's always using the most up-to-date version. - _SynthLogic get src { - if (_src.replacement != null) { - _src = _src.replacement!; - assert(_src.replacement == null, 'should not be a chain...'); - } - return _src; - } - - /// Constructs a representation of an assignment. - _SynthAssignment(this._src, this._dst); - - @override - String toString() => '$dst <= $src'; -} diff --git a/lib/src/synthesizers/systemverilog/systemverilog.dart b/lib/src/synthesizers/systemverilog/systemverilog.dart new file mode 100644 index 000000000..b4f1b167f --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog.dart @@ -0,0 +1,11 @@ +// Copyright (C) 2021-2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog.dart +// Definition for SystemVerilog Synthesizer +// +// 2021 August 26 +// Author: Max Korbel + +export 'systemverilog_mixins.dart'; +export 'systemverilog_synthesizer.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart b/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart new file mode 100644 index 000000000..e29be5b1f --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart @@ -0,0 +1,182 @@ +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; + +/// Represents the definition of a SystemVerilog parameter at the time of +/// declaration of a module definition. +class SystemVerilogParameterDefinition { + /// The SystemVerilog type to use for declaring this parameter. + final String type; + + /// The default value for this parameter. + final String defaultValue; + + /// The name of the parameter. + final String name; + + /// Creates a new SystemVerilog parameter definition with [name] of the + /// provided [type] with the [defaultValue]. + const SystemVerilogParameterDefinition(this.name, + {required this.type, required this.defaultValue}); +} + +/// Allows a [Module] to control the instantiation and/or definition of +/// generated SystemVerilog for that module. +mixin SystemVerilog on Module { + /// Generates custom SystemVerilog to be injected in place of a `module` + /// instantiation. + /// + /// The [instanceType] and [instanceName] represent the type and name, + /// respectively of the module that would have been instantiated had it not + /// been overridden. [ports] is a mapping from the [Module]'s port names to + /// the names of the signals that are passed into those ports in the generated + /// SystemVerilog. + /// + /// If a standard instantiation is desired, either return `null` or use + /// [SystemVerilogSynthesizer.instantiationVerilogFor] with + /// `forceStandardInstantiation` set to `true`. By default, `null` is + /// returned and thus a standard instantiation is used. + String? instantiationVerilog( + String instanceType, + String instanceName, + Map ports, + ) => + null; + + /// A list of names of [input]s which should not have any SystemVerilog + /// expressions (including constants) in-lined into them. Only signal names + /// will be fed into these. + final List expressionlessInputs = const []; + + /// A custom SystemVerilog definition to be produced for this [Module]. + /// + /// If an empty string is returned (the default behavior), then no definition + /// will be generated. + /// + /// If `null` is returned, then a default definition will be generated. + /// + /// This function should have no side effects and always return the same thing + /// for the same inputs. + String? definitionVerilog(String definitionType) => ''; + + /// A collection of SystemVerilog [SystemVerilogParameterDefinition]s to be + /// declared on the definition when generating SystemVerilog for this [Module] + /// if [generatedDefinitionType] is [DefinitionGenerationType.standard]. + /// + /// If `null` is returned (the default), then no parameters will be generated. + /// Otherwise, this function should have no side effects and always return the + /// same thing for the same inputs. + List? get definitionParameters => null; + + /// What kind of SystemVerilog definition this [Module] generates, or whether + /// it does at all. + /// + /// By default, this is automatically calculated based on the return value of + /// [definitionVerilog]. + DefinitionGenerationType get generatedDefinitionType { + final def = definitionVerilog('*PLACEHOLDER*'); + if (def == null) { + return DefinitionGenerationType.standard; + } else if (def.isNotEmpty) { + return DefinitionGenerationType.custom; + } else { + return DefinitionGenerationType.none; + } + } +} + +/// A type of generation for generated outputs. +enum DefinitionGenerationType { + /// No definition will be generated. + none, + + /// A standard definition will be generated. + standard, + + /// A custom definition will be generated. + custom, +} + +/// Allows a [Module] to define a special type of [SystemVerilog] which can be +/// inlined within other SystemVerilog code. +/// +/// The inline SystemVerilog will get parentheses wrapped around it and then +/// dropped into other code in the same way a variable name is. +mixin InlineSystemVerilog on Module implements SystemVerilog { + /// Generates custom SystemVerilog to be injected in place of the output + /// port's corresponding signal name. + /// + /// The [inputs] are a mapping from the [Module]'s port names to the names of + /// the signals that are passed into those ports in the generated + /// SystemVerilog. It will only contain [input]s and [inOut]s, as there should + /// only be one [output] (named [resultSignalName]) which is driven by the + /// expression. + /// + /// The output will be appropriately wrapped with parentheses to guarantee + /// proper order of operations. + String inlineVerilog(Map inputs); + + /// The name of the [output] (or [inOut]) port which can be the in-lined + /// symbol. + /// + /// By default, this assumes one [output] port. This should be overridden in + /// classes which have an [inOut] port as the in-lined symbol. + String get resultSignalName { + if (outputs.keys.length != 1) { + throw Exception('Inline verilog expected to have exactly one output,' + ' but saw $outputs.'); + } + + return outputs.keys.first; + } + + @override + String instantiationVerilog( + String instanceType, + String instanceName, + Map ports, + ) { + final result = ports[resultSignalName]; + final inputPorts = Map.fromEntries( + ports.entries.where((element) => + inputs.containsKey(element.key) || + (inOuts.containsKey(element.key) && element.key != resultSignalName)), + ); + final inline = inlineVerilog(inputPorts); + return 'assign $result = $inline; // $instanceName'; + } + + @override + @protected + final List expressionlessInputs = const []; + + @override + String? definitionVerilog(String definitionType) => ''; + + @override + DefinitionGenerationType get generatedDefinitionType => + DefinitionGenerationType.none; + + @override + List? get definitionParameters => null; +} + +/// Allows a [Module] to define a custom implementation of SystemVerilog to be +/// injected in generated output instead of instantiating a separate `module`. +@Deprecated('Use `SystemVerilog` instead') +mixin CustomSystemVerilog on Module { + /// Generates custom SystemVerilog to be injected in place of a `module` + /// instantiation. + /// + /// The [instanceType] and [instanceName] represent the type and name, + /// respectively of the module that would have been instantiated had it not + /// been overridden. The [Map]s [inputs] and [outputs] are a mapping from the + /// [Module]'s port names to the names of the signals that are passed into + /// those ports in the generated SystemVerilog. + String instantiationVerilog(String instanceType, String instanceName, + Map inputs, Map outputs); + + /// A list of names of [input]s which should not have any SystemVerilog + /// expressions (including constants) in-lined into them. Only signal names + /// will be fed into these. + final List expressionlessInputs = const []; +} diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart new file mode 100644 index 000000000..5fae44549 --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart @@ -0,0 +1,267 @@ +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; + +class SystemVerilogSynthModuleDefinition extends SynthModuleDefinition { + SystemVerilogSynthModuleDefinition(super.module); + + //TODO + @override + void process() { + _replaceNetConnections(); + _collapseChainableModules(); + _replaceInOutConnectionInlineableModules(); + } + + @override + SynthSubModuleInstantiation createSubModuleInstantiation(Module m) => + SystemVerilogSynthSubModuleInstantiation(m); + + /// Creates a new [_NetConnect] module to synthesize assignment between two + /// [LogicNet]s. + SystemVerilogSynthSubModuleInstantiation _addNetConnect( + SynthLogic dst, SynthLogic src) { + // make an (unconnected) module representing the assignment + final netConnect = + _NetConnect(LogicNet(width: dst.width), LogicNet(width: src.width)); + + // instantiate the module within the definition + final netConnectSynthSubModInst = + (getSynthSubModuleInstantiation(netConnect) + as SystemVerilogSynthSubModuleInstantiation) + + // map inouts to the appropriate `_SynthLogic`s + ..setInOutMapping(_NetConnect.n0Name, dst) + ..setInOutMapping(_NetConnect.n1Name, src); + + // notify the `SynthBuilder` that it needs declaration + supportingModules.add(netConnect); + + return netConnectSynthSubModInst; + } + + /// Replace all [assignments] between two [LogicNet]s with a [_NetConnect]. + void _replaceNetConnections() { + final reducedAssignments = []; + + for (final assignment in assignments) { + if (assignment.src.isNet && assignment.dst.isNet) { + _addNetConnect(assignment.dst, assignment.src); + } else { + reducedAssignments.add(assignment); + } + } + + // only swap them if we actually did anything + if (assignments.length != reducedAssignments.length) { + assignments + ..clear() + ..addAll(reducedAssignments); + } + } + + /// Collapses chainable, inlineable modules. + void _collapseChainableModules() { + // collapse multiple lines of in-line assignments into one where they are + // unnamed one-liners + // for example, be capable of creating lines like: + // assign x = a & b & c & _d_and_e + // assign _d_and_e = d & e + // assign y = _d_and_e + + // Also feed collapsed chained modules into other modules + // Need to consider order of operations in systemverilog or else add () + // everywhere! (for now add the parentheses) + + // Algorithm: + // - find submodule instantiations that are inlineable + // - filter to those who only output as input to one other module + // - pass an override to the submodule instantiation that the corresponding + // input should map to the output of another submodule instantiation + // do not collapse if signal feeds to multiple inputs of other modules + + final inlineableSubmoduleInstantiations = module.subModules + .whereType() + .map((m) => getSynthSubModuleInstantiation(m) + as SystemVerilogSynthSubModuleInstantiation); + + // number of times each signal name is used by any module + final signalUsage = {}; + + for (final subModuleInstantiation + in moduleToSubModuleInstantiationMap.values) { + for (final inSynthLogic in [ + ...subModuleInstantiation.inputMapping.values, + ...subModuleInstantiation.inOutMapping.values + ]) { + if (inputs.contains(inSynthLogic) || inOuts.contains(inSynthLogic)) { + // dont worry about inputs to THIS module + continue; + } + + subModuleInstantiation as SystemVerilogSynthSubModuleInstantiation; + + if (subModuleInstantiation.inlineResultLogic == inSynthLogic) { + // don't worry about the result signal + continue; + } + + signalUsage.update( + inSynthLogic, + (value) => value + 1, + ifAbsent: () => 1, + ); + } + } + + final singleUseSignals = {}; + signalUsage.forEach((signal, signalUsageCount) { + // don't collapse if: + // - used more than once + // - inline modules for preferred names + if (signalUsageCount == 1 && signal.mergeable) { + singleUseSignals.add(signal); + } + }); + + final singleUsageInlineableSubmoduleInstantiations = + inlineableSubmoduleInstantiations.where((subModuleInstantiation) { + // inlineable modules have only 1 result signal + final resultSynthLogic = subModuleInstantiation.inlineResultLogic!; + + return singleUseSignals.contains(resultSynthLogic); + }); + + // remove any inlineability for those that want no expressions + for (final MapEntry(key: subModule, value: instantiation) + in moduleToSubModuleInstantiationMap.entries) { + if (subModule is SystemVerilog) { + singleUseSignals.removeAll(subModule.expressionlessInputs.map((e) => + instantiation.inputMapping[e] ?? instantiation.inOutMapping[e])); + } + // ignore: deprecated_member_use_from_same_package + else if (subModule is CustomSystemVerilog) { + singleUseSignals.removeAll(subModule.expressionlessInputs.map((e) => + instantiation.inputMapping[e] ?? instantiation.inOutMapping[e])); + } + } + + final synthLogicToInlineableSynthSubmoduleMap = + {}; + for (final subModuleInstantiation + in singleUsageInlineableSubmoduleInstantiations) { + (subModuleInstantiation.module as InlineSystemVerilog).resultSignalName; + + // inlineable modules have only 1 result signal + final resultSynthLogic = subModuleInstantiation.inlineResultLogic!; + + // clear declaration of intermediate signal replaced by inline + internalSignals.remove(resultSynthLogic); + + // clear declaration of instantiation for inline module + subModuleInstantiation.clearDeclaration(); + + synthLogicToInlineableSynthSubmoduleMap[resultSynthLogic] = + subModuleInstantiation; + } + + for (final subModuleInstantiation + in moduleToSubModuleInstantiationMap.values) { + subModuleInstantiation as SystemVerilogSynthSubModuleInstantiation; + + subModuleInstantiation.synthLogicToInlineableSynthSubmoduleMap = + synthLogicToInlineableSynthSubmoduleMap; + } + } + + /// Finds all [InlineSystemVerilog] modules where all ports are [LogicNet]s + /// and which have not had their declarations cleared and replaces them with a + /// [_NetConnect] assignment instead of a normal assignment. + void _replaceInOutConnectionInlineableModules() { + for (final subModuleInstantiation + in moduleToSubModuleInstantiationMap.values.toList().where((e) => + e.module is InlineSystemVerilog && + e.needsDeclaration && + e.outputMapping.isEmpty && + e.inOutMapping.isNotEmpty)) { + // algorithm: + // - mark module as not needing declaration + // - add a net_connect + // - update the net_connect's inlineablesynthsubmodulemap + + subModuleInstantiation as SystemVerilogSynthSubModuleInstantiation; + + subModuleInstantiation.clearDeclaration(); + + final resultName = (subModuleInstantiation.module as InlineSystemVerilog) + .resultSignalName; + + final subModResult = subModuleInstantiation.inOutMapping[resultName]!; + + // use a dummy as a placeholder, it will not really be used since we are + // updating the inlineable map + final dummy = + SynthLogic(LogicNet(name: 'DUMMY', width: subModResult.width)); + + final netConnectSynthSubmod = _addNetConnect(subModResult, dummy) + ..synthLogicToInlineableSynthSubmoduleMap ??= {}; + + netConnectSynthSubmod.synthLogicToInlineableSynthSubmoduleMap![dummy] = + subModuleInstantiation; + } + } +} + +/// A special [Module] for connecting or assigning two SystemVerilog nets +/// together bidirectionally. +/// +/// The `alias` keyword in SystemVerilog could alternatively work, but many +/// tools do not support it, so this `module` definition is a convenient trick +/// to accomplish the same thing in a tool-compatible way. +class _NetConnect extends Module with SystemVerilog { + static const String _definitionName = 'net_connect'; + + /// The width of the nets on this instance. + final int width; + + @override + bool get hasBuilt => + // we force it to say it has built since it is being generated post-build + true; + + /// The name of net 0. + static final String n0Name = Naming.unpreferredName('n0'); + + /// The name of net 1. + static final String n1Name = Naming.unpreferredName('n1'); + + _NetConnect(LogicNet n0, LogicNet n1) + : assert(n0.width == n1.width, 'Widths must be equal.'), + width = n0.width, + super( + definitionName: _definitionName, + name: _definitionName, + ) { + n0 = addInOut(n0Name, n0, width: width); + n1 = addInOut(n1Name, n1, width: width); + } + + @override + String instantiationVerilog( + String instanceType, String instanceName, Map ports) { + assert(instanceType == _definitionName, + 'Instance type selected should match the definition name.'); + return '$instanceType' + ' #(.WIDTH($width))' + ' $instanceName' + ' (${ports[n0Name]}, ${ports[n1Name]});'; + } + + @override + String? definitionVerilog(String definitionType) => ''' +// A special module for connecting two nets bidirectionally +module $definitionType #(parameter WIDTH=1) (w, w); +inout wire[WIDTH-1:0] w; +endmodule'''; +} diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart new file mode 100644 index 000000000..56b9e041d --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart @@ -0,0 +1,58 @@ +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; + +class SystemVerilogSynthSubModuleInstantiation + extends SynthSubModuleInstantiation { + /// If [module] is [InlineSystemVerilog], this will be the [SynthLogic] that + /// is the `result` of that module. Otherwise, `null`. + SynthLogic? get inlineResultLogic => module is! InlineSystemVerilog + ? null + : (outputMapping[(module as InlineSystemVerilog).resultSignalName] ?? + inOutMapping[(module as InlineSystemVerilog).resultSignalName]); + + SystemVerilogSynthSubModuleInstantiation(super.module); + + /// Mapping from [SynthLogic]s which are outputs of inlineable SV to those + /// inlineable modules. + Map? + synthLogicToInlineableSynthSubmoduleMap; + + /// Provides a mapping from ports of this module to a string that can be fed + /// into that port, which may include inline SV modules as well. + Map _modulePortsMapWithInline( + Map plainPorts) => + plainPorts.map((name, synthLogic) => MapEntry( + name, + synthLogicToInlineableSynthSubmoduleMap?[synthLogic] + ?.inlineVerilog() ?? + synthLogic.name)); + + /// Provides the inline SV representation for this module. + /// + /// Should only be called if [module] is [InlineSystemVerilog]. + String inlineVerilog() { + final inlineSvRepresentation = + (module as InlineSystemVerilog).inlineVerilog( + _modulePortsMapWithInline({...inputMapping, ...inOutMapping} + ..remove((module as InlineSystemVerilog).resultSignalName)), + ); + + return '($inlineSvRepresentation)'; + } + + /// Provides the full SV instantiation for this module. + String? instantiationVerilog(String instanceType) { + if (!needsDeclaration) { + return null; + } + return SystemVerilogSynthesizer.instantiationVerilogFor( + module: module, + instanceType: instanceType, + instanceName: name, + ports: _modulePortsMapWithInline({ + ...inputMapping, + ...outputMapping, + ...inOutMapping, + })); + } +} diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart new file mode 100644 index 000000000..3bf58fbf8 --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -0,0 +1,198 @@ +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; + +/// A [SynthesisResult] representing a [Module] that provides a custom +/// SystemVerilog definition. +class SystemVerilogCustomDefinitionSynthesisResult extends SynthesisResult { + SystemVerilogCustomDefinitionSynthesisResult( + super.module, super.getInstanceTypeOfModule) + : assert( + module is SystemVerilog && + module.generatedDefinitionType == + DefinitionGenerationType.custom, + 'This should only be used for custom system verilog definitions.'); + + @override + int get matchHashCode => + (module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!.hashCode; + + @override + bool matchesImplementation(SynthesisResult other) => + other is SystemVerilogCustomDefinitionSynthesisResult && + (module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')! == + (other.module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!; + + @override + String toFileContents() => (module as SystemVerilog) + .definitionVerilog(getInstanceTypeOfModule(module))!; +} + +/// A [SynthesisResult] representing a conversion of a [Module] to +/// SystemVerilog. +class SystemVerilogSynthesisResult extends SynthesisResult { + /// A cached copy of the generated ports. + late final String _portsString; + + /// A cached copy of the generated contents of the module. + late final String _moduleContentsString; + + /// A cached copy of the generated parameters. + late final String? _parameterString; + + /// The main [SynthModuleDefinition] for this. + final SynthModuleDefinition _synthModuleDefinition; + + @override + List get supportingModules => + _synthModuleDefinition.supportingModules; + + SystemVerilogSynthesisResult(super.module, super.getInstanceTypeOfModule) + : _synthModuleDefinition = SystemVerilogSynthModuleDefinition(module) { + _portsString = _verilogPorts(); + _moduleContentsString = _verilogModuleContents(getInstanceTypeOfModule); + _parameterString = _verilogParameters(module); + } + + @override + bool matchesImplementation(SynthesisResult other) => + other is SystemVerilogSynthesisResult && + other._portsString == _portsString && + other._parameterString == _parameterString && + other._moduleContentsString == _moduleContentsString; + + @override + int get matchHashCode => + _portsString.hashCode ^ + _moduleContentsString.hashCode ^ + _parameterString.hashCode; + + @override + String toFileContents() => _toVerilog(getInstanceTypeOfModule); + + /// Representation of all input port declarations in generated SV. + List _verilogInputs() { + final declarations = _synthModuleDefinition.inputs + .map((sig) => 'input ${sig.definitionType()} ${sig.definitionName()}') + .toList(growable: false); + return declarations; + } + + /// Representation of all output port declarations in generated SV. + List _verilogOutputs() { + final declarations = _synthModuleDefinition.outputs + .map((sig) => 'output ${sig.definitionType()} ${sig.definitionName()}') + .toList(growable: false); + return declarations; + } + + /// Representation of all inout port declarations in generated SV. + List _verilogInOuts() { + final declarations = _synthModuleDefinition.inOuts + .map((sig) => 'inout ${sig.definitionType()} ${sig.definitionName()}') + .toList(growable: false); + return declarations; + } + + /// Representation of all internal net declarations in generated SV. + String _verilogInternalSignals() { + final declarations = []; + for (final sig in _synthModuleDefinition.internalSignals + .sorted((a, b) => a.name.compareTo(b.name))) { + if (sig.needsDeclaration) { + declarations.add('${sig.definitionType()} ${sig.definitionName()};'); + } + } + return declarations.join('\n'); + } + + /// Representation of all assignments in generated SV. + String _verilogAssignments() { + final assignmentLines = []; + for (final assignment in _synthModuleDefinition.assignments) { + assert( + !(assignment.src.isNet && assignment.dst.isNet), + 'Net connections should have been implemented as' + ' bidirectional net connections.'); + + assignmentLines + .add('assign ${assignment.dst.name} = ${assignment.src.name};'); + } + return assignmentLines.join('\n'); + } + + /// Representation of all sub-module instantiations in generated SV. + String _verilogSubModuleInstantiations( + String Function(Module module) getInstanceTypeOfModule) { + final subModuleLines = []; + for (final subModuleInstantiation + in _synthModuleDefinition.moduleToSubModuleInstantiationMap.values) { + final instanceType = + getInstanceTypeOfModule(subModuleInstantiation.module); + + subModuleInstantiation as SystemVerilogSynthSubModuleInstantiation; + + final instantiationVerilog = + subModuleInstantiation.instantiationVerilog(instanceType); + if (instantiationVerilog != null) { + subModuleLines.add(instantiationVerilog); + } + } + return subModuleLines.join('\n'); + } + + /// The contents of this module converted to SystemVerilog without module + /// declaration, ports, etc. + String _verilogModuleContents( + String Function(Module module) getInstanceTypeOfModule) => + [ + _verilogInternalSignals(), + _verilogAssignments(), // order matters! + _verilogSubModuleInstantiations(getInstanceTypeOfModule), + ].where((element) => element.isNotEmpty).join('\n'); + + /// The representation of all port declarations. + String _verilogPorts() => [ + ..._verilogInputs(), + ..._verilogOutputs(), + ..._verilogInOuts(), + ].join(',\n'); + + String? _verilogParameters(Module module) { + if (module is SystemVerilog) { + final defParams = module.definitionParameters; + if (defParams == null || defParams.isEmpty) { + return null; + } + + return [ + '#(', + defParams + .map((p) => 'parameter ${p.type} ${p.name} = ${p.defaultValue}') + .join(',\n'), + ')', + ].join('\n'); + } + + return null; + } + + /// The full SV representation of this module. + String _toVerilog(String Function(Module module) getInstanceTypeOfModule) { + final verilogModuleName = getInstanceTypeOfModule(module); + return [ + [ + 'module $verilogModuleName', + _parameterString, + '(', + ].nonNulls.join(' '), + _portsString, + ');', + _moduleContentsString, + 'endmodule : $verilogModuleName' + ].join('\n'); + } +} diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart new file mode 100644 index 000000000..68fc200d7 --- /dev/null +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart @@ -0,0 +1,144 @@ +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; +import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart'; + +/// A [Synthesizer] which generates equivalent SystemVerilog as the +/// given [Module]. +/// +/// Attempts to maintain signal naming and structure as much as possible. +class SystemVerilogSynthesizer extends Synthesizer { + @override + bool generatesDefinition(Module module) => + // ignore: deprecated_member_use_from_same_package + !((module is CustomSystemVerilog) || + (module is SystemVerilog && + module.generatedDefinitionType == DefinitionGenerationType.none)); + + /// Creates a line of SystemVerilog that instantiates [module]. + /// + /// The instantiation will create it as type [instanceType] and name + /// [instanceName]. + /// + /// [ports] maps [module] input/output/inout names to a verilog signal name. + /// + /// For example: + /// To generate this SystemVerilog: `sig_c = sig_a & sig_b` + /// Based on this module definition: `c <= a & b` + /// The values for [ports] should be: + /// ports: `{ 'a' : 'sig_a', 'b' : 'sig_b', 'c' : 'sig_c'}` + /// + /// If [forceStandardInstantiation] is set, then the standard instantiation + /// for SystemVerilog modules will be used. + /// + /// If [parameters] is provided, then the module will be instantiated with + /// all of the keys as parameter names set to the corresponding values + /// provided. + static String instantiationVerilogFor( + {required Module module, + required String instanceType, + required String instanceName, + required Map ports, + Map? parameters, + bool forceStandardInstantiation = false}) { + if (!forceStandardInstantiation) { + if (module is SystemVerilog) { + return module.instantiationVerilog( + instanceType, + instanceName, + ports, + ) ?? + instantiationVerilogFor( + module: module, + instanceType: instanceType, + instanceName: instanceName, + ports: ports, + forceStandardInstantiation: true); + } + // ignore: deprecated_member_use_from_same_package + else if (module is CustomSystemVerilog) { + return module.instantiationVerilog( + instanceType, + instanceName, + Map.fromEntries(ports.entries + .where((element) => module.inputs.containsKey(element.key))), + Map.fromEntries(ports.entries + .where((element) => module.outputs.containsKey(element.key))), + ); + } + } + + //non-custom needs more details + final connections = []; + + for (final signalName in module.inputs.keys) { + connections.add('.$signalName(${ports[signalName]!})'); + } + + for (final signalName in module.outputs.keys) { + connections.add('.$signalName(${ports[signalName]!})'); + } + + for (final signalName in module.inOuts.keys) { + connections.add('.$signalName(${ports[signalName]!})'); + } + + final connectionsStr = connections.join(','); + + var parameterString = ''; + if (parameters != null && parameters.isNotEmpty) { + final parameterContents = + parameters.entries.map((e) => '.${e.key}(${e.value})').join(','); + parameterString = '#($parameterContents)'; + } + + return '$instanceType $parameterString $instanceName($connectionsStr);'; + } + + /// Creates a line of SystemVerilog that instantiates [module]. + /// + /// The instantiation will create it as type [instanceType] and name + /// [instanceName]. + /// + /// [inputs] and [outputs] map `module` input/output name to a verilog signal + /// name. + /// + /// For example: + /// To generate this SystemVerilog: `sig_c = sig_a & sig_b` + /// Based on this module definition: `c <= a & b` + /// The values for [inputs] and [outputs] should be: + /// inputs: `{ 'a' : 'sig_a', 'b' : 'sig_b'}` + /// outputs: `{ 'c' : 'sig_c' }` + @Deprecated('Use `instantiationVerilogFor` instead.') + static String instantiationVerilogWithParameters( + Module module, + String instanceType, + String instanceName, + Map inputs, + Map outputs, + {Map inOuts = const {}, + Map? parameters, + bool forceStandardInstantiation = false}) => + instantiationVerilogFor( + module: module, + instanceType: instanceType, + instanceName: instanceName, + ports: {...inputs, ...outputs, ...inOuts}, + parameters: parameters, + forceStandardInstantiation: forceStandardInstantiation, + ); + + @override + SynthesisResult synthesize( + Module module, String Function(Module module) getInstanceTypeOfModule) { + assert( + module is! SystemVerilog || + module.generatedDefinitionType != DefinitionGenerationType.none, + 'SystemVerilog modules synthesized must generate a definition.'); + + return module is SystemVerilog && + module.generatedDefinitionType == DefinitionGenerationType.custom + ? SystemVerilogCustomDefinitionSynthesisResult( + module, getInstanceTypeOfModule) + : SystemVerilogSynthesisResult(module, getInstanceTypeOfModule); + } +} diff --git a/lib/src/synthesizers/utilities/synth_assignment.dart b/lib/src/synthesizers/utilities/synth_assignment.dart new file mode 100644 index 000000000..1f5cb543d --- /dev/null +++ b/lib/src/synthesizers/utilities/synth_assignment.dart @@ -0,0 +1,36 @@ +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; + +/// Represents an assignment between two signals. +class SynthAssignment { + SynthLogic _dst; + + /// The destination being driven by this assignment. + /// + /// Ensures it's always using the most up-to-date version. + SynthLogic get dst { + if (_dst.replacement != null) { + _dst = _dst.replacement!; + assert(_dst.replacement == null, 'should not be a chain...'); + } + return _dst; + } + + SynthLogic _src; + + /// The source driving in this assignment. + /// + /// Ensures it's always using the most up-to-date version. + SynthLogic get src { + if (_src.replacement != null) { + _src = _src.replacement!; + assert(_src.replacement == null, 'should not be a chain...'); + } + return _src; + } + + /// Constructs a representation of an assignment. + SynthAssignment(this._src, this._dst); + + @override + String toString() => '$dst <= $src'; +} diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart new file mode 100644 index 000000000..366799aa3 --- /dev/null +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -0,0 +1,343 @@ +import 'dart:collection'; + +import 'package:collection/collection.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/utilities/sanitizer.dart'; +import 'package:rohd/src/utilities/uniquifier.dart'; + +/// Represents a logic signal in the generated code within a module. +class SynthLogic { + /// All [Logic]s represented, regardless of type. + List get logics => UnmodifiableListView([ + if (_reservedLogic != null) _reservedLogic!, + if (_constLogic != null) _constLogic!, + if (_renameableLogic != null) _renameableLogic!, + ..._mergeableLogics, + ..._unnamedLogics, + ]); + + /// If this was merged and is now replaced by another, then this is non-null + /// and points to it. + SynthLogic? get replacement => _replacement?.replacement ?? _replacement; + set replacement(SynthLogic? newReplacement) { + _replacement?.replacement = newReplacement; + _replacement = newReplacement; + } + + /// The width of any/all of the [logics]. + int get width => logics.first.width; + + SynthLogic? _replacement; + + /// Indicates that this has a reserved name. + bool get isReserved => _reservedLogic != null; + + /// The [Logic] whose name is reserved, if there is one. + Logic? _reservedLogic; + + /// The [Logic] whose name is renameable, if there is one. + Logic? _renameableLogic; + + /// [Logic]s that are marked mergeable. + final Set _mergeableLogics = {}; + + /// [Logic]s that are unnamed. + final Set _unnamedLogics = {}; + + /// The [Logic] whose value represents a constant, if there is one. + Const? _constLogic; + + /// Assignments should be eliminated rather than assign to `z`, so this + /// indicates if this [SynthLogic] is actually pointing to a [Const] that + /// is floating. + bool get isFloatingConstant => _constLogic?.value.isFloating ?? false; + + /// Whether this represents a constant. + bool get isConstant => _constLogic != null; + + /// Whether this represents a net. + bool get isNet => + // can just look at the first since nets and non-nets cannot be merged + logics.first.isNet || (isArray && (logics.first as LogicArray).isNet); + + /// If set, then this should never pick the constant as the name. + bool get constNameDisallowed => _constNameDisallowed; + bool _constNameDisallowed; + + /// Whether this signal should be declared. + bool get needsDeclaration => !(isConstant && !_constNameDisallowed); + + /// Two [SynthLogic]s that are not [mergeable] cannot be merged with each + /// other. If onlyt one of them is not [mergeable], it can adopt the elements + /// from the other. + bool get mergeable => + _reservedLogic == null && _constLogic == null && _renameableLogic == null; + + /// True only if this represents a [LogicArray]. + final bool isArray; + + /// The chosen name of this. + /// + /// Must call [pickName] before this is accessible. + String get name { + assert(_replacement == null, + 'If this has been replaced, then we should not be getting its name.'); + assert(isConstant || Sanitizer.isSanitary(_name!), + 'Signal names should be sanitary, but found $_name.'); + + return _name!; + } + + String? _name; + + /// Picks a [name]. + /// + /// Must be called exactly once. + void pickName(Uniquifier uniquifier) { + assert(_name == null, 'Should only pick a name once.'); + + _name = _findName(uniquifier); + } + + /// Finds the best name from the collection of [Logic]s. + String _findName(Uniquifier uniquifier) { + // check for const + if (_constLogic != null) { + if (!_constNameDisallowed) { + return _constLogic!.value.toString(); + } else { + assert( + logics.length > 1, + 'If there is a consant, but the const name is not allowed, ' + 'there needs to be another option'); + } + } + + // check for reserved + if (_reservedLogic != null) { + return uniquifier.getUniqueName( + initialName: _reservedLogic!.name, reserved: true); + } + + // check for renameable + if (_renameableLogic != null) { + return uniquifier.getUniqueName(initialName: _renameableLogic!.name); + } + + // pick a preferred, available, mergeable name, if one exists + final unpreferredMergeableLogics = []; + final uniquifiableMergeableLogics = []; + for (final mergeableLogic in _mergeableLogics) { + if (Naming.isUnpreferred(mergeableLogic.name)) { + unpreferredMergeableLogics.add(mergeableLogic); + } else if (!uniquifier.isAvailable(mergeableLogic.name)) { + uniquifiableMergeableLogics.add(mergeableLogic); + } else { + return uniquifier.getUniqueName(initialName: mergeableLogic.name); + } + } + + // uniquify a preferred, mergeable name, if one exists + if (uniquifiableMergeableLogics.isNotEmpty) { + return uniquifier.getUniqueName( + initialName: uniquifiableMergeableLogics.first.name); + } + + // pick an available unpreferred mergeable name, if one exists, otherwise + // uniquify an unpreferred mergeable name + if (unpreferredMergeableLogics.isNotEmpty) { + return uniquifier.getUniqueName( + initialName: unpreferredMergeableLogics + .firstWhereOrNull( + (element) => uniquifier.isAvailable(element.name)) + ?.name ?? + unpreferredMergeableLogics.first.name); + } + + // pick anything (unnamed) and uniquify as necessary (considering preferred) + // no need to prefer an available one here, since it's all unnamed + return uniquifier.getUniqueName( + initialName: _unnamedLogics + .firstWhereOrNull( + (element) => !Naming.isUnpreferred(element.name)) + ?.name ?? + _unnamedLogics.first.name); + } + + /// Creates an instance to represent [initialLogic] and any that merge + /// into it. + SynthLogic(Logic initialLogic, + {Naming? namingOverride, bool constNameDisallowed = false}) + : isArray = initialLogic is LogicArray, + _constNameDisallowed = constNameDisallowed { + _addLogic(initialLogic, namingOverride: namingOverride); + } + + /// Returns the [SynthLogic] that should be *removed*. + static SynthLogic? tryMerge(SynthLogic a, SynthLogic b) { + if (_constantsMergeable(a, b)) { + // case to avoid things like a constant assigned to another constant + a.adopt(b); + return b; + } + + if (!a.mergeable && !b.mergeable) { + return null; + } + + if (a.isNet != b.isNet) { + // do not merge nets with non-nets + return null; + } + + if (b.mergeable) { + a.adopt(b); + return b; + } else { + b.adopt(a); + return a; + } + } + + /// Indicates whether two constants can be merged. + static bool _constantsMergeable(SynthLogic a, SynthLogic b) => + a.isConstant && + b.isConstant && + a._constLogic!.value == b._constLogic!.value && + !a._constNameDisallowed && + !b._constNameDisallowed; + + /// Merges [other] to be represented by `this` instead, and updates the + /// [other] that it has been replaced. + void adopt(SynthLogic other) { + assert(other.mergeable || _constantsMergeable(this, other), + 'Cannot merge a non-mergeable into this.'); + assert(other.isArray == isArray, 'Cannot merge arrays and non-arrays'); + + _constNameDisallowed |= other._constNameDisallowed; + + // only take one of the other's items if we don't have it already + _constLogic ??= other._constLogic; + _reservedLogic ??= other._reservedLogic; + _renameableLogic ??= other._renameableLogic; + + // the rest, take them all + _mergeableLogics.addAll(other._mergeableLogics); + _unnamedLogics.addAll(other._unnamedLogics); + + // keep track that it was replaced by this + other.replacement = this; + } + + /// Adds a new [logic] to be represented by this. + void _addLogic(Logic logic, {Naming? namingOverride}) { + final naming = namingOverride ?? logic.naming; + if (logic is Const) { + _constLogic = logic; + } else { + switch (naming) { + case Naming.reserved: + _reservedLogic = logic; + case Naming.renameable: + _renameableLogic = logic; + case Naming.mergeable: + _mergeableLogics.add(logic); + case Naming.unnamed: + _unnamedLogics.add(logic); + } + } + } + + @override + String toString() => '${_name == null ? 'null' : '"$name"'}, ' + 'logics contained: ${logics.map((e) => e.name).toList()}'; + + /// Provides a definition for a range in SV from a width. + static String _widthToRangeDef(int width, {bool forceRange = false}) { + if (width > 1 || forceRange) { + return '[${width - 1}:0]'; + } else { + return ''; + } + } + + String definitionType() => isNet ? 'wire' : 'logic'; + + /// Computes the name of the signal at declaration time with appropriate + /// dimensions included. + String definitionName() { + String packedDims; + String unpackedDims; + + // we only use this for dimensions, so first is fine + final logic = logics.first; + + if (isArray) { + 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 { + packedDims = _widthToRangeDef(logic.width); + unpackedDims = ''; + } + + return [packedDims, name, unpackedDims] + .where((e) => e.isNotEmpty) + .join(' '); + } +} + +/// Represents an element of a [LogicArray]. +/// +/// Does not fully override or properly implement all characteristics of +/// [SynthLogic], so this should be used cautiously. +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 { + final parentArrayname = parentArray.replacement?.name ?? parentArray.name; + final n = '$parentArrayname[${logic.arrayIndex!}]'; + assert( + Sanitizer.isSanitary( + n.substring(0, n.contains('[') ? n.indexOf('[') : null)), + 'Array name should be sanitary, but found $n', + ); + return n; + } + + /// The element of the [parentArray]. + final Logic logic; + + /// Creates an instance of an element of a [LogicArray]. + SynthLogicArrayElement(this.logic, this.parentArray) + : assert(logic.isArrayMember, + 'Should only be used for elements in a LogicArray'), + super(logic); + + @override + String toString() => '${_name == null ? 'null' : '"$name"'},' + ' parentArray=($parentArray), element ${logic.arrayIndex}, logic: $logic' + ' logics contained: ${logics.map((e) => e.name).toList()}'; +} diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart new file mode 100644 index 000000000..2001a04ff --- /dev/null +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -0,0 +1,527 @@ +import 'dart:collection'; + +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/collections/traverseable_collection.dart'; +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; +import 'package:rohd/src/utilities/uniquifier.dart'; + +/// Represents the definition of a module. +class SynthModuleDefinition { + /// The [Module] being defined. + final Module module; + + final List assignments = []; + + /// All other internal signals that are not ports. + /// + /// This is the only collection that maye have mergeable items in it. + final Set internalSignals = {}; + + /// All the input ports. + /// + /// This will *never* have any mergeable items in it. + final Set inputs = {}; + + /// All the output ports. + /// + /// This will *never* have any mergeable items in it. + final Set outputs = {}; + + /// All the output ports. + /// + /// This will *never* have any mergeable items in it. + final Set inOuts = {}; + + /// A mapping from original [Logic]s to the [SynthLogic]s that represent + /// them. + final Map logicToSynthMap = HashMap(); + + /// A mapping from the original [Module]s to the + /// [SynthSubModuleInstantiation]s that represent them. + final Map + moduleToSubModuleInstantiationMap = {}; + + /// Either accesses a previously created [SynthSubModuleInstantiation] + /// corresponding to [m], or else creates a new one and adds it to the + /// [moduleToSubModuleInstantiationMap]. + SynthSubModuleInstantiation getSynthSubModuleInstantiation(Module m) { + if (moduleToSubModuleInstantiationMap.containsKey(m)) { + return moduleToSubModuleInstantiationMap[m]!; + } else { + final newSSMI = createSubModuleInstantiation(m); + moduleToSubModuleInstantiationMap[m] = newSSMI; + return newSSMI; + } + } + + @visibleForOverriding + SynthSubModuleInstantiation createSubModuleInstantiation(Module m) => + SynthSubModuleInstantiation(m); + + @override + String toString() => "module name: '${module.name}'"; + + /// Used to uniquify any identifiers, including signal names + /// and module instances. + final Uniquifier _synthInstantiationNameUniquifier; + + /// Either accesses a previously created [SynthLogic] corresponding to + /// [logic], or else creates a new one and adds it to the [logicToSynthMap]. + SynthLogic? _getSynthLogic( + Logic? logic, + ) { + if (logic == null) { + return null; + } else if (logicToSynthMap.containsKey(logic)) { + return logicToSynthMap[logic]!; + } else { + SynthLogic newSynth; + if (logic.isArrayMember) { + // grab the parent array (potentially recursively) + final parentArraySynthLogic = + // ignore: unnecessary_null_checks + _getSynthLogic(logic.parentStructure!); + + newSynth = SynthLogicArrayElement(logic, parentArraySynthLogic!); + } else { + final disallowConstName = logic.isInput && + // ignore: deprecated_member_use_from_same_package + ((logic.parentModule is CustomSystemVerilog && + // ignore: deprecated_member_use_from_same_package + (logic.parentModule! as CustomSystemVerilog) + .expressionlessInputs + .contains(logic.name)) || + (logic.parentModule is SystemVerilog && + (logic.parentModule! as SystemVerilog) + .expressionlessInputs + .contains(logic.name))); + + newSynth = SynthLogic( + logic, + namingOverride: (logic.isPort && logic.parentModule != module) + ? Naming.mergeable + : null, + constNameDisallowed: disallowConstName, + ); + } + + logicToSynthMap[logic] = newSynth; + return newSynth; + } + } + + /// A [List] of supporting modules that need to be instantiated within this + /// definition. + final List supportingModules = []; + + /// Creates a new definition representation for this [module]. + SynthModuleDefinition(this.module) + : _synthInstantiationNameUniquifier = Uniquifier( + reservedNames: { + ...module.inputs.keys, + ...module.outputs.keys, + ...module.inOuts.keys, + }, + ), + assert( + !(module is SystemVerilog && + module.generatedDefinitionType == + DefinitionGenerationType.none), + 'Do not build a definition for a module' + ' which generates no definition!') { + // start by traversing output signals + final logicsToTraverse = TraverseableCollection() + ..addAll(module.outputs.values) + ..addAll(module.inOuts.values); + + for (final output in module.outputs.values) { + outputs.add(_getSynthLogic(output)!); + } + + // make sure disconnected inputs are included + for (final input in module.inputs.values) { + inputs.add(_getSynthLogic(input)!); + } + + // make sure disconnected inouts are included, also + for (final inOut in module.inOuts.values) { + inOuts.add(_getSynthLogic(inOut)!); + } + + // find any named signals sitting around that don't do anything + // this is not necessary for functionality, just nice naming inclusion + logicsToTraverse.addAll( + module.internalSignals + .where((element) => element.naming != Naming.unnamed), + ); + + // make sure floating modules are included + for (final subModule in module.subModules) { + getSynthSubModuleInstantiation(subModule); + logicsToTraverse + ..addAll(subModule.inputs.values) + ..addAll(subModule.outputs.values) + ..addAll(subModule.inOuts.values); + } + + // search for other modules contained within this module + + for (var i = 0; i < logicsToTraverse.length; i++) { + final receiver = logicsToTraverse[i]; + + assert( + receiver.parentModule != null, + 'Any signal traced by this should have been detected by build,' + ' but $receiver was not.'); + + if (receiver.parentModule != module && + !module.subModules.contains(receiver.parentModule)) { + // This should never happen! + assert(false, 'Receiver is not in this module or a submodule.'); + continue; + } + + if (receiver is LogicArray) { + logicsToTraverse.addAll(receiver.elements); + } + + if (receiver.isArrayMember) { + logicsToTraverse.add(receiver.parentStructure!); + } + + final synthReceiver = _getSynthLogic(receiver)!; + + if (receiver is LogicNet) { + logicsToTraverse.addAll([ + ...receiver.srcConnections, + ...receiver.dstConnections + ].where((element) => element.parentModule == module)); + + for (final srcConnection in receiver.srcConnections) { + if (srcConnection.parentModule == module || + (srcConnection.isOutput && + srcConnection.parentModule!.parent == module)) { + final netSynthDriver = _getSynthLogic(srcConnection)!; + + assignments.add(SynthAssignment( + netSynthDriver, + synthReceiver, + )); + } + } + } + + final driver = receiver.srcConnection; + + final receiverIsConstant = driver == null && receiver is Const; + + final receiverIsModuleInput = + module.isInput(receiver) && !receiver.isArrayMember; + final receiverIsModuleOutput = + module.isOutput(receiver) && !receiver.isArrayMember; + final receiverIsModuleInOut = + module.isInOut(receiver) && !receiver.isArrayMember; + + final synthDriver = _getSynthLogic(driver); + + if (receiverIsModuleInput) { + inputs.add(synthReceiver); + } else if (receiverIsModuleOutput) { + outputs.add(synthReceiver); + } else if (receiverIsModuleInOut) { + inOuts.add(synthReceiver); + } else { + internalSignals.add(synthReceiver); + } + + final receiverIsSubmoduleInOut = + receiver.isInOut && (receiver.parentModule?.parent == module); + if (receiverIsSubmoduleInOut) { + final subModule = receiver.parentModule!; + + if (synthReceiver is! SynthLogicArrayElement) { + getSynthSubModuleInstantiation(subModule) + .setInOutMapping(receiver.name, synthReceiver); + } + + logicsToTraverse.addAll(subModule.inOuts.values); + } + + final receiverIsSubModuleOutput = + receiver.isOutput && (receiver.parentModule?.parent == module); + if (receiverIsSubModuleOutput) { + final subModule = receiver.parentModule!; + + // array elements are not named ports, just contained in array + if (synthReceiver is! SynthLogicArrayElement) { + getSynthSubModuleInstantiation(subModule) + .setOutputMapping(receiver.name, synthReceiver); + } + + logicsToTraverse + ..addAll(subModule.inputs.values) + ..addAll(subModule.inOuts.values); + } else if (driver != null) { + if (!module.isInput(receiver) && !module.isInOut(receiver)) { + // stop at the input to this module + logicsToTraverse.add(driver); + assignments.add(SynthAssignment(synthDriver!, synthReceiver)); + } + } else if (receiverIsConstant && !receiver.value.isFloating) { + // this is a const that is valid, *partially* invalid (e.g. 0b1z1x0), + // or anything that's not *entirely* floating (since those we can leave + // as completely undriven). + + // make a new const node, it will merge away if not needed + final newReceiverConst = _getSynthLogic(Const(receiver.value))!; + internalSignals.add(newReceiverConst); + assignments.add(SynthAssignment(newReceiverConst, synthReceiver)); + } + + final receiverIsSubModuleInput = + receiver.isInput && (receiver.parentModule?.parent == module); + if (receiverIsSubModuleInput) { + final subModule = receiver.parentModule!; + + // array elements are not named ports, just contained in array + if (synthReceiver is! SynthLogicArrayElement) { + getSynthSubModuleInstantiation(subModule) + .setInputMapping(receiver.name, synthReceiver); + } + } + } + + // The order of these is important! + _collapseArrays(); + _collapseAssignments(); + _assignSubmodulePortMapping(); + process(); + _pickNames(); + } + + //TODO is this name good? + @protected + @visibleForOverriding + void process() { + // by default, nothing! + } + + /// Updates all sub-module instantiations with information about which + /// [SynthLogic] should be used for their ports. + void _assignSubmodulePortMapping() { + for (final submoduleInstantiation + in moduleToSubModuleInstantiationMap.values) { + for (final inputName in submoduleInstantiation.module.inputs.keys) { + final orig = submoduleInstantiation.inputMapping[inputName]!; + submoduleInstantiation.setInputMapping( + inputName, orig.replacement ?? orig, + replace: true); + } + + for (final outputName in submoduleInstantiation.module.outputs.keys) { + final orig = submoduleInstantiation.outputMapping[outputName]!; + submoduleInstantiation.setOutputMapping( + outputName, orig.replacement ?? orig, + replace: true); + } + + for (final inOutName in submoduleInstantiation.module.inOuts.keys) { + final orig = submoduleInstantiation.inOutMapping[inOutName]!; + submoduleInstantiation.setInOutMapping( + inOutName, orig.replacement ?? orig, + replace: true); + } + } + } + + /// Picks names of signals and sub-modules. + void _pickNames() { + // first ports get priority + for (final input in inputs) { + input.pickName(_synthInstantiationNameUniquifier); + } + for (final output in outputs) { + output.pickName(_synthInstantiationNameUniquifier); + } + for (final inOut in inOuts) { + inOut.pickName(_synthInstantiationNameUniquifier); + } + + // pick names of *reserved* submodule instances + final nonReservedSubmodules = []; + for (final submodule in moduleToSubModuleInstantiationMap.values) { + if (submodule.module.reserveName) { + submodule.pickName(_synthInstantiationNameUniquifier); + assert(submodule.module.name == submodule.name, + 'Expect reserved names to retain their name.'); + } else { + nonReservedSubmodules.add(submodule); + } + } + + // then *reserved* internal signals get priority + final nonReservedSignals = []; + for (final signal in internalSignals) { + if (signal.isReserved) { + signal.pickName(_synthInstantiationNameUniquifier); + } else { + nonReservedSignals.add(signal); + } + } + + // then submodule instances + for (final submodule + in nonReservedSubmodules.where((element) => element.needsDeclaration)) { + submodule.pickName(_synthInstantiationNameUniquifier); + } + + // then the rest of the internal signals + for (final signal in nonReservedSignals) { + signal.pickName(_synthInstantiationNameUniquifier); + } + } + + /// Merges bit blasted array assignments into one single assignment when + /// it's full array-full array assignment + void _collapseArrays() { + final boringArrayPairs = <(SynthLogic, SynthLogic)>[]; + + var prevAssignmentCount = 0; + while (prevAssignmentCount != assignments.length) { + final reducedAssignments = []; + + final groupedAssignments = + <(SynthLogic, SynthLogic), List>{}; + + for (final assignment in assignments) { + final src = assignment.src; + final dst = assignment.dst; + + if (src is SynthLogicArrayElement && dst is SynthLogicArrayElement) { + final srcArray = src.parentArray; + final dstArray = dst.parentArray; + + assert(srcArray.logics.length == 1, 'should be 1 name for the array'); + assert(dstArray.logics.length == 1, 'should be 1 name for the array'); + + if (srcArray.logics.first.elements.length != + dstArray.logics.first.elements.length || + boringArrayPairs.contains((srcArray, dstArray))) { + reducedAssignments.add(assignment); + } else { + groupedAssignments[(srcArray, dstArray)] ??= []; + groupedAssignments[(srcArray, dstArray)]!.add(assignment); + } + } else { + reducedAssignments.add(assignment); + } + } + + for (final MapEntry(key: (srcArray, dstArray), value: arrAssignments) + in groupedAssignments.entries) { + assert( + srcArray.logics.first.elements.length == + dstArray.logics.first.elements.length, + 'should be equal lengths of elements in both arrays by now'); + + // first requirement is that all elements have been assigned + var shouldMerge = + arrAssignments.length == srcArray.logics.first.elements.length; + + if (shouldMerge) { + // only check each element if the lengths match + for (final arrAssignment in arrAssignments) { + final arrAssignmentSrc = + (arrAssignment.src as SynthLogicArrayElement).logic; + final arrAssignmentDst = + (arrAssignment.dst as SynthLogicArrayElement).logic; + + if (arrAssignmentSrc.arrayIndex! != arrAssignmentDst.arrayIndex!) { + shouldMerge = false; + break; + } + } + } + + if (shouldMerge) { + reducedAssignments.add(SynthAssignment(srcArray, dstArray)); + } else { + reducedAssignments.addAll(arrAssignments); + boringArrayPairs.add((srcArray, dstArray)); + } + } + + prevAssignmentCount = assignments.length; + assignments + ..clear() + ..addAll(reducedAssignments); + } + } + + /// Collapses assignments that don't need to remain present. + void _collapseAssignments() { + // there might be more assign statements than necessary, so let's ditch them + var prevAssignmentCount = 0; + + while (prevAssignmentCount != assignments.length) { + // keep looping until it stops shrinking + final reducedAssignments = []; + for (final assignment in assignments) { + final dst = assignment.dst; + final src = assignment.src; + + assert(dst != src, + 'No circular assignment allowed between $dst and $src.'); + + final mergedAway = SynthLogic.tryMerge(dst, src); + + if (mergedAway != null) { + final kept = mergedAway == dst ? src : dst; + + final foundInternal = internalSignals.remove(mergedAway); + if (!foundInternal) { + final foundKept = internalSignals.remove(kept); + assert(foundKept, + 'One of the two should be internal since we cant merge ports.'); + + if (inputs.contains(mergedAway)) { + inputs + ..remove(mergedAway) + ..add(kept); + } else if (outputs.contains(mergedAway)) { + outputs + ..remove(mergedAway) + ..add(kept); + } else if (inOuts.contains(mergedAway)) { + inOuts + ..remove(mergedAway) + ..add(kept); + } + } + } else if (assignment.src.isFloatingConstant) { + internalSignals.remove(assignment.src); + } else { + reducedAssignments.add(assignment); + } + } + prevAssignmentCount = assignments.length; + assignments + ..clear() + ..addAll(reducedAssignments); + } + + // update the look-up table post-merge + logicToSynthMap.clear(); + for (final synthLogic in [ + ...inputs, + ...outputs, + ...inOuts, + ...internalSignals + ]) { + for (final logic in synthLogic.logics) { + logicToSynthMap[logic] = synthLogic; + } + } + } +} diff --git a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart new file mode 100644 index 000000000..7b387fd13 --- /dev/null +++ b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart @@ -0,0 +1,98 @@ +import 'dart:collection'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/utilities/utilities.dart'; +import 'package:rohd/src/utilities/uniquifier.dart'; + +/// Represents an instantiation of a module within another module. +class SynthSubModuleInstantiation { + /// The module represented. + final Module module; + + /// The name of this instance. + String? _name; + + /// Must call [pickName] before this is accessible. + String get name => _name!; + + /// Selects a name for this module instance. Must be called exactly once. + void pickName(Uniquifier uniquifier) { + assert(_name == null, 'Should only pick a name once.'); + + _name = uniquifier.getUniqueName( + initialName: module.uniqueInstanceName, + reserved: module.reserveName, + nullStarter: 'm', + ); + } + + /// A mapping of input port name to [SynthLogic]. + late final Map inputMapping = + UnmodifiableMapView(_inputMapping); + final Map _inputMapping = {}; + + /// Adds an input mapping from [name] to [synthLogic]. + void setInputMapping(String name, SynthLogic synthLogic, + {bool replace = false}) { + assert(module.inputs.containsKey(name), + 'Input $name not found in module ${module.name}.'); + assert( + (replace && _inputMapping.containsKey(name)) || + !_inputMapping.containsKey(name), + 'A mapping already exists to this input: $name.'); + + _inputMapping[name] = synthLogic; + } + + /// A mapping of output port name to [SynthLogic]. + late final Map outputMapping = + UnmodifiableMapView(_outputMapping); + final Map _outputMapping = {}; + + /// Adds an output mapping from [name] to [synthLogic]. + void setOutputMapping(String name, SynthLogic synthLogic, + {bool replace = false}) { + assert(module.outputs.containsKey(name), + 'Output $name not found in module ${module.name}.'); + assert( + (replace && _outputMapping.containsKey(name)) || + !_outputMapping.containsKey(name), + 'A mapping already exists to this output: $name.'); + + _outputMapping[name] = synthLogic; + } + + /// A mapping of output port name to [SynthLogic]. + late final Map inOutMapping = + UnmodifiableMapView(_inOutMapping); + final Map _inOutMapping = {}; + + void setInOutMapping(String name, SynthLogic synthLogic, + {bool replace = false}) { + assert(module.inOuts.containsKey(name), + 'InOut $name not found in module ${module.name}.'); + assert( + (replace && _inOutMapping.containsKey(name)) || + !_inOutMapping.containsKey(name), + 'A mapping already exists to this output: $name.'); + + _inOutMapping[name] = synthLogic; + } + + /// Indicates whether this module should be declared. + bool get needsDeclaration => _needsDeclaration; + bool _needsDeclaration = true; + + /// Removes the need for this module to be declared (via [needsDeclaration]). + void clearDeclaration() { + _needsDeclaration = false; + } + + /// Creates an instantiation for [module]. + SynthSubModuleInstantiation(this.module); + + @override + String toString() => + "_SynthSubModuleInstantiation ${_name == null ? 'null' : '"$name"'}, " + "module name:'${module.name}'"; +} diff --git a/lib/src/synthesizers/utilities/utilities.dart b/lib/src/synthesizers/utilities/utilities.dart new file mode 100644 index 000000000..50ce7aaaf --- /dev/null +++ b/lib/src/synthesizers/utilities/utilities.dart @@ -0,0 +1,7 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'synth_assignment.dart'; +export 'synth_logic.dart'; +export 'synth_module_definition.dart'; +export 'synth_sub_module_instantiation.dart'; From ccbd4155690fb65a38cac3dff2f9f82e9e114686 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 16 Dec 2024 09:11:35 -0800 Subject: [PATCH 02/24] minor tweaks --- lib/src/synthesizers/synthesis_result.dart | 2 ++ .../systemverilog/systemverilog_synthesis_result.dart | 4 ++-- .../synthesizers/systemverilog/systemverilog_synthesizer.dart | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/synthesizers/synthesis_result.dart b/lib/src/synthesizers/synthesis_result.dart index 1ebdc98e7..7dc115cff 100644 --- a/lib/src/synthesizers/synthesis_result.dart +++ b/lib/src/synthesizers/synthesis_result.dart @@ -49,6 +49,8 @@ abstract class SynthesisResult { @override int get hashCode => matchHashCode; + //TODO: make this so it supports MULTIPLE files + /// Generates what could go into a file String toFileContents(); diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 3bf58fbf8..5b4e0a931 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -71,7 +71,7 @@ class SystemVerilogSynthesisResult extends SynthesisResult { _parameterString.hashCode; @override - String toFileContents() => _toVerilog(getInstanceTypeOfModule); + String toFileContents() => _toVerilog(); /// Representation of all input port declarations in generated SV. List _verilogInputs() { @@ -181,7 +181,7 @@ class SystemVerilogSynthesisResult extends SynthesisResult { } /// The full SV representation of this module. - String _toVerilog(String Function(Module module) getInstanceTypeOfModule) { + String _toVerilog() { final verilogModuleName = getInstanceTypeOfModule(module); return [ [ diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart index 68fc200d7..bcc44fbcc 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart @@ -1,3 +1,5 @@ +//TODO: headers! + import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart'; From 2262dfe2016b9c32b9730886825bcba23069055e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 26 Dec 2024 16:21:38 -0800 Subject: [PATCH 03/24] refactor to return multiple file contents --- lib/src/synthesizers/synth_builder.dart | 3 ++- lib/src/synthesizers/synth_file_contents.dart | 11 +++++++++++ lib/src/synthesizers/synthesis_result.dart | 6 +++--- lib/src/synthesizers/synthesizers.dart | 3 ++- .../systemverilog_synth_module_definition.dart | 1 - .../systemverilog_synthesis_result.dart | 16 ++++++++++++---- .../systemverilog/systemverilog_synthesizer.dart | 1 - test/synth_builder_test.dart | 5 +++-- 8 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 lib/src/synthesizers/synth_file_contents.dart diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index d613feb13..2137059b7 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -60,8 +60,9 @@ class SynthBuilder { /// Collects a [List] of [String]s representing file contents generated by /// the [synthesizer]. - List getFileContents() => synthesisResults + List getFileContents() => synthesisResults .map((synthesisResult) => synthesisResult.toFileContents()) + .flattened .toList(growable: false); /// Provides an instance type name for [module]. diff --git a/lib/src/synthesizers/synth_file_contents.dart b/lib/src/synthesizers/synth_file_contents.dart new file mode 100644 index 000000000..7ecd34723 --- /dev/null +++ b/lib/src/synthesizers/synth_file_contents.dart @@ -0,0 +1,11 @@ +class SynthFileContents { + final String name; + final String? description; + final String contents; + + const SynthFileContents( + {required this.name, required this.contents, this.description}); + + @override + String toString() => contents; +} diff --git a/lib/src/synthesizers/synthesis_result.dart b/lib/src/synthesizers/synthesis_result.dart index 7dc115cff..e1005c8ec 100644 --- a/lib/src/synthesizers/synthesis_result.dart +++ b/lib/src/synthesizers/synthesis_result.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synthesis_result.dart @@ -10,7 +10,7 @@ import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; -/// An object representing the output of a Synthesizer +/// An object representing the output of a Synthesizer for one [module]. @immutable abstract class SynthesisResult { /// The top level [Module] associated with this result. @@ -52,7 +52,7 @@ abstract class SynthesisResult { //TODO: make this so it supports MULTIPLE files /// Generates what could go into a file - String toFileContents(); + List toFileContents(); /// If provided, a [List] of additional [Module]s that should be included in /// the generated results. diff --git a/lib/src/synthesizers/synthesizers.dart b/lib/src/synthesizers/synthesizers.dart index 528577bb4..3f9f3f111 100644 --- a/lib/src/synthesizers/synthesizers.dart +++ b/lib/src/synthesizers/synthesizers.dart @@ -1,7 +1,8 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'synth_builder.dart'; +export 'synth_file_contents.dart'; export 'synthesis_result.dart'; export 'synthesizer.dart'; export 'systemverilog/systemverilog.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart index 5fae44549..8213a5bf0 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart @@ -1,5 +1,4 @@ import 'package:rohd/rohd.dart'; -import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 5b4e0a931..3a2b2a49b 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -1,6 +1,5 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; -import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; @@ -27,8 +26,12 @@ class SystemVerilogCustomDefinitionSynthesisResult extends SynthesisResult { (other.module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!; @override - String toFileContents() => (module as SystemVerilog) - .definitionVerilog(getInstanceTypeOfModule(module))!; + List toFileContents() => List.unmodifiable([ + SynthFileContents( + name: instanceTypeName, + contents: (module as SystemVerilog) + .definitionVerilog(getInstanceTypeOfModule(module))!) + ]); } /// A [SynthesisResult] representing a conversion of a [Module] to @@ -71,7 +74,12 @@ class SystemVerilogSynthesisResult extends SynthesisResult { _parameterString.hashCode; @override - String toFileContents() => _toVerilog(); + List toFileContents() => List.unmodifiable([ + SynthFileContents( + name: instanceTypeName, + contents: _toVerilog(), + ) + ]); /// Representation of all input port declarations in generated SV. List _verilogInputs() { diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart index bcc44fbcc..a1536d3bb 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart @@ -1,7 +1,6 @@ //TODO: headers! import 'package:rohd/rohd.dart'; -import 'package:rohd/src/synthesizers/systemverilog/systemverilog_mixins.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart'; /// A [Synthesizer] which generates equivalent SystemVerilog as the diff --git a/test/synth_builder_test.dart b/test/synth_builder_test.dart index 48a23dec0..07f043bc1 100644 --- a/test/synth_builder_test.dart +++ b/test/synth_builder_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synth_builder_test.dart @@ -66,7 +66,8 @@ void main() { for (final submod in mod.subModules) { final synth = SynthBuilder(submod, SystemVerilogSynthesizer()); - expect(synth.getFileContents()[0], contains(submod.definitionName)); + expect(synth.getFileContents()[0].contents, + contains(submod.definitionName)); } }); }); From 8dc3c9c36bbc9189d84ceaf691bacbf4b11b7ea6 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 30 Dec 2024 13:34:27 -0800 Subject: [PATCH 04/24] add a reminder todo for issue --- lib/src/synthesizers/synth_builder.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index 2137059b7..e6a1c60d6 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -35,6 +35,8 @@ class SynthBuilder { /// [Uniquifier] for instance type names. final Uniquifier _instanceTypeUniquifier = Uniquifier(); + //TODO: consider https://github.com/intel/rohd/issues/434 + /// Constructs a [SynthBuilder] based on the [top] module and /// using [synthesizer] for generating outputs. SynthBuilder(this.top, this.synthesizer) { From 231a4c490ef95d3d9dc73960db91c1e8fbade19a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 14:52:14 -0700 Subject: [PATCH 05/24] initial work on LogicEnum --- lib/src/signals/logic_enum.dart | 145 ++++++++++++++++++++++++++++++++ lib/src/signals/signals.dart | 1 + lib/src/signals/wire.dart | 14 +++ test/logic_enum_test.dart | 58 +++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 lib/src/signals/logic_enum.dart create mode 100644 test/logic_enum_test.dart diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart new file mode 100644 index 000000000..ad0041bc3 --- /dev/null +++ b/lib/src/signals/logic_enum.dart @@ -0,0 +1,145 @@ +part of 'signals.dart'; + +// TODO: what if you assign between LogicEnums of the same T, but different mappings? +// - by default, throw an exception if assignment between enums of different mappings? +// TODO: should we support enum ports? then where does the typedef live? + +class LogicEnum extends Logic { + //TODO: if a `put` has an illegal value prop X + + late final Map mapping; + // late final List values; + + //TODO better exceptions throughout + + T get valueEnum => mapping.entries + .firstWhere((entry) => entry.value == value, + orElse: () => throw StateError('Value $value does not co.')) + .key; + + static Map _computeMapping( + {required Map mapping, required int width}) { + final computedMapping = mapping + .map((key, value) => MapEntry(key, LogicValue.of(value, width: width))); + + if (computedMapping.values.any((v) => !v.isValid)) { + throw ArgumentError('Mapping values must be valid LogicValues,' + ' but found: $computedMapping'); + } + + // check that any `int` or `BigInt` mappings actually ended up matching + for (final MapEntry(key: key, value: computedValue) + in computedMapping.entries) { + if (mapping[key] is int) { + if (computedValue.toInt() != mapping[key]) { + throw ArgumentError( + 'Mapping value for $key is not equal to the original int value.' + ' Computed: $computedValue, Original: ${mapping[key]}'); + } + } + + if (mapping[key] is BigInt) { + if (computedValue.toBigInt() != mapping[key]) { + throw ArgumentError( + 'Mapping value for $key is not equal to the original BigInt value.' + ' Computed: $computedValue, Original: ${mapping[key]}'); + } + } + } + + if (computedMapping.values.toSet().length != + computedMapping.values.length) { + throw ArgumentError('Mapping values must be unique,' + ' but found duplicates: $computedMapping'); + } + + return computedMapping; + } + + static int _computeWidth( + {int? requestedWidth, Map? mapping}) { + var width = 1; + + if (mapping != null) { + width = LogicValue.ofInt(mapping.length, 32).clog2().toInt(); + + if (mapping.values.toSet().length != mapping.values.length) { + throw ArgumentError( + 'Mapping values must be unique, but found duplicates: $mapping'); + } + + for (final value in [ + ...mapping.values.whereType(), + ...mapping.values.whereType().map(LogicValue.ofString), + ...mapping.values + .whereType>() + .map(LogicValue.ofIterable) + ]) { + if (value.width > width) { + width = value.width; + } + } + } + + if (requestedWidth != null) { + if (requestedWidth < width) { + throw ArgumentError( + 'Requested width $requestedWidth is less than the minimum' + ' required width $width.'); + } + width = requestedWidth; + } + + return width; + } + + LogicEnum.withMapping(Map mapping, + {int? width, super.name, super.naming}) + : super(width: _computeWidth(requestedWidth: width, mapping: mapping)) { + this.mapping = + Map.unmodifiable(_computeMapping(mapping: mapping, width: this.width)); + + _wire._constrainValue((value) { + if (value.isFloating) { + return LogicValue.filled(this.width, LogicValue.z); + } + if (!value.isValid) { + return LogicValue.filled(this.width, LogicValue.x); + } + if (!this.mapping.containsValue(value)) { + return LogicValue.filled(this.width, LogicValue.x); + } + return value; + }); + } + + LogicEnum(List values, {int? width, String? name, Naming? naming}) + : this.withMapping( + Map.fromEntries( + values.mapIndexed((index, value) => MapEntry(value, index))), + width: width, + name: name, + naming: naming); + + @override + void put(dynamic val, {bool fill = false}) { + if (val is T) { + if (fill) { + throw Exception(); //TODO + } + + if (!mapping.containsKey(val)) { + throw Exception('Value $val is not mapped in $mapping.'); + } + + // ignore: unnecessary_null_checks + super.put(mapping[val]!); + } else { + super.put(val, fill: fill); + } + } + + //TODO: clone + + //TODO need to update the Wire to have "restrictions" on legal values +} diff --git a/lib/src/signals/signals.dart b/lib/src/signals/signals.dart index 348487a72..98a4f5f4f 100644 --- a/lib/src/signals/signals.dart +++ b/lib/src/signals/signals.dart @@ -23,3 +23,4 @@ part 'wire_net.dart'; part 'logic_structure.dart'; part 'logic_array.dart'; part 'logic_net.dart'; +part 'logic_enum.dart'; diff --git a/lib/src/signals/wire.dart b/lib/src/signals/wire.dart index 32241f169..d78a00100 100644 --- a/lib/src/signals/wire.dart +++ b/lib/src/signals/wire.dart @@ -145,6 +145,7 @@ class _Wire { _Wire _adopt(_Wire other) { _glitchController.emitter.adopt(other._glitchController.emitter); other._migrateChangedTriggers(this); + _valueConstraints.addAll(other._valueConstraints); // ignore: avoid_returning_this return this; @@ -245,9 +246,19 @@ class _Wire { newValue = LogicValue.filled(width, LogicValue.x); } + for (final constraint in _valueConstraints) { + newValue = constraint(newValue); + } + _updateValue(newValue, signalName: signalName); } + //TODO docs + final List<_LogicValueConstraint> _valueConstraints = []; + void _constrainValue(_LogicValueConstraint constraint) { + _valueConstraints.add(constraint); + } + /// Updates the value of this signal to [newValue]. void _updateValue(LogicValue newValue, {required String signalName}) { final prevValue = value; @@ -265,3 +276,6 @@ class _Wire { @override String toString() => 'wire $hashCode'; } + +//TODO docs +typedef _LogicValueConstraint = LogicValue Function(LogicValue origValue); diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart new file mode 100644 index 000000000..37541ac8d --- /dev/null +++ b/test/logic_enum_test.dart @@ -0,0 +1,58 @@ +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/signals/signals.dart'; +import 'package:test/test.dart'; + +enum TestEnum { a, b, c } + +class MyListLogicEnum extends LogicEnum { + MyListLogicEnum() : super(TestEnum.values); +} + +class MyMapLogicEnum extends LogicEnum { + MyMapLogicEnum() + : super.withMapping({ + TestEnum.a: 1, + // TestEnum.b: 5, // `b` is not mapped! + TestEnum.c: 7, + }, width: 3); +} + +void main() { + test('enum populates based on list of values', () { + final e = MyListLogicEnum(); + + expect(e.mapping.length, TestEnum.values.length); + expect(e.width, 2); + + var idx = 0; + for (final val in TestEnum.values) { + expect(e.mapping.containsKey(val), isTrue); + expect(e.mapping[val]!.width, e.width); + expect(e.mapping[val]!.toInt(), idx++); + } + }); + + test('enum only allows legal values', () { + final e = MyListLogicEnum(); + expect(e.value.isFloating, isTrue); + e.put(0); + expect(e.value.toInt(), 0); + expect(e.valueEnum, TestEnum.a); + e.put(1); + expect(e.value.toInt(), 1); + expect(e.valueEnum, TestEnum.b); + e.put(2); + expect(e.value.toInt(), 2); + expect(e.valueEnum, TestEnum.c); + e.put(3); + expect(e.value, LogicValue.filled(e.width, LogicValue.x)); + // expect(() => e.valueEnum, throwsA(isA())); //TODO + }); + + test('enum puts with enums', () { + final e = MyListLogicEnum(); + e.put(TestEnum.b); + expect(e.value.toInt(), TestEnum.b.index); + expect(e.valueEnum, TestEnum.b); + }); +} From b2834f56e704757b3ff2cd02cbfbb5713a21413e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 16:01:29 -0700 Subject: [PATCH 06/24] deprecate ane make new names, to preserve backwards compatibility --- lib/src/synthesizers/synth_builder.dart | 11 +++++++++-- lib/src/synthesizers/synthesis_result.dart | 10 ++++++---- .../systemverilog_synthesis_result.dart | 12 ++++++++++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index e6a1c60d6..bc0f5dae3 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synth_builder.dart @@ -62,8 +62,15 @@ class SynthBuilder { /// Collects a [List] of [String]s representing file contents generated by /// the [synthesizer]. - List getFileContents() => synthesisResults + @Deprecated('Use `getSynthFileContents()` instead.') + List getFileContents() => synthesisResults .map((synthesisResult) => synthesisResult.toFileContents()) + .toList(growable: false); + + /// Collects a [List] of [SynthFileContents]s representing file contents + /// generated by the [synthesizer]. + List getSynthFileContents() => synthesisResults + .map((synthesisResult) => synthesisResult.toSynthFileContents()) .flattened .toList(growable: false); diff --git a/lib/src/synthesizers/synthesis_result.dart b/lib/src/synthesizers/synthesis_result.dart index e1005c8ec..27abb8fe9 100644 --- a/lib/src/synthesizers/synthesis_result.dart +++ b/lib/src/synthesizers/synthesis_result.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synthesis_result.dart @@ -49,10 +49,12 @@ abstract class SynthesisResult { @override int get hashCode => matchHashCode; - //TODO: make this so it supports MULTIPLE files + /// Generates what could go into a file. + @Deprecated('Use `toSynthFileContents()` instead.') + String toFileContents(); - /// Generates what could go into a file - List toFileContents(); + /// Generates contents for a number of files. + List toSynthFileContents(); /// If provided, a [List] of additional [Module]s that should be included in /// the generated results. diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 3a2b2a49b..07057e949 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -26,7 +26,11 @@ class SystemVerilogCustomDefinitionSynthesisResult extends SynthesisResult { (other.module as SystemVerilog).definitionVerilog('*PLACEHOLDER*')!; @override - List toFileContents() => List.unmodifiable([ + String toFileContents() => (module as SystemVerilog) + .definitionVerilog(getInstanceTypeOfModule(module))!; + + @override + List toSynthFileContents() => List.unmodifiable([ SynthFileContents( name: instanceTypeName, contents: (module as SystemVerilog) @@ -74,9 +78,13 @@ class SystemVerilogSynthesisResult extends SynthesisResult { _parameterString.hashCode; @override - List toFileContents() => List.unmodifiable([ + String toFileContents() => _toVerilog(); + + @override + List toSynthFileContents() => List.unmodifiable([ SynthFileContents( name: instanceTypeName, + description: 'SystemVerilog module definition for $instanceTypeName', contents: _toVerilog(), ) ]); From 0157952502f8a93630b94c08f092782c07ef3d98 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 16:38:16 -0700 Subject: [PATCH 07/24] adjust apis a bit --- .../module/module_not_built_exception.dart | 7 ++++-- lib/src/module.dart | 10 +++----- lib/src/synthesizers/synth_builder.dart | 25 +++++++++++++++---- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/lib/src/exceptions/module/module_not_built_exception.dart b/lib/src/exceptions/module/module_not_built_exception.dart index ca2bb8760..e9858828a 100644 --- a/lib/src/exceptions/module/module_not_built_exception.dart +++ b/lib/src/exceptions/module/module_not_built_exception.dart @@ -14,6 +14,9 @@ import 'package:rohd/rohd.dart'; 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.']); + ModuleNotBuiltException(Module module, [String? additionalInformation]) + : super([ + 'Module $module has not yet built! Must call build() first.', + additionalInformation + ].nonNulls.join(' ')); } diff --git a/lib/src/module.dart b/lib/src/module.dart index f0b5d8119..73cead4b4 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -199,8 +199,7 @@ abstract class Module { String get uniqueInstanceName => hasBuilt || reserveName ? _uniqueInstanceName : throw ModuleNotBuiltException( - 'Module must be built to access uniquified name.' - ' Call build() before accessing this.'); + this, 'Module must be built to access uniquified name.'); String _uniqueInstanceName; /// If true, guarantees [uniqueInstanceName] matches [name] or else the @@ -251,8 +250,7 @@ abstract class Module { Iterable hierarchy() { if (!hasBuilt) { throw ModuleNotBuiltException( - 'Module must be built before accessing hierarchy.' - ' Call build() before executing this.'); + this, 'Module must be built before accessing hierarchy.'); } Module? pModule = this; final hierarchyQueue = Queue(); @@ -902,7 +900,7 @@ abstract class Module { /// may have other output formats, languages, files, etc. String generateSynth() { if (!_hasBuilt) { - throw ModuleNotBuiltException(); + throw ModuleNotBuiltException(this); } final synthHeader = ''' @@ -915,7 +913,7 @@ abstract class Module { '''; return synthHeader + SynthBuilder(this, SystemVerilogSynthesizer()) - .getFileContents() + .getSynthFileContents() .join('\n\n////////////////////\n\n'); } } diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index bc0f5dae3..009cadda3 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -16,7 +16,12 @@ import 'package:rohd/src/utilities/uniquifier.dart'; /// a [Synthesizer]. class SynthBuilder { /// The top-level [Module] to be synthesized. - final Module top; + @Deprecated('Use `tops` instead.') + Module get top => + tops.length != 1 ? throw Exception('TODO') : tops.first; //TODO exception + + /// The top-level [Module]s to be synthesized. + final List tops; /// The [Synthesizer] to use for generating an output. final Synthesizer synthesizer; @@ -39,12 +44,20 @@ class SynthBuilder { /// Constructs a [SynthBuilder] based on the [top] module and /// using [synthesizer] for generating outputs. - SynthBuilder(this.top, this.synthesizer) { - if (!top.hasBuilt) { - throw ModuleNotBuiltException(); + SynthBuilder(Module top, Synthesizer synthesizer) + : this.multi([top], synthesizer); + + /// Constructs a [SynthBuilder] based on the provided [tops] modules and + /// using [synthesizer] for generating outputs. + SynthBuilder.multi(List tops, this.synthesizer) + : tops = List.unmodifiable(tops) { + for (final top in tops) { + if (!top.hasBuilt) { + throw ModuleNotBuiltException(top); + } } - final modulesToParse = [top]; + final modulesToParse = [...tops]; for (var i = 0; i < modulesToParse.length; i++) { final moduleI = modulesToParse[i]; if (!synthesizer.generatesDefinition(moduleI)) { @@ -53,6 +66,8 @@ class SynthBuilder { modulesToParse.addAll(moduleI.subModules); } + //TODO: test that multiple mods with same module definition name doesn't conflict + // go backwards to start from the bottom (...now we're here) // critical to go in this order for caching to work properly modulesToParse.reversed From 97790a99f42302c346a78bb271226627e7baad79 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 16:56:30 -0700 Subject: [PATCH 08/24] tested that multi top works in synth builder --- test/synth_builder_test.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/synth_builder_test.dart b/test/synth_builder_test.dart index 07f043bc1..89f3fe393 100644 --- a/test/synth_builder_test.dart +++ b/test/synth_builder_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synth_builder_test.dart @@ -14,6 +14,7 @@ class TopModule extends Module { TopModule(Logic a, Logic b) : super(name: 'topmodule') { a = addInput('a', a, width: a.width); b = addInput('b', b, width: b.width); + final y = addOutput('y', width: a.width); final z = addOutput('z', width: b.width); final z2 = addOutput('z2', width: b.width); @@ -66,9 +67,26 @@ void main() { for (final submod in mod.subModules) { final synth = SynthBuilder(submod, SystemVerilogSynthesizer()); - expect(synth.getFileContents()[0].contents, + expect(synth.getSynthFileContents()[0].contents, contains(submod.definitionName)); } }); + + test('multi-top synthbuilder works', () async { + final top1 = TopModule(Logic(), Logic()); + final top2 = TopModule(Logic(width: 8), Logic()); + + await top1.build(); + await top2.build(); + + final synthBuilder = + SynthBuilder.multi([top1, top2], SystemVerilogSynthesizer()); + final synthResults = synthBuilder.synthesisResults; + + expect(synthResults.where((e) => e.module == top1).length, 1); + expect(synthResults.where((e) => e.module == top2).length, 1); + expect(synthResults.where((e) => e.module is AModule).length, 2); + expect(synthResults.where((e) => e.module is BModule).length, 1); + }); }); } From dda9399d80910e0a8e6b34efb5bdd8ce74b0a443 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 17:57:56 -0700 Subject: [PATCH 09/24] test more stuff about synthfilecontents --- test/synth_builder_test.dart | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/synth_builder_test.dart b/test/synth_builder_test.dart index 89f3fe393..3cda6989b 100644 --- a/test/synth_builder_test.dart +++ b/test/synth_builder_test.dart @@ -67,8 +67,23 @@ void main() { for (final submod in mod.subModules) { final synth = SynthBuilder(submod, SystemVerilogSynthesizer()); - expect(synth.getSynthFileContents()[0].contents, + final firstSynthFileContents = synth.getSynthFileContents()[0]; + expect( + firstSynthFileContents.contents, contains(submod.definitionName)); + expect(firstSynthFileContents.name, submod.definitionName); + + expect( + synth.synthesisResults.first.toSynthFileContents().first.contents, + firstSynthFileContents.contents); + + expect(firstSynthFileContents.description, contains(submod.definitionName)); + + // test backwards compatibility + expect( + // ignore: deprecated_member_use_from_same_package + synth.getFileContents().first, + firstSynthFileContents.toString()); } }); From 4e9c3730f8cc4787e2c5c25ba46c020f35e03bf1 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 18:24:12 -0700 Subject: [PATCH 10/24] cleanup and docs --- lib/src/exceptions/exceptions.dart | 3 ++- lib/src/exceptions/synth_exception.dart | 17 +++++++++++++++++ lib/src/synthesizers/synth_builder.dart | 9 +++------ lib/src/synthesizers/synth_file_contents.dart | 16 ++++++++++++++++ lib/src/synthesizers/synthesizers.dart | 2 +- .../systemverilog/systemverilog.dart | 6 ------ .../systemverilog/systemverilog_mixins.dart | 9 +++++++++ .../systemverilog_synth_module_definition.dart | 12 +++++++++++- ...verilog_synth_sub_module_instantiation.dart | 12 ++++++++++++ .../systemverilog_synthesis_result.dart | 18 ++++++++++++++++++ .../systemverilog_synthesizer.dart | 9 ++++++++- .../utilities/synth_assignment.dart | 9 +++++++++ .../synthesizers/utilities/synth_logic.dart | 11 ++++++++--- .../utilities/synth_module_definition.dart | 18 +++++++++++++++++- .../synth_sub_module_instantiation.dart | 10 ++++++++++ lib/src/synthesizers/utilities/utilities.dart | 2 +- 16 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 lib/src/exceptions/synth_exception.dart diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart index e0945dd4b..6267a70c7 100644 --- a/lib/src/exceptions/exceptions.dart +++ b/lib/src/exceptions/exceptions.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export './conditionals/conditional_exceptions.dart'; @@ -11,4 +11,5 @@ export './sim_compare/sim_compare_exceptions.dart'; export 'illegal_configuration_exception.dart'; export 'rohd_exception.dart'; export 'simulator_exception.dart'; +export 'synth_exception.dart'; export 'unsupported_type_exception.dart'; diff --git a/lib/src/exceptions/synth_exception.dart b/lib/src/exceptions/synth_exception.dart new file mode 100644 index 000000000..0bca5294b --- /dev/null +++ b/lib/src/exceptions/synth_exception.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_exception.dart +// Definition for exception when an error occurs in the Synthesizer stack. +// +// 2025 June 24 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; + +/// An [Exception] thrown when an error occurs in the simulator. +class SynthException extends RohdException { + /// Constructs a new [Exception] for when an error occurs in the [Synthesizer] + /// stack. + SynthException(super.message); +} diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index 009cadda3..7ad79b9d5 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -17,8 +17,9 @@ import 'package:rohd/src/utilities/uniquifier.dart'; class SynthBuilder { /// The top-level [Module] to be synthesized. @Deprecated('Use `tops` instead.') - Module get top => - tops.length != 1 ? throw Exception('TODO') : tops.first; //TODO exception + Module get top => tops.length != 1 + ? throw SynthException('There must be exactly one top to use this API.') + : tops.first; /// The top-level [Module]s to be synthesized. final List tops; @@ -40,8 +41,6 @@ class SynthBuilder { /// [Uniquifier] for instance type names. final Uniquifier _instanceTypeUniquifier = Uniquifier(); - //TODO: consider https://github.com/intel/rohd/issues/434 - /// Constructs a [SynthBuilder] based on the [top] module and /// using [synthesizer] for generating outputs. SynthBuilder(Module top, Synthesizer synthesizer) @@ -66,8 +65,6 @@ class SynthBuilder { modulesToParse.addAll(moduleI.subModules); } - //TODO: test that multiple mods with same module definition name doesn't conflict - // go backwards to start from the bottom (...now we're here) // critical to go in this order for caching to work properly modulesToParse.reversed diff --git a/lib/src/synthesizers/synth_file_contents.dart b/lib/src/synthesizers/synth_file_contents.dart index 7ecd34723..bf5497a36 100644 --- a/lib/src/synthesizers/synth_file_contents.dart +++ b/lib/src/synthesizers/synth_file_contents.dart @@ -1,8 +1,24 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_file_contents.dart +// Definition for `SynthFileContents` +// +// 2025 June 24 +// Author: Max Korbel + +/// Represents contents of a file. class SynthFileContents { + /// The name of the content or file. final String name; + + /// An (optional) description of what this represents. final String? description; + + /// The actual contents of the file. final String contents; + /// Creates a new [SynthFileContents]. const SynthFileContents( {required this.name, required this.contents, this.description}); diff --git a/lib/src/synthesizers/synthesizers.dart b/lib/src/synthesizers/synthesizers.dart index 3f9f3f111..b8c8523ec 100644 --- a/lib/src/synthesizers/synthesizers.dart +++ b/lib/src/synthesizers/synthesizers.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'synth_builder.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog.dart b/lib/src/synthesizers/systemverilog/systemverilog.dart index b4f1b167f..281b05df9 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog.dart @@ -1,11 +1,5 @@ // Copyright (C) 2021-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause -// -// systemverilog.dart -// Definition for SystemVerilog Synthesizer -// -// 2021 August 26 -// Author: Max Korbel export 'systemverilog_mixins.dart'; export 'systemverilog_synthesizer.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart b/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart index e29be5b1f..89984811d 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_mixins.dart @@ -1,3 +1,12 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog_mixins.dart +// Definition for SystemVerilog Mixins and supporting stuff +// +// 2025 June +// Author: Max Korbel + import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart index 8213a5bf0..b2758c742 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart @@ -1,11 +1,21 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog_synth_module_definition.dart +// Definition for SystemVerilogSynthModuleDefinition +// +// 2025 June +// Author: Max Korbel + import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; +/// A special [SynthModuleDefinition] for SystemVerilog modules. class SystemVerilogSynthModuleDefinition extends SynthModuleDefinition { + /// Creates a new [SystemVerilogSynthModuleDefinition] for the given [module]. SystemVerilogSynthModuleDefinition(super.module); - //TODO @override void process() { _replaceNetConnections(); diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart index 56b9e041d..4f2a61c40 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart @@ -1,6 +1,16 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog_synth_sub_module_instantiation.dart +// Definition for SystemVerilogSynthSubModuleInstantiation +// +// 2025 June +// Author: Max Korbel + import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; +/// Represents a submodule instantiation for SystemVerilog. class SystemVerilogSynthSubModuleInstantiation extends SynthSubModuleInstantiation { /// If [module] is [InlineSystemVerilog], this will be the [SynthLogic] that @@ -10,6 +20,8 @@ class SystemVerilogSynthSubModuleInstantiation : (outputMapping[(module as InlineSystemVerilog).resultSignalName] ?? inOutMapping[(module as InlineSystemVerilog).resultSignalName]); + /// Creates a new [SystemVerilogSynthSubModuleInstantiation] for the given + /// [module]. SystemVerilogSynthSubModuleInstantiation(super.module); /// Mapping from [SynthLogic]s which are outputs of inlineable SV to those diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 07057e949..069f3e31a 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -1,12 +1,29 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog_synthesis_result.dart +// Definition for SystemVerilogCustomDefinitionSynthesisResult +// +// 2025 June +// Author: Max Korbel + import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; +/// Extra utilities on [SynthLogic] to help with SystemVerilog synthesis. +extension on SynthLogic { + /// Gets the SystemVerilog type for this signal. + String definitionType() => isNet ? 'wire' : 'logic'; +} + /// A [SynthesisResult] representing a [Module] that provides a custom /// SystemVerilog definition. class SystemVerilogCustomDefinitionSynthesisResult extends SynthesisResult { + /// Creates a new [SystemVerilogCustomDefinitionSynthesisResult] for the given + /// [module]. SystemVerilogCustomDefinitionSynthesisResult( super.module, super.getInstanceTypeOfModule) : assert( @@ -57,6 +74,7 @@ class SystemVerilogSynthesisResult extends SynthesisResult { List get supportingModules => _synthModuleDefinition.supportingModules; + /// Creates a new [SystemVerilogSynthesisResult] for the given [module]. SystemVerilogSynthesisResult(super.module, super.getInstanceTypeOfModule) : _synthModuleDefinition = SystemVerilogSynthModuleDefinition(module) { _portsString = _verilogPorts(); diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart index a1536d3bb..d8b5bae36 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart @@ -1,4 +1,11 @@ -//TODO: headers! +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// systemverilog_synthesizer.dart +// Definition for SystemVerilog Synthesizer +// +// 2025 June +// Author: Max Korbel import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart'; diff --git a/lib/src/synthesizers/utilities/synth_assignment.dart b/lib/src/synthesizers/utilities/synth_assignment.dart index 1f5cb543d..7ccef1873 100644 --- a/lib/src/synthesizers/utilities/synth_assignment.dart +++ b/lib/src/synthesizers/utilities/synth_assignment.dart @@ -1,3 +1,12 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_assignment.dart +// Definition for assignments +// +// 2025 June +// Author: Max Korbel + import 'package:rohd/src/synthesizers/utilities/utilities.dart'; /// Represents an assignment between two signals. diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 366799aa3..4d2e45f49 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -1,4 +1,11 @@ -import 'dart:collection'; +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_logic.dart +// Definitions for signal representations during generation +// +// 2025 June +// Author: Max Korbel import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; @@ -261,8 +268,6 @@ class SynthLogic { } } - String definitionType() => isNet ? 'wire' : 'logic'; - /// Computes the name of the signal at declaration time with appropriate /// dimensions included. String definitionName() { diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index 2001a04ff..d92df06cf 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -1,3 +1,12 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_module_definition.dart +// Definitions for a module definition +// +// 2025 June +// Author: Max Korbel + import 'dart:collection'; import 'package:meta/meta.dart'; @@ -11,6 +20,7 @@ class SynthModuleDefinition { /// The [Module] being defined. final Module module; + /// All the assignments that are part of this definition. final List assignments = []; /// All other internal signals that are not ports. @@ -55,6 +65,11 @@ class SynthModuleDefinition { } } + /// Creates a [SynthSubModuleInstantiation] representing the instantiation of + /// [m]. + /// + /// This can be overridden to provide custom types for sub-module + /// instantiation. @visibleForOverriding SynthSubModuleInstantiation createSubModuleInstantiation(Module m) => SynthSubModuleInstantiation(m); @@ -300,7 +315,8 @@ class SynthModuleDefinition { _pickNames(); } - //TODO is this name good? + /// Performs additional processing on the current definition to simplify, + /// reduce, etc. @protected @visibleForOverriding void process() { diff --git a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart index 7b387fd13..dd3efae00 100644 --- a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart +++ b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart @@ -1,3 +1,12 @@ +// Copyright (C) 2021-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// synth_sub_module_instantiation.dart +// Definitions for a submodule instantiations. +// +// 2025 June +// Author: Max Korbel + import 'dart:collection'; import 'package:rohd/rohd.dart'; @@ -67,6 +76,7 @@ class SynthSubModuleInstantiation { UnmodifiableMapView(_inOutMapping); final Map _inOutMapping = {}; + /// Adds an inOut mapping from [name] to [synthLogic]. void setInOutMapping(String name, SynthLogic synthLogic, {bool replace = false}) { assert(module.inOuts.containsKey(name), diff --git a/lib/src/synthesizers/utilities/utilities.dart b/lib/src/synthesizers/utilities/utilities.dart index 50ce7aaaf..c3cccdf32 100644 --- a/lib/src/synthesizers/utilities/utilities.dart +++ b/lib/src/synthesizers/utilities/utilities.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Intel Corporation +// Copyright (C) 2024-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'synth_assignment.dart'; From fd6c0cbe84d27c055df87edabfb2946ec1a6257a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 18:29:39 -0700 Subject: [PATCH 11/24] update docs a bit --- doc/user_guide/_docs/A21-generation.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/user_guide/_docs/A21-generation.md b/doc/user_guide/_docs/A21-generation.md index e401e94ae..00d3d25bb 100644 --- a/doc/user_guide/_docs/A21-generation.md +++ b/doc/user_guide/_docs/A21-generation.md @@ -5,7 +5,7 @@ last_modified_at: 2023-11-13 toc: true --- -Hardware in ROHD is convertible to an output format via `Synthesizer`s, the most popular of which is SystemVerilog. Hardware in ROHD can be converted to logically equivalent, human readable SystemVerilog with structure, hierarchy, ports, and names maintained. +Hardware in ROHD is convertible to an output format via `Synthesizer`s, the most popular of which is SystemVerilog. Hardware in ROHD can be converted to logically equivalent, human-readable SystemVerilog with structure, hierarchy, ports, and names maintained. The simplest way to generate SystemVerilog is with the helper method `generateSynth` in `Module`: @@ -38,7 +38,7 @@ Port names are always maintained exactly in generated SystemVerilog, so they mus - The `definitionName`, which maps to the name of the module declaration in SystemVerilog. - If you want to ensure this does not change (e.g. uniquified because multiple different declarations have the same `definitionname`), set `reserveDefinitionName` to `true`. -- The `name`, which maps to the instance name when that instance is instanitated as a sub-module of another module. +- The `name`, which maps to the instance name when that instance is instantiated as a sub-module of another module. - If you want to ensure this does not change (e.g. uniquified because other signals or sub-modules would have the same name), then set `reserveName` to `true`. ### Internal signals @@ -46,9 +46,9 @@ Port names are always maintained exactly in generated SystemVerilog, so they mus Internal signals, unlike ports, don't need to always have the same exact name as in the original hardware definition. - If you do not name a signal, it will get a default name. Generated code will attempt to avoid keeping that intermediate signal around (declared) if possible. -- If you do name a signal, by default it will be characterized as `renameable`. This means it will try to keep that name in generated output, but may rename it for uniqification purposes. +- If you do name a signal, by default it will be characterized as `renameable`. This means it will try to keep that name in generated output, but may rename it for uniquification purposes. - If you want to make sure an internal signal maintains exactly the name you want, you can mark it explicitly with `reserved`. -- You can downgrade a named signal as well to `mergeable` or even `unnamed`, if you care less about it's name in generated outputs and prefer that others will take over. +- You can downgrade a named signal as well to `mergeable` or even `unnamed`, if you care less about its name in generated outputs and prefer that others will take over. ### Unpreferred names @@ -56,4 +56,4 @@ The `Naming.unpreferredName` function will modify a signal name to indicate to d ## More advanced generation -Under the hood of `generateSynth`, it's actually using a [`SynthBuilder`](https://intel.github.io/rohd/rohd/SynthBuilder-class.html) which accepts a `Module` and a `Synthesizer` (usually a `SystemVerilogSynthesizer`) as arguments. This `SynthBuilder` can provide a collection of `String` file contents via `getFileContents`, or you can ask for the full set of `synthesisResults`, which contains `SynthesisResult`s which can each be converted `toFileContents` but also has context about the `module` it refers to, the `instanceTypeName`, etc. With these APIs, you can easily generate named files, add file headers, ignore generation of some modules, generate file lists for other tools, etc. +Under the hood of `generateSynth`, it's actually using a [`SynthBuilder`](https://intel.github.io/rohd/rohd/SynthBuilder-class.html) which accepts a `Module` and a `Synthesizer` (usually a `SystemVerilogSynthesizer`) as arguments. This `SynthBuilder` can provide a collection of `String` file contents via `getFileContents`, or you can ask for the full set of `synthesisResults`, which contains `SynthesisResult`s which can each be converted `toSynthFileContents` but also has context about the `module` it refers to, the `instanceTypeName`, etc. With these APIs, you can easily generate named files, add file headers, ignore generation of some modules, generate file lists for other tools, etc. The `SynthBuilder.multi` constructor makes it convenient to generate outputs for multiple independent hierarchies. From bc9346738461626f1be1b254cc3f513375111296 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 24 Jun 2025 18:43:38 -0700 Subject: [PATCH 12/24] tweaks to docs --- doc/user_guide/_docs/A20-logic-arrays.md | 4 ++-- lib/src/synthesizers/synth_builder.dart | 20 +++++++++---------- .../utilities/synth_assignment.dart | 2 ++ .../synthesizers/utilities/synth_logic.dart | 6 ++++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/doc/user_guide/_docs/A20-logic-arrays.md b/doc/user_guide/_docs/A20-logic-arrays.md index 3ce96c16e..5650f48c4 100644 --- a/doc/user_guide/_docs/A20-logic-arrays.md +++ b/doc/user_guide/_docs/A20-logic-arrays.md @@ -61,8 +61,8 @@ selectIndexValueArrayA <= arrayA.elements.selectIndex(id, defaultValue: defaultV selectFromValueArrayA <= id.selectFrom(arrayA.elements, defaultValue: defaultValue); ``` -An example code is given to demonstrate a usage of selectIndex and selectFrom for logic arrays. -Please see code here: [logic_array.dart](https://github.com/intel/rohd/blob/main/example) +An example code is given to demonstrate a usage of `selectIndex` and `selectFrom` for logic arrays. +Please see code here: [logic_array.dart](https://github.com/intel/rohd/blob/main/example/logic_array.dart) ### 2. Using a list of `Logic` elements diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart index 7ad79b9d5..54e312ab3 100644 --- a/lib/src/synthesizers/synth_builder.dart +++ b/lib/src/synthesizers/synth_builder.dart @@ -27,8 +27,8 @@ class SynthBuilder { /// The [Synthesizer] to use for generating an output. final Synthesizer synthesizer; - /// A [Map] from instances of [Module]s to the type that should represent - /// them in the synthesized output. + /// A [Map] from instances of [Module]s to the type that should represent them + /// in the synthesized output. final Map _moduleToInstanceTypeMap = {}; /// All the [SynthesisResult]s generated by this [SynthBuilder]. @@ -41,13 +41,13 @@ class SynthBuilder { /// [Uniquifier] for instance type names. final Uniquifier _instanceTypeUniquifier = Uniquifier(); - /// Constructs a [SynthBuilder] based on the [top] module and - /// using [synthesizer] for generating outputs. + /// Constructs a [SynthBuilder] based on the [top] module and using + /// [synthesizer] for generating outputs. SynthBuilder(Module top, Synthesizer synthesizer) : this.multi([top], synthesizer); - /// Constructs a [SynthBuilder] based on the provided [tops] modules and - /// using [synthesizer] for generating outputs. + /// Constructs a [SynthBuilder] based on the provided [tops] modules and using + /// [synthesizer] for generating outputs. SynthBuilder.multi(List tops, this.synthesizer) : tops = List.unmodifiable(tops) { for (final top in tops) { @@ -72,8 +72,8 @@ class SynthBuilder { .forEach(_getInstanceType); } - /// Collects a [List] of [String]s representing file contents generated by - /// the [synthesizer]. + /// Collects a [List] of [String]s representing file contents generated by the + /// [synthesizer]. @Deprecated('Use `getSynthFileContents()` instead.') List getFileContents() => synthesisResults .map((synthesisResult) => synthesisResult.toFileContents()) @@ -88,8 +88,8 @@ class SynthBuilder { /// Provides an instance type name for [module]. /// - /// If a name already exists for [module], it will return the same one. - /// If another [Module] is equivalent (as determined by comparing the + /// If a name already exists for [module], it will return the same one. If + /// another [Module] is equivalent (as determined by comparing the /// [SynthesisResult]s), they will both get the same name. String _getInstanceType(Module module) { if (!synthesizer.generatesDefinition(module)) { diff --git a/lib/src/synthesizers/utilities/synth_assignment.dart b/lib/src/synthesizers/utilities/synth_assignment.dart index 7ccef1873..7f2cc8b72 100644 --- a/lib/src/synthesizers/utilities/synth_assignment.dart +++ b/lib/src/synthesizers/utilities/synth_assignment.dart @@ -11,6 +11,7 @@ import 'package:rohd/src/synthesizers/utilities/utilities.dart'; /// Represents an assignment between two signals. class SynthAssignment { + /// The initial destination. SynthLogic _dst; /// The destination being driven by this assignment. @@ -24,6 +25,7 @@ class SynthAssignment { return _dst; } + /// The initial source. SynthLogic _src; /// The source driving in this assignment. diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 4d2e45f49..182774cf8 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -31,11 +31,12 @@ class SynthLogic { _replacement = newReplacement; } + /// The direct replacement of this [SynthLogic]. + SynthLogic? _replacement; + /// The width of any/all of the [logics]. int get width => logics.first.width; - SynthLogic? _replacement; - /// Indicates that this has a reserved name. bool get isReserved => _reservedLogic != null; @@ -95,6 +96,7 @@ class SynthLogic { return _name!; } + /// The name of this, if it has been picked. String? _name; /// Picks a [name]. From 0fa161f2af7f97af480fbfeb118f02b93c97cfa9 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 25 Jun 2025 14:32:18 -0700 Subject: [PATCH 13/24] wip enum sv gen --- lib/src/signals/logic_enum.dart | 2 ++ .../systemverilog/systemverilog_synthesis_result.dart | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index ad0041bc3..6f66ef54b 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -4,6 +4,8 @@ part of 'signals.dart'; // - by default, throw an exception if assignment between enums of different mappings? // TODO: should we support enum ports? then where does the typedef live? +//TODO: do we need to support arrays of enums? + class LogicEnum extends Logic { //TODO: if a `put` has an illegal value prop X diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 069f3e31a..cb063ee59 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -17,6 +17,7 @@ import 'package:rohd/src/synthesizers/utilities/utilities.dart'; extension on SynthLogic { /// Gets the SystemVerilog type for this signal. String definitionType() => isNet ? 'wire' : 'logic'; + //TODO: what about typedef types here? } /// A [SynthesisResult] representing a [Module] that provides a custom @@ -178,6 +179,9 @@ class SystemVerilogSynthesisResult extends SynthesisResult { return subModuleLines.join('\n'); } + /// Internal `typedef` definitions for this module. + String _verilogTypedefs() {} + /// The contents of this module converted to SystemVerilog without module /// declaration, ports, etc. String _verilogModuleContents( From d8c9fabe1aa0642c06a37525ffc375d0bf285d77 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 25 Jun 2025 14:33:22 -0700 Subject: [PATCH 14/24] try a new runner --- .github/workflows/general.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 48215e8e1..a4121f0b1 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -16,7 +16,7 @@ jobs: name: Run Checks permissions: {} timeout-minutes: 60 - runs-on: ${{ github.repository_owner == 'intel' && 'intel-ubuntu-latest' || 'ubuntu-latest' }} + runs-on: 'jf5-ubuntu-latest' #${{ github.repository_owner == 'intel' && 'intel-ubuntu-latest' || 'ubuntu-latest' }} steps: - name: Checkout uses: actions/checkout@v4 From 2d1b65e140433629e3bf94a14190fbfe8a22c523 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Fri, 27 Jun 2025 16:48:14 -0700 Subject: [PATCH 15/24] wip implementing enum stuff --- lib/src/finite_state_machine.dart | 16 ++-- lib/src/signals/logic_def.dart | 20 +++++ lib/src/signals/logic_enum.dart | 76 +++++++++++++++++-- lib/src/signals/signals.dart | 1 + .../systemverilog_synthesis_result.dart | 3 +- .../utilities/synth_enum_definition.dart | 33 ++++++++ .../synthesizers/utilities/synth_logic.dart | 73 +++++++++++++++++- .../utilities/synth_module_definition.dart | 18 ++--- 8 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 lib/src/signals/logic_def.dart create mode 100644 lib/src/synthesizers/utilities/synth_enum_definition.dart diff --git a/lib/src/finite_state_machine.dart b/lib/src/finite_state_machine.dart index 689f7fabc..197cd0097 100644 --- a/lib/src/finite_state_machine.dart +++ b/lib/src/finite_state_machine.dart @@ -15,13 +15,13 @@ import 'package:rohd/rohd.dart'; /// Deprecated: use [FiniteStateMachine] instead. @Deprecated('Use FiniteStateMachine instead') -typedef StateMachine = FiniteStateMachine; +typedef StateMachine = FiniteStateMachine; /// Simple class for FSM [FiniteStateMachine]. /// /// Abstraction for representing Finite state machines (FSM). /// Contains the logic for performing the state transitions. -class FiniteStateMachine { +class FiniteStateMachine { /// List of all the [State]s in this machine. List> get states => UnmodifiableListView(_states); final List> _states; @@ -93,6 +93,10 @@ class FiniteStateMachine { /// If `true`, the [reset] signal is asynchronous. final bool asyncReset; + LogicEnum stateEnum({String? name}) => + LogicEnum.withMapping(stateIndexLookup, + name: name, definitionName: StateIdentifier.runtimeType.toString()); + /// Creates an finite state machine for the specified list of [_states], with /// an initial state of [resetState] (when synchronous [reset] is high) and /// transitions on positive [clk] edges. @@ -120,6 +124,7 @@ class FiniteStateMachine { List setupActions = const [], }) : setupActions = List.unmodifiable(setupActions), stateWidth = _logBase(_states.length, 2), + //TODO currentState and nextState should be LogicEnum currentState = Logic(name: 'currentState', width: _logBase(_states.length, 2)), nextState = @@ -138,8 +143,9 @@ class FiniteStateMachine { currentState, _states .map((state) => CaseItem( - Const(_stateValueLookup[state], width: stateWidth) - .named(state.identifier.toString()), + stateEnum()..getsEnum(state.identifier), + // Const(_stateValueLookup[state], width: stateWidth) + // .named(state.identifier.toString()), [ ...state.actions, Case( @@ -226,7 +232,7 @@ class FiniteStateMachine { } /// Simple class to initialize each state of the FSM. -class State { +class State { /// Identifier or name of the state. final StateIdentifier identifier; diff --git a/lib/src/signals/logic_def.dart b/lib/src/signals/logic_def.dart new file mode 100644 index 000000000..e992c0612 --- /dev/null +++ b/lib/src/signals/logic_def.dart @@ -0,0 +1,20 @@ +part of 'signals.dart'; + +@internal +sealed class LogicDef extends Logic { + final bool reserveDefinitionName; + + // TODO: test naming conflicts in generated RTL + String get definitionName => + Sanitizer.sanitizeSV(_definitionName ?? runtimeType.toString()); + final String? _definitionName; + + LogicDef({ + super.width, + super.name, + super.naming, + String? definitionName, + this.reserveDefinitionName = false, + }) : _definitionName = Naming.validatedName(definitionName, + reserveName: reserveDefinitionName); +} diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index 6f66ef54b..906bc0d50 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -6,7 +6,9 @@ part of 'signals.dart'; //TODO: do we need to support arrays of enums? -class LogicEnum extends Logic { +//TODO: how do you define a constant version of an enum? + +class LogicEnum extends LogicDef { //TODO: if a `put` has an illegal value prop X late final Map mapping; @@ -16,7 +18,8 @@ class LogicEnum extends Logic { T get valueEnum => mapping.entries .firstWhere((entry) => entry.value == value, - orElse: () => throw StateError('Value $value does not co.')) + orElse: () => throw StateError( + 'Value $value does not correspond to any enum in $mapping')) .key; static Map _computeMapping( @@ -95,9 +98,21 @@ class LogicEnum extends Logic { return width; } - LogicEnum.withMapping(Map mapping, - {int? width, super.name, super.naming}) - : super(width: _computeWidth(requestedWidth: width, mapping: mapping)) { + /// TODO + /// + /// If [reserveDefinitionName] is true, then the enum names will be reserved + /// as well. + LogicEnum.withMapping( + Map mapping, { + int? width, + super.name, + super.naming, + String? definitionName, + super.reserveDefinitionName, + }) : super( + width: _computeWidth(requestedWidth: width, mapping: mapping), + definitionName: Naming.validatedName(T.runtimeType.toString(), + reserveName: reserveDefinitionName)) { this.mapping = Map.unmodifiable(_computeMapping(mapping: mapping, width: this.width)); @@ -115,13 +130,43 @@ class LogicEnum extends Logic { }); } - LogicEnum(List values, {int? width, String? name, Naming? naming}) + LogicEnum(List values, + {int? width, + String? name, + Naming? naming, + String? definitionName, + bool reserveDefinitionName = false}) : this.withMapping( Map.fromEntries( values.mapIndexed((index, value) => MapEntry(value, index))), width: width, name: name, - naming: naming); + naming: naming, + definitionName: definitionName, + reserveDefinitionName: reserveDefinitionName); + + // TODO: do we really need to track this isConst? + bool _isConst = false; + bool get isConst => _isConst; + + /// Drives this [LogicEnum] with a constant value matching the enum [value]. + void getsEnum(T value) { + gets(Const(mapping[value])); + } + + @override + void gets(Logic other) { + if (other is Const) { + if (!mapping.containsValue(other.value)) { + throw Exception( + 'Value ${other.value} is not mapped in $mapping for enum $T.'); + } + + _isConst = true; + } + + super.gets(other); + } @override void put(dynamic val, {bool fill = false}) { @@ -141,6 +186,23 @@ class LogicEnum extends Logic { } } + bool isEquivalentTypeTo(Logic other) { + if (other is! LogicEnum) { + return false; + } + + final mappingsEqual = const DeepCollectionEquality.unordered().equals( + mapping, + other.mapping, + ); + + if (!mappingsEqual) { + return false; + } + + return true; + } + //TODO: clone //TODO need to update the Wire to have "restrictions" on legal values diff --git a/lib/src/signals/signals.dart b/lib/src/signals/signals.dart index 98a4f5f4f..8971c04c0 100644 --- a/lib/src/signals/signals.dart +++ b/lib/src/signals/signals.dart @@ -24,3 +24,4 @@ part 'logic_structure.dart'; part 'logic_array.dart'; part 'logic_net.dart'; part 'logic_enum.dart'; +part 'logic_def.dart'; diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index cb063ee59..90091431e 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -153,6 +153,7 @@ class SystemVerilogSynthesisResult extends SynthesisResult { 'Net connections should have been implemented as' ' bidirectional net connections.'); + // TODO: if we have an enum assigned to a constant, then use enum! assignmentLines .add('assign ${assignment.dst.name} = ${assignment.src.name};'); } @@ -180,7 +181,7 @@ class SystemVerilogSynthesisResult extends SynthesisResult { } /// Internal `typedef` definitions for this module. - String _verilogTypedefs() {} + // String _verilogTypedefs() {} // TODO typedef stuff /// The contents of this module converted to SystemVerilog without module /// declaration, ports, etc. diff --git a/lib/src/synthesizers/utilities/synth_enum_definition.dart b/lib/src/synthesizers/utilities/synth_enum_definition.dart new file mode 100644 index 000000000..5451a7829 --- /dev/null +++ b/lib/src/synthesizers/utilities/synth_enum_definition.dart @@ -0,0 +1,33 @@ +import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/utilities/uniquifier.dart'; + +@immutable +class SynthEnumDefinition { + final LogicEnum characteristicEnum; + + final String definitionName; + final Map enumToNameMapping; + + SynthEnumDefinition(this.characteristicEnum, Uniquifier identifierUniquifier) + : definitionName = identifierUniquifier.getUniqueName( + initialName: characteristicEnum.definitionName, + reserved: characteristicEnum.reserveDefinitionName), + enumToNameMapping = Map.unmodifiable(characteristicEnum.mapping.map( + (key, value) => MapEntry( + key, + identifierUniquifier.getUniqueName( + initialName: key.name, + reserved: characteristicEnum.reserveDefinitionName), + ), + )); +} + +@immutable +class SynthEnumDefinitionKey { + //TODO: finish up this key as a lookup key for SynthEnumDefinition + final Map enumMapping; + SynthEnumDefinitionKey(LogicEnum characteristicEnum) + : enumMapping = Map.unmodifiable(characteristicEnum.mapping); +} diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 182774cf8..8b3745869 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -46,6 +46,12 @@ class SynthLogic { /// The [Logic] whose name is renameable, if there is one. Logic? _renameableLogic; + /// A [LogicEnum] that is characteristic of any merged [LogicEnum]s into this. + LogicEnum? get characteristicEnum => _firstEnum; + + /// The first [LogicEnum] merged into this [SynthLogic], if there is one. + LogicEnum? _firstEnum; + /// [Logic]s that are marked mergeable. final Set _mergeableLogics = {}; @@ -68,6 +74,11 @@ class SynthLogic { // can just look at the first since nets and non-nets cannot be merged logics.first.isNet || (isArray && (logics.first as LogicArray).isNet); + /// Whether this represents an enum. + bool get isEnum => + // can just look at the first since enums and non-enums cannot be merged + logics.first is LogicEnum; + /// If set, then this should never pick the constant as the name. bool get constNameDisallowed => _constNameDisallowed; bool _constNameDisallowed; @@ -106,14 +117,20 @@ class SynthLogic { assert(_name == null, 'Should only pick a name once.'); _name = _findName(uniquifier); + //TODO: dont allow merge after name picked? } /// Finds the best name from the collection of [Logic]s. String _findName(Uniquifier uniquifier) { // check for const - if (_constLogic != null) { + if (isConstant) { if (!_constNameDisallowed) { - return _constLogic!.value.toString(); + if (isEnum) { + // TODO: here is where we need to pring name of enum! + // return charachteristicEnum!.mapping[_constLogic] + } else { + return _constLogic!.value.toString(); + } } else { assert( logics.length > 1, @@ -122,6 +139,8 @@ class SynthLogic { } } + //TODO: for enums, all the value names must be unique in the scope as well! + // check for reserved if (_reservedLogic != null) { return uniquifier.getUniqueName( @@ -199,6 +218,44 @@ class SynthLogic { return null; } + if (a.isEnum || b.isEnum) { + // do not merge enums with non-enums (except for constants) + final oneIsConst = a.isConstant || b.isConstant; + + if (oneIsConst) { + // check to make sure the constant is legal for the enum, otherwise it + // will generate illegal verilog + //TODO: test this scenario! + + final theConst = a.isConstant ? a : b; + final theEnum = a.isEnum ? a : b; + assert(theConst != theEnum, + 'Const and enum should be different SynthLogics.'); + + final constVal = theConst._constLogic!.value; + final enumMapping = theEnum.characteristicEnum!.mapping; + if (!enumMapping.values.contains(constVal)) { + //TODO: better exceptions + throw Exception('Assignment of $constVal to enum' + ' with mapping $enumMapping is not legal.'); + } + } else { + // if not a const scenario, check enum rules + if (a.isEnum != b.isEnum) { + return null; + } + + final aEnum = a.logics.first as LogicEnum; + final bEnum = b.logics.first as LogicEnum; + // if the enums are incompatible, do not merge + if (!aEnum.isEquivalentTypeTo(bEnum)) { + return null; + } + } + + // otherwise, continue on with normal merging flow + } + if (b.mergeable) { a.adopt(b); return b; @@ -222,6 +279,10 @@ class SynthLogic { assert(other.mergeable || _constantsMergeable(this, other), 'Cannot merge a non-mergeable into this.'); assert(other.isArray == isArray, 'Cannot merge arrays and non-arrays'); + assert( + _name == null, 'Cannot merge into this after a name has been picked.'); + assert(other._name == null, + 'Cannot merge into other after a name has been picked.'); _constNameDisallowed |= other._constNameDisallowed; @@ -229,6 +290,7 @@ class SynthLogic { _constLogic ??= other._constLogic; _reservedLogic ??= other._reservedLogic; _renameableLogic ??= other._renameableLogic; + _firstEnum ??= other._firstEnum; // the rest, take them all _mergeableLogics.addAll(other._mergeableLogics); @@ -255,6 +317,13 @@ class SynthLogic { _unnamedLogics.add(logic); } } + + if (logic is LogicEnum) { + assert(characteristicEnum?.isEquivalentTypeTo(logic) ?? true, + 'Cannot add a LogicEnum that is not equivalent to the existing one.'); + + _firstEnum ??= logic; + } } @override diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index d92df06cf..1d733cf5d 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -79,7 +79,7 @@ class SynthModuleDefinition { /// Used to uniquify any identifiers, including signal names /// and module instances. - final Uniquifier _synthInstantiationNameUniquifier; + final Uniquifier _synthIdentifierUniquifier; /// Either accesses a previously created [SynthLogic] corresponding to /// [logic], or else creates a new one and adds it to the [logicToSynthMap]. @@ -132,7 +132,7 @@ class SynthModuleDefinition { /// Creates a new definition representation for this [module]. SynthModuleDefinition(this.module) - : _synthInstantiationNameUniquifier = Uniquifier( + : _synthIdentifierUniquifier = Uniquifier( reservedNames: { ...module.inputs.keys, ...module.outputs.keys, @@ -355,20 +355,20 @@ class SynthModuleDefinition { void _pickNames() { // first ports get priority for (final input in inputs) { - input.pickName(_synthInstantiationNameUniquifier); + input.pickName(_synthIdentifierUniquifier); } for (final output in outputs) { - output.pickName(_synthInstantiationNameUniquifier); + output.pickName(_synthIdentifierUniquifier); } for (final inOut in inOuts) { - inOut.pickName(_synthInstantiationNameUniquifier); + inOut.pickName(_synthIdentifierUniquifier); } // pick names of *reserved* submodule instances final nonReservedSubmodules = []; for (final submodule in moduleToSubModuleInstantiationMap.values) { if (submodule.module.reserveName) { - submodule.pickName(_synthInstantiationNameUniquifier); + submodule.pickName(_synthIdentifierUniquifier); assert(submodule.module.name == submodule.name, 'Expect reserved names to retain their name.'); } else { @@ -380,7 +380,7 @@ class SynthModuleDefinition { final nonReservedSignals = []; for (final signal in internalSignals) { if (signal.isReserved) { - signal.pickName(_synthInstantiationNameUniquifier); + signal.pickName(_synthIdentifierUniquifier); } else { nonReservedSignals.add(signal); } @@ -389,12 +389,12 @@ class SynthModuleDefinition { // then submodule instances for (final submodule in nonReservedSubmodules.where((element) => element.needsDeclaration)) { - submodule.pickName(_synthInstantiationNameUniquifier); + submodule.pickName(_synthIdentifierUniquifier); } // then the rest of the internal signals for (final signal in nonReservedSignals) { - signal.pickName(_synthInstantiationNameUniquifier); + signal.pickName(_synthIdentifierUniquifier); } } From 8177d88f9759a917f4e8088144628ca4680d064c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 30 Jun 2025 11:11:17 -0700 Subject: [PATCH 16/24] first basic test passing with enum sorta --- lib/src/finite_state_machine.dart | 15 +++---- lib/src/signals/logic_enum.dart | 6 ++- .../systemverilog_synthesis_result.dart | 26 +++++++++-- .../utilities/synth_enum_definition.dart | 25 ++++++++++- .../synthesizers/utilities/synth_logic.dart | 44 +++++++++++++++++-- .../utilities/synth_module_definition.dart | 37 ++++++++++++++++ test/fsm_test.dart | 2 +- 7 files changed, 135 insertions(+), 20 deletions(-) diff --git a/lib/src/finite_state_machine.dart b/lib/src/finite_state_machine.dart index 197cd0097..27f9aabd0 100644 --- a/lib/src/finite_state_machine.dart +++ b/lib/src/finite_state_machine.dart @@ -71,7 +71,8 @@ class FiniteStateMachine { /// /// Use [getStateIndex] to map from a [StateIdentifier] to the value on this /// bus. - final Logic currentState; + late final LogicEnum currentState = + stateEnum(name: 'currentState'); /// A [List] of [Conditional] actions to perform at the beginning of the /// evaluation of actions for the [FiniteStateMachine]. This is useful for @@ -82,7 +83,8 @@ class FiniteStateMachine { /// /// Use [getStateIndex] to map from a [StateIdentifier] to the value on this /// bus. - final Logic nextState; + late final LogicEnum nextState = + stateEnum(name: 'nextState'); /// Returns a ceiling on the log of [x] base [base]. static int _logBase(num x, num base) => (log(x) / log(base)).ceil(); @@ -95,7 +97,7 @@ class FiniteStateMachine { LogicEnum stateEnum({String? name}) => LogicEnum.withMapping(stateIndexLookup, - name: name, definitionName: StateIdentifier.runtimeType.toString()); + name: name, definitionName: StateIdentifier.toString()); /// Creates an finite state machine for the specified list of [_states], with /// an initial state of [resetState] (when synchronous [reset] is high) and @@ -123,12 +125,7 @@ class FiniteStateMachine { this.asyncReset = false, List setupActions = const [], }) : setupActions = List.unmodifiable(setupActions), - stateWidth = _logBase(_states.length, 2), - //TODO currentState and nextState should be LogicEnum - currentState = - Logic(name: 'currentState', width: _logBase(_states.length, 2)), - nextState = - Logic(name: 'nextState', width: _logBase(_states.length, 2)) { + stateWidth = _logBase(_states.length, 2) { _validate(); var stateCounter = 0; diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index 906bc0d50..0e04424c7 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -151,6 +151,10 @@ class LogicEnum extends LogicDef { /// Drives this [LogicEnum] with a constant value matching the enum [value]. void getsEnum(T value) { + if (!mapping.containsKey(value)) { + //TODO exception + throw Exception('Value $value is not mapped in $mapping for enum $T.'); + } gets(Const(mapping[value])); } @@ -191,7 +195,7 @@ class LogicEnum extends LogicDef { return false; } - final mappingsEqual = const DeepCollectionEquality.unordered().equals( + final mappingsEqual = const MapEquality().equals( mapping, other.mapping, ); diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index 90091431e..a12177a3a 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -11,13 +11,28 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart'; import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synth_sub_module_instantiation.dart'; +import 'package:rohd/src/synthesizers/utilities/synth_enum_definition.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; /// Extra utilities on [SynthLogic] to help with SystemVerilog synthesis. extension on SynthLogic { /// Gets the SystemVerilog type for this signal. - String definitionType() => isNet ? 'wire' : 'logic'; - //TODO: what about typedef types here? + String definitionType() => isEnum + ? enumDefinition!.definitionName + : isNet + ? 'wire' + : 'logic'; +} + +extension on SynthEnumDefinition { + String toSystemVerilogTypedef() { + final enumName = definitionName; + final enumType = 'logic [${characteristicEnum.width - 1}:0]'; + final enumValues = enumToNameMapping.entries + .map((e) => '${e.value} = ${characteristicEnum.mapping[e.key]}') + .join(', '); + return 'typedef enum $enumType { $enumValues } $enumName;'; + } } /// A [SynthesisResult] representing a [Module] that provides a custom @@ -181,13 +196,18 @@ class SystemVerilogSynthesisResult extends SynthesisResult { } /// Internal `typedef` definitions for this module. - // String _verilogTypedefs() {} // TODO typedef stuff + String _verilogTypedefs() => _enumTypeDefs(); + + String _enumTypeDefs() => _synthModuleDefinition.enumDefinitions + .map((e) => e.toSystemVerilogTypedef()) + .join('\n'); /// The contents of this module converted to SystemVerilog without module /// declaration, ports, etc. String _verilogModuleContents( String Function(Module module) getInstanceTypeOfModule) => [ + _verilogTypedefs(), _verilogInternalSignals(), _verilogAssignments(), // order matters! _verilogSubModuleInstantiations(getInstanceTypeOfModule), diff --git a/lib/src/synthesizers/utilities/synth_enum_definition.dart b/lib/src/synthesizers/utilities/synth_enum_definition.dart index 5451a7829..a18bdb9f0 100644 --- a/lib/src/synthesizers/utilities/synth_enum_definition.dart +++ b/lib/src/synthesizers/utilities/synth_enum_definition.dart @@ -11,6 +11,7 @@ class SynthEnumDefinition { final Map enumToNameMapping; SynthEnumDefinition(this.characteristicEnum, Uniquifier identifierUniquifier) + //TODO: sanitization! : definitionName = identifierUniquifier.getUniqueName( initialName: characteristicEnum.definitionName, reserved: characteristicEnum.reserveDefinitionName), @@ -27,7 +28,27 @@ class SynthEnumDefinition { @immutable class SynthEnumDefinitionKey { //TODO: finish up this key as a lookup key for SynthEnumDefinition - final Map enumMapping; + final Map enumMapping; + final String? reservedName; SynthEnumDefinitionKey(LogicEnum characteristicEnum) - : enumMapping = Map.unmodifiable(characteristicEnum.mapping); + : enumMapping = characteristicEnum.mapping, + reservedName = characteristicEnum.reserveDefinitionName + ? characteristicEnum.definitionName + : null; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + + return other is SynthEnumDefinitionKey && + const MapEquality() + .equals(other.enumMapping, enumMapping) && + other.reservedName == reservedName; + } + + @override + int get hashCode => + const MapEquality().hash(enumMapping) ^ + reservedName.hashCode; } diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 8b3745869..71f22b34f 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -9,6 +9,7 @@ import 'package:collection/collection.dart'; import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/utilities/synth_enum_definition.dart'; import 'package:rohd/src/utilities/sanitizer.dart'; import 'package:rohd/src/utilities/uniquifier.dart'; @@ -47,10 +48,20 @@ class SynthLogic { Logic? _renameableLogic; /// A [LogicEnum] that is characteristic of any merged [LogicEnum]s into this. - LogicEnum? get characteristicEnum => _firstEnum; + LogicEnum? get characteristicEnum => _characteristicEnum; /// The first [LogicEnum] merged into this [SynthLogic], if there is one. - LogicEnum? _firstEnum; + LogicEnum? _characteristicEnum; + + SynthEnumDefinition? get enumDefinition => _enumDefinition; + set enumDefinition(SynthEnumDefinition? definition) { + assert(definition != null, 'Cannot set enum definition to null.'); + assert( + _enumDefinition == null, 'Cannot set enum definition more than once.'); + _enumDefinition = definition; + } + + SynthEnumDefinition? _enumDefinition; /// [Logic]s that are marked mergeable. final Set _mergeableLogics = {}; @@ -251,6 +262,14 @@ class SynthLogic { if (!aEnum.isEquivalentTypeTo(bEnum)) { return null; } + + if (aEnum.reserveDefinitionName && + bEnum.reserveDefinitionName && + aEnum.definitionName != bEnum.definitionName) { + // if both enums reserve their definition names, and they are + // different, then we cannot merge + return null; + } } // otherwise, continue on with normal merging flow @@ -290,7 +309,7 @@ class SynthLogic { _constLogic ??= other._constLogic; _reservedLogic ??= other._reservedLogic; _renameableLogic ??= other._renameableLogic; - _firstEnum ??= other._firstEnum; + _characteristicEnum ??= other._characteristicEnum; // the rest, take them all _mergeableLogics.addAll(other._mergeableLogics); @@ -322,7 +341,20 @@ class SynthLogic { assert(characteristicEnum?.isEquivalentTypeTo(logic) ?? true, 'Cannot add a LogicEnum that is not equivalent to the existing one.'); - _firstEnum ??= logic; + if (logic.reserveDefinitionName) { + // if the added `logic` reserves its definition name, then we + // should use it as the characteristic enum + assert( + _characteristicEnum == null || + !_characteristicEnum!.reserveDefinitionName || + logic.definitionName == _characteristicEnum!.definitionName, + 'Cannot add a LogicEnum that reserves its definition name, but has a ' + 'different definition name than the existing characteristic enum.', + ); + _characteristicEnum = logic; + } + + _characteristicEnum ??= logic; } } @@ -342,6 +374,10 @@ class SynthLogic { /// Computes the name of the signal at declaration time with appropriate /// dimensions included. String definitionName() { + if (isEnum) { + return name; + } + String packedDims; String unpackedDims; diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index 1d733cf5d..d04f4f624 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -12,6 +12,7 @@ import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd/src/collections/traverseable_collection.dart'; +import 'package:rohd/src/synthesizers/utilities/synth_enum_definition.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; import 'package:rohd/src/utilities/uniquifier.dart'; @@ -351,6 +352,29 @@ class SynthModuleDefinition { } } + final Map _enumDefinitions = + {}; + + List get enumDefinitions => + _enumDefinitions.values.toList(growable: false); + + void _pickDefinitionEnumName(SynthLogic synthEnum) { + assert(synthEnum.isEnum, 'Only call this on SynthLogic that is an enum.'); + final key = SynthEnumDefinitionKey(synthEnum.characteristicEnum!); + if (_enumDefinitions.containsKey(key)) { + // already have a definition for this enum + synthEnum.enumDefinition = _enumDefinitions[key]; + } else { + // create a new definition for this enum + final newDefinition = SynthEnumDefinition( + synthEnum.characteristicEnum!, + _synthIdentifierUniquifier, + ); + _enumDefinitions[key] = newDefinition; + synthEnum.enumDefinition = newDefinition; + } + } + /// Picks names of signals and sub-modules. void _pickNames() { // first ports get priority @@ -364,6 +388,16 @@ class SynthModuleDefinition { inOut.pickName(_synthIdentifierUniquifier); } + // pick names of *reserved* definition-type enums + final nonReservedEnumDefs = []; + for (final signal in internalSignals.where((e) => e.isEnum)) { + if (signal.characteristicEnum!.reserveDefinitionName) { + _pickDefinitionEnumName(signal); + } else { + nonReservedEnumDefs.add(signal); + } + } + // pick names of *reserved* submodule instances final nonReservedSubmodules = []; for (final submodule in moduleToSubModuleInstantiationMap.values) { @@ -386,6 +420,9 @@ class SynthModuleDefinition { } } + // then enum definitions that are not reserved + nonReservedEnumDefs.forEach(_pickDefinitionEnumName); + // then submodule instances for (final submodule in nonReservedSubmodules.where((element) => element.needsDeclaration)) { diff --git a/test/fsm_test.dart b/test/fsm_test.dart index b5f010a56..c520eaa56 100644 --- a/test/fsm_test.dart +++ b/test/fsm_test.dart @@ -343,7 +343,7 @@ void main() { }) ]; await SimCompare.checkFunctionalVector(mod, vectors); - SimCompare.checkIverilogVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors, dontDeleteTmpFiles: true); verifyMermaidStateDiagram(_trafficFSMPath); }); From d51be818578133062ba41e9eb7496f934e992cb9 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 30 Jun 2025 11:27:07 -0700 Subject: [PATCH 17/24] basic sv gen testing --- lib/src/finite_state_machine.dart | 3 +- lib/src/signals/logic_enum.dart | 5 +- .../utilities/synth_enum_definition.dart | 8 ++- test/logic_enum_test.dart | 52 +++++++++++++++++-- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/lib/src/finite_state_machine.dart b/lib/src/finite_state_machine.dart index 27f9aabd0..56a8bdd71 100644 --- a/lib/src/finite_state_machine.dart +++ b/lib/src/finite_state_machine.dart @@ -96,8 +96,7 @@ class FiniteStateMachine { final bool asyncReset; LogicEnum stateEnum({String? name}) => - LogicEnum.withMapping(stateIndexLookup, - name: name, definitionName: StateIdentifier.toString()); + LogicEnum.withMapping(stateIndexLookup, name: name); /// Creates an finite state machine for the specified list of [_states], with /// an initial state of [resetState] (when synchronous [reset] is high) and diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index 0e04424c7..2b2049349 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -111,8 +111,9 @@ class LogicEnum extends LogicDef { super.reserveDefinitionName, }) : super( width: _computeWidth(requestedWidth: width, mapping: mapping), - definitionName: Naming.validatedName(T.runtimeType.toString(), - reserveName: reserveDefinitionName)) { + definitionName: definitionName ?? + Naming.validatedName(T.toString(), + reserveName: reserveDefinitionName)) { this.mapping = Map.unmodifiable(_computeMapping(mapping: mapping, width: this.width)); diff --git a/lib/src/synthesizers/utilities/synth_enum_definition.dart b/lib/src/synthesizers/utilities/synth_enum_definition.dart index a18bdb9f0..24f69d30b 100644 --- a/lib/src/synthesizers/utilities/synth_enum_definition.dart +++ b/lib/src/synthesizers/utilities/synth_enum_definition.dart @@ -38,8 +38,12 @@ class SynthEnumDefinitionKey { @override bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other.runtimeType != runtimeType) return false; + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } return other is SynthEnumDefinitionKey && const MapEquality() diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index 37541ac8d..b4b3f34b9 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -5,11 +5,11 @@ import 'package:test/test.dart'; enum TestEnum { a, b, c } class MyListLogicEnum extends LogicEnum { - MyListLogicEnum() : super(TestEnum.values); + MyListLogicEnum({super.name}) : super(TestEnum.values); } class MyMapLogicEnum extends LogicEnum { - MyMapLogicEnum() + MyMapLogicEnum({super.name}) : super.withMapping({ TestEnum.a: 1, // TestEnum.b: 5, // `b` is not mapped! @@ -17,6 +17,24 @@ class MyMapLogicEnum extends LogicEnum { }, width: 3); } +class SimpleModWithEnum extends Module { + SimpleModWithEnum(Logic carrot) { + carrot = addInput('carrot', carrot, width: 3); + final e = MyMapLogicEnum(name: 'elephant'); + addOutput('banana', width: 3) <= carrot & e; + } +} + +class ConflictingEnumMod extends Module { + ConflictingEnumMod(Logic carrot) { + carrot = addInput('carrot', carrot, width: 3); + final e1 = MyListLogicEnum(name: 'elephantList'); + final e2 = MyMapLogicEnum(name: 'elephantMap'); + + addOutput('banana', width: 3) <= carrot & (e1.zeroExtend(3) ^ e2); + } +} + void main() { test('enum populates based on list of values', () { final e = MyListLogicEnum(); @@ -50,9 +68,35 @@ void main() { }); test('enum puts with enums', () { - final e = MyListLogicEnum(); - e.put(TestEnum.b); + final e = MyListLogicEnum()..put(TestEnum.b); expect(e.value.toInt(), TestEnum.b.index); expect(e.valueEnum, TestEnum.b); }); + + test('simple mod with enum gen good sv', () async { + final mod = SimpleModWithEnum(Logic(width: 3)); + await mod.build(); + + final sv = mod.generateSynth(); + + expect(sv, + contains("typedef enum logic [2:0] { a = 3'h1, c = 3'h7 } TestEnum;")); + expect(sv, contains('TestEnum elephant;')); + }); + + test('conflicting enum mod gen good sv', () async { + final mod = ConflictingEnumMod(Logic(width: 3)); + await mod.build(); + + final sv = mod.generateSynth(); + + expect( + sv, + contains('typedef enum logic [1:0]' + " { a = 2'h0, b = 2'h1, c = 2'h2 } TestEnum;")); + expect( + sv, + contains('typedef enum logic [2:0]' + " { a_0 = 3'h1, c_0 = 3'h7 } TestEnum_0;")); + }); } From 09c11cf3538d6e0c567a0b9437d1f79cdd87873f Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 30 Jun 2025 15:29:08 -0700 Subject: [PATCH 18/24] got enum and const merging working better --- .../synthesizers/utilities/synth_logic.dart | 118 ++++++++++-------- test/logic_enum_test.dart | 78 ++++++++---- 2 files changed, 118 insertions(+), 78 deletions(-) diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 71f22b34f..ff8dc63db 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -86,9 +86,7 @@ class SynthLogic { logics.first.isNet || (isArray && (logics.first as LogicArray).isNet); /// Whether this represents an enum. - bool get isEnum => - // can just look at the first since enums and non-enums cannot be merged - logics.first is LogicEnum; + bool get isEnum => characteristicEnum != null; /// If set, then this should never pick the constant as the name. bool get constNameDisallowed => _constNameDisallowed; @@ -137,8 +135,10 @@ class SynthLogic { if (isConstant) { if (!_constNameDisallowed) { if (isEnum) { - // TODO: here is where we need to pring name of enum! - // return charachteristicEnum!.mapping[_constLogic] + return enumDefinition!.enumToNameMapping[characteristicEnum! + .mapping.entries + .firstWhere((e) => e.value == _constLogic!.value) + .key]!; } else { return _constLogic!.value.toString(); } @@ -214,65 +214,26 @@ class SynthLogic { /// Returns the [SynthLogic] that should be *removed*. static SynthLogic? tryMerge(SynthLogic a, SynthLogic b) { + assert(a != b, 'Cannot merge a SynthLogic with itself.'); + if (_constantsMergeable(a, b)) { // case to avoid things like a constant assigned to another constant a.adopt(b); return b; } - if (!a.mergeable && !b.mergeable) { - return null; - } - if (a.isNet != b.isNet) { // do not merge nets with non-nets return null; } - if (a.isEnum || b.isEnum) { - // do not merge enums with non-enums (except for constants) - final oneIsConst = a.isConstant || b.isConstant; - - if (oneIsConst) { - // check to make sure the constant is legal for the enum, otherwise it - // will generate illegal verilog - //TODO: test this scenario! - - final theConst = a.isConstant ? a : b; - final theEnum = a.isEnum ? a : b; - assert(theConst != theEnum, - 'Const and enum should be different SynthLogics.'); - - final constVal = theConst._constLogic!.value; - final enumMapping = theEnum.characteristicEnum!.mapping; - if (!enumMapping.values.contains(constVal)) { - //TODO: better exceptions - throw Exception('Assignment of $constVal to enum' - ' with mapping $enumMapping is not legal.'); - } - } else { - // if not a const scenario, check enum rules - if (a.isEnum != b.isEnum) { - return null; - } - - final aEnum = a.logics.first as LogicEnum; - final bEnum = b.logics.first as LogicEnum; - // if the enums are incompatible, do not merge - if (!aEnum.isEquivalentTypeTo(bEnum)) { - return null; - } - - if (aEnum.reserveDefinitionName && - bEnum.reserveDefinitionName && - aEnum.definitionName != bEnum.definitionName) { - // if both enums reserve their definition names, and they are - // different, then we cannot merge - return null; - } - } + if (_enumAndConstMergeable(a, b)) { + a.adopt(b); + return b; + } - // otherwise, continue on with normal merging flow + if (!a.mergeable && !b.mergeable) { + return null; } if (b.mergeable) { @@ -292,10 +253,61 @@ class SynthLogic { !a._constNameDisallowed && !b._constNameDisallowed; + /// Indicates whether [a] and [b] represent one enum and one constant which + /// can be merged. + static bool _enumAndConstMergeable(SynthLogic a, SynthLogic b) { + final enums = [a, b].where((e) => e.isEnum).toList(growable: false); + final constants = [a, b].where((e) => e.isConstant).toList(growable: false); + + // if no enums, then this is not a reason to merge + if (enums.isEmpty) { + return false; + } + + // if we have two enums, make sure they are compatible + if (enums.length == 2) { + if (!enums[0] + .characteristicEnum! + .isEquivalentTypeTo(enums[1].characteristicEnum!)) { + return false; + } + + if (enums[0].characteristicEnum!.reserveDefinitionName && + enums[1].characteristicEnum!.reserveDefinitionName && + enums[0].characteristicEnum!.definitionName != + enums[1].characteristicEnum!.definitionName) { + return false; + } + + for (final e in enums) { + if (!e.mergeable) { + return false; + } + } + } + + // if one is constant, then ensure the constant is legal + if (constants.isNotEmpty) { + for (final c in constants) { + for (final e in enums) { + final enumMapping = e.characteristicEnum!.mapping; + if (!enumMapping.values.contains(c._constLogic!.value)) { + return false; + } + } + } + } + + return true; + } + /// Merges [other] to be represented by `this` instead, and updates the /// [other] that it has been replaced. void adopt(SynthLogic other) { - assert(other.mergeable || _constantsMergeable(this, other), + assert( + other.mergeable || + _constantsMergeable(this, other) || + _enumAndConstMergeable(this, other), 'Cannot merge a non-mergeable into this.'); assert(other.isArray == isArray, 'Cannot merge arrays and non-arrays'); assert( diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index b4b3f34b9..493272acc 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -35,6 +35,14 @@ class ConflictingEnumMod extends Module { } } +class ModWithEnumConstAssignment extends Module { + ModWithEnumConstAssignment(Logic carrot) { + carrot = addInput('carrot', carrot, width: 2); + final e = MyListLogicEnum(name: 'elephant')..getsEnum(TestEnum.b); + addOutput('banana', width: 2) <= carrot & e; + } +} + void main() { test('enum populates based on list of values', () { final e = MyListLogicEnum(); @@ -73,30 +81,50 @@ void main() { expect(e.valueEnum, TestEnum.b); }); - test('simple mod with enum gen good sv', () async { - final mod = SimpleModWithEnum(Logic(width: 3)); - await mod.build(); - - final sv = mod.generateSynth(); - - expect(sv, - contains("typedef enum logic [2:0] { a = 3'h1, c = 3'h7 } TestEnum;")); - expect(sv, contains('TestEnum elephant;')); - }); - - test('conflicting enum mod gen good sv', () async { - final mod = ConflictingEnumMod(Logic(width: 3)); - await mod.build(); - - final sv = mod.generateSynth(); - - expect( - sv, - contains('typedef enum logic [1:0]' - " { a = 2'h0, b = 2'h1, c = 2'h2 } TestEnum;")); - expect( - sv, - contains('typedef enum logic [2:0]' - " { a_0 = 3'h1, c_0 = 3'h7 } TestEnum_0;")); + group('enum sv gen', () { + test('simple mod with enum gen good sv', () async { + final mod = SimpleModWithEnum(Logic(width: 3)); + await mod.build(); + + final sv = mod.generateSynth(); + + expect( + sv, + contains( + "typedef enum logic [2:0] { a = 3'h1, c = 3'h7 } TestEnum;")); + expect(sv, contains('TestEnum elephant;')); + }); + + test('conflicting enum mod gen good sv', () async { + final mod = ConflictingEnumMod(Logic(width: 3)); + await mod.build(); + + final sv = mod.generateSynth(); + + print(sv); + + // don't care which one has _0, but one of the does! + expect( + sv, + contains( + 'typedef enum logic [2:0]' " { a = 3'h1, c = 3'h7 } TestEnum;")); + expect( + sv, + contains('typedef enum logic [1:0]' + " { a_0 = 2'h0, b = 2'h1, c_0 = 2'h2 } TestEnum_0;")); + }); + + test('enum constant assignment uses enum name', () async { + final mod = ModWithEnumConstAssignment(Logic(width: 2)); + await mod.build(); + + final sv = mod.generateSynth(); + + expect( + sv, + contains('typedef enum logic [1:0]' + " { a = 2'h0, b = 2'h1, c = 2'h2 } TestEnum;")); + expect(sv, contains('assign banana = carrot & b;')); + }); }); } From b645199e641d109d69f4d17f37f7707d2f68d2ee Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 1 Jul 2025 11:25:34 -0700 Subject: [PATCH 19/24] got some basic cond type passing working --- lib/src/module.dart | 11 ++++++ lib/src/modules/conditionals/always.dart | 8 ++++ lib/src/modules/conditionals/conditional.dart | 17 +++++++++ .../conditionals/conditional_assign.dart | 11 ++++-- lib/src/signals/logic_enum.dart | 11 ++++-- ...systemverilog_synth_module_definition.dart | 8 +++- .../synthesizers/utilities/synth_logic.dart | 9 ++++- .../utilities/synth_module_definition.dart | 16 ++++---- .../synth_sub_module_instantiation.dart | 27 ++++++++++++++ test/logic_enum_test.dart | 37 +++++++++++++++++-- 10 files changed, 136 insertions(+), 19 deletions(-) diff --git a/lib/src/module.dart b/lib/src/module.dart index 73cead4b4..9a403d785 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -55,6 +55,17 @@ abstract class Module { /// An internal mapping of inOut names to their sources to this [Module]. late final Map _inOutSources = {}; + /// A mapping between [inputs], [outputs], and/or [inOuts] which must have the + /// same type as each other. The keys of the map will be updated to match the + /// type of the values. + /// + /// This is used for type checking for [LogicEnum]s through [Conditional]s. + /// + /// NOTE: This is for internal usage only, and the API will not be guaranteed + /// to be stable. + @internal + final Map portTypePairs = {}; + /// The parent [Module] of this [Module]. /// /// This only gets populated after its parent [Module], if it exists, has diff --git a/lib/src/modules/conditionals/always.dart b/lib/src/modules/conditionals/always.dart index 0fe6b3f99..69ad9df16 100644 --- a/lib/src/modules/conditionals/always.dart +++ b/lib/src/modules/conditionals/always.dart @@ -137,6 +137,11 @@ abstract class Always extends Module with SystemVerilog { // share the registration information down conditional.updateAssignmentMaps( assignedReceiverToOutputMap, assignedDriverToInputMap); + + portTypePairs.addAll(conditional.portTypePairs.map((k, v) => MapEntry( + conditional.driverOrReceiverPort(k), + conditional.driverOrReceiverPort(v), + ))); } } @@ -177,6 +182,9 @@ abstract class Always extends Module with SystemVerilog { final outputs = Map.fromEntries(ports.entries .where((element) => this.outputs.containsKey(element.key))); + assert(ports.length == inputs.length + outputs.length, + 'All ports of an always should be inputs or outputs'); + var verilog = ''; verilog += '// $instanceName\n'; verilog += '${alwaysVerilogStatement(inputs)} begin\n'; diff --git a/lib/src/modules/conditionals/conditional.dart b/lib/src/modules/conditionals/conditional.dart index c26a60b3a..8b25fb477 100644 --- a/lib/src/modules/conditionals/conditional.dart +++ b/lib/src/modules/conditionals/conditional.dart @@ -71,6 +71,14 @@ abstract class Conditional { Logic receiverOutput(Logic receiver) => _assignedReceiverToOutputMap[receiver]!; + //TODO: do we like this API? + @protected + Logic driverOrReceiverPort(Logic driverOrReceiver) => + _assignedDriverToInputMap[driverOrReceiver] ?? + _assignedReceiverToOutputMap[driverOrReceiver] ?? + (throw Exception(//TODO: exception + 'Logic $driverOrReceiver is not a driver or receiver in this Conditional.')); + /// Executes the functionality of this [Conditional] and /// populates [drivenSignals] with all [Logic]s that were driven /// during execution. @@ -120,6 +128,15 @@ abstract class Conditional { /// Does *not* recursively call down through sub-[Conditional]s. List get conditionals; + /// A mapping between [receivers] and [drivers] to be fed up to the enclosing + /// [Combinational] or [Sequential]'s [Module.portTypePairs]. + /// + /// NOTE: This is for internal usage only, and the API will not be guaranteed + /// to be stable. + @internal + Map get portTypePairs => + {for (final cond in conditionals) ...cond.portTypePairs}; + /// Returns a [String] of SystemVerilog to be used in generated output. /// /// The [indent] is used for pretty-printing, and should generally be diff --git a/lib/src/modules/conditionals/conditional_assign.dart b/lib/src/modules/conditionals/conditional_assign.dart index 6d5d6904e..f62a28d8e 100644 --- a/lib/src/modules/conditionals/conditional_assign.dart +++ b/lib/src/modules/conditionals/conditional_assign.dart @@ -13,15 +13,18 @@ import 'package:rohd/src/modules/conditionals/ssa.dart'; /// An assignment that only happens under certain conditions. /// -/// [Logic] has a short-hand for creating [ConditionalAssign] via the -/// `<` operator. +/// [Logic] has a short-hand for creating [ConditionalAssign] via the `<` +/// operator. class ConditionalAssign extends Conditional { - /// The input to this assignment. + /// The receiver for this assignment. final Logic receiver; - /// The output of this assignment. + /// The driver for this assignment. final Logic driver; + @override + Map get portTypePairs => {driver: receiver}; + /// Conditionally assigns [receiver] to the value of [driver]. ConditionalAssign(this.receiver, this.driver) { if (driver.width != receiver.width) { diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index 2b2049349..aebe1ce5b 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -208,7 +208,12 @@ class LogicEnum extends LogicDef { return true; } - //TODO: clone - - //TODO need to update the Wire to have "restrictions" on legal values + @override + LogicEnum clone({String? name}) => LogicEnum.withMapping(mapping, + width: width, + name: name ?? this.name, + naming: + naming, //TODO: use same mechanism as Logic for naming determination + definitionName: definitionName, + reserveDefinitionName: reserveDefinitionName); } diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart index b2758c742..45bd6c808 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synth_module_definition.dart @@ -14,7 +14,13 @@ import 'package:rohd/src/synthesizers/utilities/utilities.dart'; /// A special [SynthModuleDefinition] for SystemVerilog modules. class SystemVerilogSynthModuleDefinition extends SynthModuleDefinition { /// Creates a new [SystemVerilogSynthModuleDefinition] for the given [module]. - SystemVerilogSynthModuleDefinition(super.module); + SystemVerilogSynthModuleDefinition(super.module) + : assert( + !(module is SystemVerilog && + module.generatedDefinitionType == + DefinitionGenerationType.none), + 'Do not build a definition for a module' + ' which generates no definition!'); @override void process() { diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index ff8dc63db..98caa92bb 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -230,8 +230,15 @@ class SynthLogic { if (_enumAndConstMergeable(a, b)) { a.adopt(b); return b; + } else if (a.isEnum && b.isEnum) { + //TODO: test this scenario + + // don't merge enums if they are not mergeable + return null; } + //TODO: should enum and non-enum be mergeable? + if (!a.mergeable && !b.mergeable) { return null; } @@ -253,7 +260,7 @@ class SynthLogic { !a._constNameDisallowed && !b._constNameDisallowed; - /// Indicates whether [a] and [b] represent one enum and one constant which + /// Indicates whether [a] and [b] represent enum(s) and constant(s) that /// can be merged. static bool _enumAndConstMergeable(SynthLogic a, SynthLogic b) { final enums = [a, b].where((e) => e.isEnum).toList(growable: false); diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index d04f4f624..49a7c7c36 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -139,13 +139,7 @@ class SynthModuleDefinition { ...module.outputs.keys, ...module.inOuts.keys, }, - ), - assert( - !(module is SystemVerilog && - module.generatedDefinitionType == - DefinitionGenerationType.none), - 'Do not build a definition for a module' - ' which generates no definition!') { + ) { // start by traversing output signals final logicsToTraverse = TraverseableCollection() ..addAll(module.outputs.values) @@ -312,6 +306,7 @@ class SynthModuleDefinition { _collapseArrays(); _collapseAssignments(); _assignSubmodulePortMapping(); + _adjustTypePairs(); process(); _pickNames(); } @@ -352,6 +347,13 @@ class SynthModuleDefinition { } } + void _adjustTypePairs() { + for (final submoduleInstantiation + in moduleToSubModuleInstantiationMap.values) { + submoduleInstantiation.adjustTypePairs(); + } + } + final Map _enumDefinitions = {}; diff --git a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart index dd3efae00..f81a53955 100644 --- a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart +++ b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart @@ -9,6 +9,7 @@ import 'dart:collection'; +import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; import 'package:rohd/src/utilities/uniquifier.dart'; @@ -89,6 +90,32 @@ class SynthSubModuleInstantiation { _inOutMapping[name] = synthLogic; } + @internal + void adjustTypePairs() { + for (final MapEntry(key: toUpdate, value: reference) + in module.portTypePairs.entries) { + final toUpdateSynth = inputMapping[toUpdate.name] ?? + outputMapping[toUpdate.name] ?? + inOutMapping[toUpdate.name]!; + final referenceSynth = inputMapping[reference.name] ?? + outputMapping[reference.name] ?? + inOutMapping[reference.name]!; + + if (referenceSynth.isEnum) { + final mergeResult = SynthLogic.tryMerge( + toUpdateSynth, + SynthLogic(referenceSynth.characteristicEnum!.clone()), + ); + if (mergeResult == null) { + //TODO + throw Exception('Unmergeable types'); + } + assert(mergeResult != toUpdateSynth, + 'We should not be replacing the original one.'); + } + } + } + /// Indicates whether this module should be declared. bool get needsDeclaration => _needsDeclaration; bool _needsDeclaration = true; diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index 493272acc..d20804833 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -43,6 +43,29 @@ class ModWithEnumConstAssignment extends Module { } } +class ModWithCaseAndEnumCondAssign extends Module { + ModWithCaseAndEnumCondAssign(Logic durian) { + durian = addInput('durian', durian); + + final currState = MyListLogicEnum(name: 'currState'); + final nextState = MyListLogicEnum(name: 'nextState'); + + nextState <= + cases( + currState, + { + 0: 0, + // MyListLogicEnum()..getsEnum(TestEnum.b): MyListLogicEnum() + // ..getsEnum(TestEnum.b), + // TestEnum.c: TestEnum.c, + //TODO: get all these working nicely + }, + width: 2); + + addOutput('pineapple') <= durian & nextState.xor(); + } +} + void main() { test('enum populates based on list of values', () { final e = MyListLogicEnum(); @@ -88,6 +111,8 @@ void main() { final sv = mod.generateSynth(); + print(sv); + expect( sv, contains( @@ -101,9 +126,7 @@ void main() { final sv = mod.generateSynth(); - print(sv); - - // don't care which one has _0, but one of the does! + // don't care which one has _0, but one of them does! expect( sv, contains( @@ -126,5 +149,13 @@ void main() { " { a = 2'h0, b = 2'h1, c = 2'h2 } TestEnum;")); expect(sv, contains('assign banana = carrot & b;')); }); + + test('enum with case and cond assignments', () async { + final mod = ModWithCaseAndEnumCondAssign(Logic()); + await mod.build(); + + final sv = mod.generateSynth(); + print(sv); + }); }); } From 7ca82de3fbfd28529d2cfa192dc982680f64474b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 1 Jul 2025 11:30:34 -0700 Subject: [PATCH 20/24] got basic case and condassign working with type matching --- lib/src/modules/conditionals/case.dart | 11 +++++++++++ test/logic_enum_test.dart | 3 +++ 2 files changed, 14 insertions(+) diff --git a/lib/src/modules/conditionals/case.dart b/lib/src/modules/conditionals/case.dart index 590437391..3047d1574 100644 --- a/lib/src/modules/conditionals/case.dart +++ b/lib/src/modules/conditionals/case.dart @@ -125,6 +125,15 @@ class Case extends Conditional { /// See [ConditionalType] for more details. final ConditionalType conditionalType; + @override + Map get portTypePairs => { + ...super.portTypePairs, + ..._itemTypePortPairs, + }; + + //TODO doc + final Map _itemTypePortPairs = {}; + /// Whenever an item in [items] matches [expression], it will be executed. /// /// If none of [items] match, then [defaultItem] is executed. @@ -136,6 +145,8 @@ class Case extends Conditional { if (item.value.width != expression.width) { throw PortWidthMismatchException.equalWidth(expression, item.value); } + + _itemTypePortPairs[item.value] = expression; } } diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index d20804833..8b0590ef9 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -156,6 +156,9 @@ void main() { final sv = mod.generateSynth(); print(sv); + + expect(sv, contains(' a : begin')); + expect(sv, contains('nextState = a;')); }); }); } From 97a89263bdc188c8ef962369406630f196f42e61 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 1 Jul 2025 15:20:26 -0700 Subject: [PATCH 21/24] added actual logicenum type support for conds --- lib/src/signals/logic_enum.dart | 1 + .../systemverilog/systemverilog_synthesizer.dart | 2 ++ .../utilities/synth_sub_module_instantiation.dart | 12 +++++++++++- test/logic_enum_test.dart | 4 ++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index aebe1ce5b..8c70cad2b 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -137,6 +137,7 @@ class LogicEnum extends LogicDef { Naming? naming, String? definitionName, bool reserveDefinitionName = false}) + //TODO: add an optional function arg to remap values : this.withMapping( Map.fromEntries( values.mapIndexed((index, value) => MapEntry(value, index))), diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart index d8b5bae36..84a89559b 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesizer.dart @@ -15,6 +15,8 @@ import 'package:rohd/src/synthesizers/systemverilog/systemverilog_synthesis_resu /// /// Attempts to maintain signal naming and structure as much as possible. class SystemVerilogSynthesizer extends Synthesizer { + //TODO: add configuration for whether to emit enums or not + @override bool generatesDefinition(Module module) => // ignore: deprecated_member_use_from_same_package diff --git a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart index f81a53955..1954185d9 100644 --- a/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart +++ b/lib/src/synthesizers/utilities/synth_sub_module_instantiation.dart @@ -11,6 +11,7 @@ import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; +import 'package:rohd/src/synthesizers/utilities/synth_enum_definition.dart'; import 'package:rohd/src/synthesizers/utilities/utilities.dart'; import 'package:rohd/src/utilities/uniquifier.dart'; @@ -102,9 +103,18 @@ class SynthSubModuleInstantiation { inOutMapping[reference.name]!; if (referenceSynth.isEnum) { + if (toUpdateSynth.isEnum && + SynthEnumDefinitionKey(toUpdateSynth.characteristicEnum!) == + SynthEnumDefinitionKey(referenceSynth.characteristicEnum!)) { + // If the types are equivalent, we can just use the original, no need + // to do any additional merging. + continue; + } + final mergeResult = SynthLogic.tryMerge( toUpdateSynth, - SynthLogic(referenceSynth.characteristicEnum!.clone()), + SynthLogic( + referenceSynth.characteristicEnum!.clone(name: 'reference')), ); if (mergeResult == null) { //TODO diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index 8b0590ef9..a65a14b95 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -55,8 +55,8 @@ class ModWithCaseAndEnumCondAssign extends Module { currState, { 0: 0, - // MyListLogicEnum()..getsEnum(TestEnum.b): MyListLogicEnum() - // ..getsEnum(TestEnum.b), + MyListLogicEnum()..getsEnum(TestEnum.b): MyListLogicEnum() + ..getsEnum(TestEnum.b), // TestEnum.c: TestEnum.c, //TODO: get all these working nicely }, From 0b39f1da3756d2524bdaeece3528b5ccf9943814 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 1 Jul 2025 17:31:12 -0700 Subject: [PATCH 22/24] got cases special case for enum --- lib/src/modules/conditionals/case.dart | 8 +++++--- lib/src/signals/logic.dart | 9 +++++---- lib/src/signals/logic_enum.dart | 16 ++++++++++++++++ test/logic_enum_test.dart | 1 + 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/src/modules/conditionals/case.dart b/lib/src/modules/conditionals/case.dart index 3047d1574..54f237b02 100644 --- a/lib/src/modules/conditionals/case.dart +++ b/lib/src/modules/conditionals/case.dart @@ -88,9 +88,11 @@ Logic cases(Logic expression, Map conditions, [ for (final condition in conditions.entries) CaseItem( - condition.key is Logic - ? condition.key as Logic - : Const(condition.key, width: expression.width), + (expression is LogicEnum && condition.key is Enum) + ? (expression.clone()..getsEnum(condition.key as Enum)) + : condition.key is Logic + ? condition.key as Logic + : Const(condition.key, width: expression.width), [result < condition.value]) ], conditionalType: conditionalType, diff --git a/lib/src/signals/logic.dart b/lib/src/signals/logic.dart index 981ad75f3..cb1cff37d 100644 --- a/lib/src/signals/logic.dart +++ b/lib/src/signals/logic.dart @@ -386,7 +386,8 @@ class Logic { /// Handles the actual connection of this [Logic] to be driven by [other]. void _connect(Logic other) { - _unassignable = true; + makeUnassignable(reason: '$this is connected to $other.'); + if (other is LogicNet) { put(other.value); other.glitch.listen((args) { @@ -670,11 +671,11 @@ class Logic { /// [Conditional]. Conditional operator <(dynamic 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.'); + throw UnassignableException(this, reason: _unassignableReason); } + //TODO: add support for enum types + if (other is Logic) { return ConditionalAssign(this, other); } else { diff --git a/lib/src/signals/logic_enum.dart b/lib/src/signals/logic_enum.dart index 8c70cad2b..a00868de9 100644 --- a/lib/src/signals/logic_enum.dart +++ b/lib/src/signals/logic_enum.dart @@ -174,6 +174,22 @@ class LogicEnum extends LogicDef { super.gets(other); } + /// Conditional assignment operator, with added support for [T] enums. + @override + Conditional operator <(dynamic other) { + if (_unassignable) { + throw UnassignableException(this, reason: _unassignableReason); + } + + //TODO: test this! + + if (other is T) { + return super < (clone()..getsEnum(other)); + } else { + return super < other; + } + } + @override void put(dynamic val, {bool fill = false}) { if (val is T) { diff --git a/test/logic_enum_test.dart b/test/logic_enum_test.dart index a65a14b95..1b9527247 100644 --- a/test/logic_enum_test.dart +++ b/test/logic_enum_test.dart @@ -58,6 +58,7 @@ class ModWithCaseAndEnumCondAssign extends Module { MyListLogicEnum()..getsEnum(TestEnum.b): MyListLogicEnum() ..getsEnum(TestEnum.b), // TestEnum.c: TestEnum.c, + TestEnum.c: 2, //TODO: get all these working nicely }, width: 2); From 0c451d6f7c635c819d400758bac79f2492354e7d Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 8 Jul 2025 11:26:39 -0700 Subject: [PATCH 23/24] wip enum stuff --- .../systemverilog/systemverilog_synthesis_result.dart | 3 +++ test/fsm_test.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart index a12177a3a..cdb4b11bb 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog_synthesis_result.dart @@ -227,6 +227,9 @@ class SystemVerilogSynthesisResult extends SynthesisResult { return null; } + //TODO: throw error if there are multiple definitionParameters with the + // same name + return [ '#(', defParams diff --git a/test/fsm_test.dart b/test/fsm_test.dart index c520eaa56..da5e4ec0d 100644 --- a/test/fsm_test.dart +++ b/test/fsm_test.dart @@ -115,6 +115,9 @@ class TrafficTestModule extends Module { final northLight = addOutput('northLight', width: traffic.width); final eastLight = addOutput('eastLight', width: traffic.width); + //TODO: can we use lightcolor as an enum also in here? + // TODO: make sure ports and enums don't merge badly! + final clk = SimpleClockGenerator(10).clk; final states = >[ From 97dccee04020d729188f2883388506a2af08279e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 8 Jul 2025 17:02:02 -0700 Subject: [PATCH 24/24] some todos --- lib/src/module.dart | 1 + lib/src/synthesizers/utilities/synth_module_definition.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/src/module.dart b/lib/src/module.dart index 9a403d785..4bba298f3 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -318,6 +318,7 @@ abstract class Module { // set unique module instance names for submodules final uniquifier = Uniquifier(); + //TODO: BUG! we must guarantee this unique name is the same one used for generation!? for (final module in _subModules) { module._uniqueInstanceName = uniquifier.getUniqueName( initialName: Sanitizer.sanitizeSV(module.name), diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index 49a7c7c36..baf219860 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -403,6 +403,7 @@ class SynthModuleDefinition { // pick names of *reserved* submodule instances final nonReservedSubmodules = []; for (final submodule in moduleToSubModuleInstantiationMap.values) { + //TODO: should ensure that uniqueInstanceName is usable! if (submodule.module.reserveName) { submodule.pickName(_synthIdentifierUniquifier); assert(submodule.module.name == submodule.name,