Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions lib/src/module.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2025 Intel Corporation
// Copyright (C) 2021-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// module.dart
Expand All @@ -11,12 +11,12 @@ import 'dart:async';
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/diagnostics/inspector_service.dart';
import 'package:rohd/src/utilities/config.dart';
import 'package:rohd/src/utilities/sanitizer.dart';
import 'package:rohd/src/utilities/signal_namer.dart';
import 'package:rohd/src/utilities/timestamper.dart';
import 'package:rohd/src/utilities/uniquifier.dart';

Expand Down Expand Up @@ -52,6 +52,41 @@ abstract class Module {
/// An internal mapping of input names to their sources to this [Module].
late final Map<String, Logic> _inputSources = {};

// ─── Canonical naming (SignalNamer) ─────────────────────────────

/// Lazily-constructed namer that owns the [Uniquifier] and the
/// sparse Logic→String cache. Initialized on first access.
@internal
late final SignalNamer signalNamer = _createSignalNamer();

SignalNamer _createSignalNamer() {
assert(hasBuilt, 'Module must be built before canonical names are bound.');
return SignalNamer.forModule(
inputs: _inputs,
outputs: _outputs,
inOuts: _inOuts,
);
}

/// Returns the collision-free signal name for [logic] within this module.
String signalName(Logic logic) => signalNamer.nameOf(logic);

/// Allocates a collision-free signal name in this module's namespace.
///
/// Used by synthesizers to name connection nets, submodule instances,
/// intermediate wires, and other artifacts that have no user-created
/// [Logic] object. The returned name is guaranteed not to collide with
/// any signal name or any previously allocated name.
///
/// When [reserved] is `true`, the exact [baseName] (after sanitization) is
/// claimed without modification; an exception is thrown if it collides.
String allocateSignalName(String baseName, {bool reserved = false}) =>
signalNamer.allocate(baseName, reserved: reserved);

/// Returns `true` if [name] has not yet been claimed as a signal name in
/// this module's namespace.
bool isSignalNameAvailable(String name) => signalNamer.isAvailable(name);

/// An internal mapping of inOut names to their sources to this [Module].
late final Map<String, Logic> _inOutSources = {};

Expand Down
5 changes: 4 additions & 1 deletion lib/src/synthesizers/synth_builder.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2025 Intel Corporation
// Copyright (C) 2021-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// synth_builder.dart
Expand Down Expand Up @@ -56,6 +56,9 @@ class SynthBuilder {
}
}

// Allow the synthesizer to prepare with knowledge of top module(s)
synthesizer.prepare(this.tops);

final modulesToParse = <Module>[...tops];
for (var i = 0; i < modulesToParse.length; i++) {
final moduleI = modulesToParse[i];
Expand Down
22 changes: 19 additions & 3 deletions lib/src/synthesizers/synthesizer.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
// Copyright (C) 2021-2023 Intel Corporation
// Copyright (C) 2021-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// synthesizer.dart
// Generic definition for something that synthesizes output files
//
// 2021 August 26
// Author: Max Korbel <max.korbel@intel.com>
//

import 'package:rohd/rohd.dart';

/// An object capable of converting a module into some new output format
abstract class Synthesizer {
/// Called by [SynthBuilder] before synthesis begins, with the top-level
/// module(s) being synthesized.
///
/// Override this method to perform any initialization that requires
/// knowledge of the top module, such as resolving port names to [Logic]
/// objects, or computing global signal sets.
///
/// The default implementation does nothing.
void prepare(List<Module> tops) {}
Comment thread
desmonddak marked this conversation as resolved.
Outdated

/// Determines whether [module] needs a separate definition or can just be
/// described in-line.
bool generatesDefinition(Module module);

/// Synthesizes [module] into a [SynthesisResult], given the mapping provided
/// by [getInstanceTypeOfModule].
///
/// Optionally a [lookupExistingResult] callback may be supplied which
/// allows the synthesizer to query already-generated `SynthesisResult`s
/// for child modules (useful when building parent output that needs
/// information from children).
SynthesisResult synthesize(
Module module, String Function(Module module) getInstanceTypeOfModule);
Module module, String Function(Module module) getInstanceTypeOfModule,
{SynthesisResult? Function(Module module)? lookupExistingResult,
Comment thread
desmonddak marked this conversation as resolved.
Outdated
Map<Module, SynthesisResult>? existingResults});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2025 Intel Corporation
// Copyright (C) 2021-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// systemverilog_synthesizer.dart
Expand Down Expand Up @@ -137,7 +137,9 @@ class SystemVerilogSynthesizer extends Synthesizer {

@override
SynthesisResult synthesize(
Module module, String Function(Module module) getInstanceTypeOfModule) {
Module module, String Function(Module module) getInstanceTypeOfModule,
{SynthesisResult? Function(Module module)? lookupExistingResult,
Map<Module, SynthesisResult>? existingResults}) {
assert(
module is! SystemVerilog ||
module.generatedDefinitionType != DefinitionGenerationType.none,
Expand Down
90 changes: 11 additions & 79 deletions lib/src/synthesizers/utilities/synth_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import 'package:meta/meta.dart';
import 'package:rohd/rohd.dart';
import 'package:rohd/src/synthesizers/utilities/utilities.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.
@internal
Expand Down Expand Up @@ -212,92 +211,25 @@ class SynthLogic {
/// The name of this, if it has been picked.
String? _name;

/// Picks a [name].
/// Picks a [name] using the module's signal namer.
///
/// Must be called exactly once.
void pickName(Uniquifier uniquifier) {
void pickName() {
assert(_name == null, 'Should only pick a name once.');

_name = _findName(uniquifier);
_name = _findName();
}

/// 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 constant, 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!.preferredSynthName,
);
}

// pick a preferred, available, mergeable name, if one exists
final unpreferredMergeableLogics = <Logic>[];
final uniquifiableMergeableLogics = <Logic>[];
for (final mergeableLogic in _mergeableLogics) {
if (Naming.isUnpreferred(mergeableLogic.preferredSynthName)) {
unpreferredMergeableLogics.add(mergeableLogic);
} else if (!uniquifier.isAvailable(mergeableLogic.preferredSynthName)) {
uniquifiableMergeableLogics.add(mergeableLogic);
} else {
return uniquifier.getUniqueName(
initialName: mergeableLogic.preferredSynthName,
);
}
}

// uniquify a preferred, mergeable name, if one exists
if (uniquifiableMergeableLogics.isNotEmpty) {
return uniquifier.getUniqueName(
initialName: uniquifiableMergeableLogics.first.preferredSynthName,
);
}

// 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.preferredSynthName),
)
?.preferredSynthName ??
unpreferredMergeableLogics.first.preferredSynthName,
///
/// Delegates to signal namer which handles constant value naming, priority
/// selection, and uniquification via the module's shared namespace.
String _findName() =>
parentSynthModuleDefinition.module.signalNamer.nameOfBest(
logics,
constValue: _constLogic,
constNameDisallowed: _constNameDisallowed,
);
}

// 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.preferredSynthName),
)
?.preferredSynthName ??
_unnamedLogics.first.preferredSynthName,
);
}

