Skip to content
20 changes: 18 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,11 +11,11 @@ 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/namer.dart';
import 'package:rohd/src/utilities/sanitizer.dart';
import 'package:rohd/src/utilities/timestamper.dart';
import 'package:rohd/src/utilities/uniquifier.dart';
Expand Down Expand Up @@ -52,6 +52,22 @@ abstract class Module {
/// An internal mapping of input names to their sources to this [Module].
late final Map<String, Logic> _inputSources = {};

// ─── Central naming (Namer) ─────────────────────────────────────

/// Central namer that owns both the signal and instance namespaces.
/// Initialized lazily on first access (after build).
@internal
late final Namer namer = _createNamer();

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

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

Expand Down
2 changes: 1 addition & 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
3 changes: 1 addition & 2 deletions lib/src/synthesizers/synthesizer.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// 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';

Expand Down
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
125 changes: 31 additions & 94 deletions lib/src/synthesizers/utilities/synth_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@ import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:rohd/rohd.dart';
import 'package:rohd/src/synthesizers/utilities/utilities.dart';
import 'package:rohd/src/utilities/namer.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
class SynthLogic {
/// Controls whether two constants with the same value driving separate
/// module inputs are merged into a single signal declaration.
///
/// When `true` (the default), identical constants are collapsed to one
/// declaration — desirable for simulation-oriented output such as
/// SystemVerilog, where a single `assign wire = VALUE;` feeds all
/// downstream consumers.
///
/// When `false`, each constant input keeps its own declaration. This is
/// useful for netlist/visualization outputs where seeing every individual
/// constant connection is more informative than an optimized fan-out net.
static bool mergeConstantInputs = true;

/// All [Logic]s represented, regardless of type.
List<Logic> get logics => UnmodifiableListView([
if (_reservedLogic != null) _reservedLogic!,
Expand Down Expand Up @@ -212,92 +225,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.namer.signalNameOfBest(
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 Expand Up @@ -342,7 +288,12 @@ class SynthLogic {
}

/// Indicates whether two constants can be merged.
///
/// Merging is only performed when [SynthLogic.mergeConstantInputs] is
/// `true`. Set it to `false` to keep each constant input as its own
/// declaration (e.g. for netlist/visualization output).
static bool _constantsMergeable(SynthLogic a, SynthLogic b) =>
SynthLogic.mergeConstantInputs &&
a.isConstant &&
b.isConstant &&
a._constLogic!.value == b._constLogic!.value &&
Expand Down Expand Up @@ -404,7 +355,7 @@ class SynthLogic {

@override
String toString() => '${_name == null ? 'null' : '"$name"'}, '
'logics contained: ${logics.map((e) => e.preferredSynthName).toList()}';
'logics contained: ${logics.map(Namer.baseName).toList()}';

/// Provides a definition for a range in SV from a width.
static String _widthToRangeDef(int width, {bool forceRange = false}) {
Expand Down Expand Up @@ -551,17 +502,3 @@ class SynthLogicArrayElement extends SynthLogic {
' parentArray=($parentArray), element ${logic.arrayIndex}, logic: $logic'
' logics contained: ${logics.map((e) => e.name).toList()}';
}

extension on Logic {
/// Returns the preferred name for this [Logic] while generating in the synth
/// stack.
String get preferredSynthName => naming == Naming.reserved
// if reserved, keep the exact name
? name
: isArrayMember
// arrays nicely name their elements already
? name
// sanitize to remove any `.` in struct names
// the base `name` will be returned if not a structure.
: Sanitizer.sanitizeSV(structureName);
}
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 `Namer.signalNameOf `(for user-created
/// [Logic] objects) or kept as literal constants and are allocated from
/// `Namer.allocateSignalName` (signal namespace). Submodule instance
/// names are allocated from `Namer.allocateInstanceName` (instance
/// namespace). Both namespaces are managed by the module's `Namer`.
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