/// Creates an instance to represent [initialLogic] and any that merge
/// into it.
Expand Down
60 changes: 30 additions & 30 deletions lib/src/synthesizers/utilities/synth_module_definition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ 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';

/// A version of [BusSubset] that can be used for slicing on [LogicStructure]
/// ports.
Expand Down Expand Up @@ -110,10 +109,6 @@ class SynthModuleDefinition {
@override
String toString() => "module name: '${module.name}'";

/// Used to uniquify any identifiers, including signal names
/// and module instances.
final Uniquifier _synthInstantiationNameUniquifier;

/// Indicates whether [logic] has a corresponding present [SynthLogic] in
/// this definition.
@internal
Expand Down Expand Up @@ -289,14 +284,7 @@ class SynthModuleDefinition {

/// Creates a new definition representation for this [module].
SynthModuleDefinition(this.module)
: _synthInstantiationNameUniquifier = Uniquifier(
reservedNames: {
...module.inputs.keys,
...module.outputs.keys,
...module.inOuts.keys,
},
),
assert(
: assert(
!(module is SystemVerilog &&
module.generatedDefinitionType ==
DefinitionGenerationType.none),
Expand Down Expand Up @@ -465,6 +453,7 @@ class SynthModuleDefinition {

final receiverIsSubModuleOutput =
receiver.isOutput && (receiver.parentModule?.parent == module);

if (receiverIsSubModuleOutput) {
final subModule = receiver.parentModule!;

Expand Down Expand Up @@ -513,6 +502,7 @@ class SynthModuleDefinition {
_collapseArrays();
_collapseAssignments();
_assignSubmodulePortMapping();

_pruneUnused();
process();
_pickNames();
Expand Down Expand Up @@ -752,49 +742,59 @@ class SynthModuleDefinition {
}

/// Picks names of signals and sub-modules.
///
/// Signal names are read from [Module.signalName] (for user-created
/// [Logic] objects) or kept as literal constants. Submodule instance
/// names and synthesizer artifacts are allocated from the shared
/// [Module] namespace via [Module.allocateSignalName], guaranteeing no
/// collisions across synthesizers.
void _pickNames() {
// first ports get priority
// Name allocation order matters — earlier claims get the unsuffixed name
// when there are collisions. This matches production ROHD priority:
// 1. Ports (reserved by _initNamespace, claimed via signalName)
// 2. Reserved submodule instances
// 3. Reserved internal signals
// 4. Non-reserved submodule instances
// 5. Non-reserved internal signals
for (final input in inputs) {
input.pickName(_synthInstantiationNameUniquifier);
input.pickName();
}
for (final output in outputs) {
output.pickName(_synthInstantiationNameUniquifier);
output.pickName();
}
for (final inOut in inOuts) {
inOut.pickName(_synthInstantiationNameUniquifier);
inOut.pickName();
}

// pick names of *reserved* submodule instances
final nonReservedSubmodules = <SynthSubModuleInstantiation>[];
// Reserved submodule instances first (they assert their exact name).
for (final submodule in subModuleInstantiations) {
if (submodule.module.reserveName) {
submodule.pickName(_synthInstantiationNameUniquifier);
submodule.pickName(module);
assert(submodule.module.name == submodule.name,
'Expect reserved names to retain their name.');
} else {
nonReservedSubmodules.add(submodule);
}
}

// then *reserved* internal signals get priority
// Reserved internal signals next.
final nonReservedSignals = <SynthLogic>[];
for (final signal in internalSignals) {
if (signal.isReserved) {
signal.pickName(_synthInstantiationNameUniquifier);
signal.pickName();
} else {
nonReservedSignals.add(signal);
}
}

// then submodule instances
for (final submodule in nonReservedSubmodules
.where((element) => element.needsInstantiation)) {
submodule.pickName(_synthInstantiationNameUniquifier);
// Then non-reserved submodule instances.
for (final submodule in subModuleInstantiations) {
if (!submodule.module.reserveName && submodule.needsInstantiation) {
submodule.pickName(module);
}
}

// then the rest of the internal signals
// Then the rest of the internal signals.
for (final signal in nonReservedSignals) {
signal.pickName(_synthInstantiationNameUniquifier);
signal.pickName();
}
}

Expand Down
Loading
Loading