Skip to content

Commit b9e35a6

Browse files
authored
Signal naming improvements (#439)
1 parent c70a1e5 commit b9e35a6

34 files changed

+1385
-359
lines changed

doc/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A `LogicValue` represents a multi-bit (including 0-bit and 1-bit) 4-value (`1`,
1616

1717
The `Module` is the fundamental building block of hardware designs in ROHD. They have clearly defined inputs and outputs, and all logic contained within the module should connect either/both from inputs and to outputs. The ROHD framework will determine at `build()` time which logic sits within which `Module`. Any functional operation, whether a simple gate or a large module, is implemented as a `Module`.
1818

19-
Every `Module` defines its own functionality. This could be through composition of other `Module`s, or through custom functional definition. For a custom functionality to be convertable to an output (e.g. SystemVerilog), it has to explicitly define how to convert it (via `CustomVerilog` or `InlineVerilog`). Any time the input of a custom functionality `Module` toggles, the outputs should correspondingly change, if necessary.
19+
Every `Module` defines its own functionality. This could be through composition of other `Module`s, or through custom functional definition. For a custom functionality to be convertable to an output (e.g. SystemVerilog), it has to explicitly define how to convert it (via `CustomVerilog` or `InlineSystemVerilog`). Any time the input of a custom functionality `Module` toggles, the outputs should correspondingly change, if necessary.
2020

2121
### Simulator
2222

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
title: "Generating Outputs"
3+
permalink: /docs/generation/
4+
last_modified_at: 2023-11-13
5+
toc: true
6+
---
7+
8+
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.
9+
10+
The simplest way to generate SystemVerilog is with the helper method `generateSynth` in `Module`:
11+
12+
```dart
13+
void main() async {
14+
final myModule = MyModule();
15+
16+
// remember that `build` returns a `Future`, hence the `await` here
17+
await myModule.build();
18+
19+
final generatedSv = myModule.generateSynth();
20+
21+
// you can print it out...
22+
print(generatedSv);
23+
24+
// or write it to a file
25+
File('myHardware.sv').writeAsStringSync(generatedSv);
26+
}
27+
```
28+
29+
The `generateSynth` function will return a `String` with the SystemVerilog `module` definitions for the top-level it is called on, as well as any sub-modules (recursively). You can dump the entire contents to a file and use it anywhere you would any other SystemVerilog.
30+
31+
## Controlling naming
32+
33+
### Modules
34+
35+
Port names are always maintained exactly in generated SystemVerilog, so they must always be unique and sanitary (valid) SystemVerilog.
36+
37+
`Module`s have two names:
38+
39+
- The `definitionName`, which maps to the name of the module declaration in SystemVerilog.
40+
- If you want to ensure this does not change (e.g. uniquified because multiple different declarations have the same `definitionname`), set `reserveDefinitionName` to `true`.
41+
- The `name`, which maps to the instance name when that instance is instanitated as a sub-module of another module.
42+
- 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`.
43+
44+
### Internal signals
45+
46+
Internal signals, unlike ports, don't need to always have the same exact name as in the original hardware definition.
47+
48+
- 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.
49+
- 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.
50+
- If you want to make sure an internal signal maintains exactly the name you want, you can mark it explicitly with `reserved`.
51+
- 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.
52+
53+
### Unpreferred names
54+
55+
The `Naming.unpreferredName` function will modify a signal name to indicate to downstream flows that the name is preferably omitted from the output, but preferable to an unnamed signal. This is generally most useful for things like output ports of `InlineSystemVerilog` modules.
56+
57+
## More advanced generation
58+
59+
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.

lib/rohd.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ export 'src/signals/signals.dart';
1111
export 'src/simulator.dart';
1212
export 'src/swizzle.dart';
1313
export 'src/synthesizers/synthesizers.dart';
14+
export 'src/utilities/naming.dart';
1415
export 'src/values/values.dart';
1516
export 'src/wave_dumper.dart';

lib/src/exceptions/name/name_exceptions.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export 'empty_reserved_name_exception.dart';
55
export 'invalid_portname_exceptions.dart';
66
export 'invalid_reserved_name_exception.dart';
77
export 'null_reserved_name_exception.dart';
8+
export 'unavailable_reserved_name_exception.dart';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (C) 2023 Intel Corporation
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
//
4+
// unavailable_reserved_name_exception.dart
5+
// An exception that thrown when a reserved name can't be acquired.
6+
//
7+
// 2023 November 3
8+
// Author: Max Korbel <[email protected]>
9+
10+
import 'package:rohd/rohd.dart';
11+
12+
/// An exception that thrown when a reserved name cannot be acquired.
13+
class UnavailableReservedNameException extends RohdException {
14+
/// Constructs an error indicating that the reserved [name] could not be
15+
/// acquired.
16+
UnavailableReservedNameException(String name)
17+
: this.withMessage('Unable to use reserved name "$name" '
18+
'because something else already has this name.');
19+
20+
/// Constructs an error indicating that the reserved `name` could not be
21+
/// acquired with a [message] explaining why.
22+
UnavailableReservedNameException.withMessage(super.message);
23+
}

lib/src/module.dart

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ abstract class Module {
4141
final Set<Logic> _internalSignals = HashSet<Logic>();
4242

4343
/// An internal list of inputs to this [Module].
44-
final Map<String, Logic> _inputs = HashMap<String, Logic>();
44+
final Map<String, Logic> _inputs = {};
4545

4646
/// An internal list of outputs to this [Module].
47-
final Map<String, Logic> _outputs = HashMap<String, Logic>();
47+
final Map<String, Logic> _outputs = {};
4848

4949
/// The parent [Module] of this [Module].
5050
///
@@ -141,23 +141,6 @@ abstract class Module {
141141
' Call build() before accessing this.');
142142
String _uniqueInstanceName;
143143

144-
/// Return string type definition name if validation passed
145-
/// else throw exception.
146-
///
147-
/// This validation method ensure that [name] is valid if
148-
/// [reserveName] set to `true`.
149-
static String? _nameValidation(String? name, bool reserveName) {
150-
if (reserveName && name == null) {
151-
throw NullReservedNameException();
152-
} else if (reserveName && name!.isEmpty) {
153-
throw EmptyReservedNameException();
154-
} else if (reserveName && !Sanitizer.isSanitary(name!)) {
155-
throw InvalidReservedNameException();
156-
} else {
157-
return name;
158-
}
159-
}
160-
161144
/// If true, guarantees [uniqueInstanceName] matches [name] or else the
162145
/// [build] will fail.
163146
final bool reserveName;
@@ -192,9 +175,10 @@ abstract class Module {
192175
this.reserveName = false,
193176
String? definitionName,
194177
this.reserveDefinitionName = false})
195-
: _uniqueInstanceName = _nameValidation(name, reserveName) ?? name,
196-
_definitionName =
197-
_nameValidation(definitionName, reserveDefinitionName);
178+
: _uniqueInstanceName =
179+
Naming.validatedName(name, reserveName: reserveName) ?? name,
180+
_definitionName = Naming.validatedName(definitionName,
181+
reserveName: reserveDefinitionName);
198182

199183
/// Returns an [Iterable] of [Module]s representing the hierarchical path to
200184
/// this [Module].
@@ -286,9 +270,6 @@ abstract class Module {
286270
await module.build();
287271
}
288272

289-
/// A prefix to add to the beginning of any port name that is "unpreferred".
290-
static String get _unpreferredPrefix => '_';
291-
292273
/// Makes a signal name "unpreferred" when considering between multiple
293274
/// possible signal names.
294275
///
@@ -299,13 +280,15 @@ abstract class Module {
299280
/// choose the other one for the final signal name. Marking signals as
300281
/// "unpreferred" can have the effect of making generated output easier to
301282
/// read.
283+
@Deprecated('Use `Naming.unpreferredName` or `Logic.naming` instead.')
302284
@protected
303-
static String unpreferredName(String name) => _unpreferredPrefix + name;
285+
static String unpreferredName(String name) => Naming.unpreferredName(name);
304286

305287
/// Returns true iff the signal name is "unpreferred".
306288
///
307289
/// See documentation for [unpreferredName] for more details.
308-
static bool isUnpreferred(String name) => name.startsWith(_unpreferredPrefix);
290+
@Deprecated('Use `Naming.isUnpreferred` or `Logic.naming` instead.')
291+
static bool isUnpreferred(String name) => Naming.isUnpreferred(name);
309292

310293
/// Searches for [Logic]s and [Module]s within this [Module] from its inputs.
311294
Future<void> _traceInputForModuleContents(Logic signal,
@@ -455,12 +438,11 @@ abstract class Module {
455438

456439
/// Checks whether a port name is safe to add (e.g. no duplicates).
457440
void _checkForSafePortName(String name) {
458-
if (!Sanitizer.isSanitary(name)) {
459-
throw InvalidPortNameException(name);
460-
}
441+
Naming.validatedName(name, reserveName: true);
461442

462443
if (outputs.containsKey(name) || inputs.containsKey(name)) {
463-
throw Exception('Already defined a port with name "$name".');
444+
throw UnavailableReservedNameException.withMessage(
445+
'Already defined a port with name "$name".');
464446
}
465447
}
466448

@@ -480,7 +462,7 @@ abstract class Module {
480462
x = x.packed;
481463
}
482464

483-
final inPort = Port(name, width)
465+
final inPort = Logic(name: name, width: width, naming: Naming.reserved)
484466
..gets(x)
485467
// ignore: invalid_use_of_protected_member
486468
..parentModule = this;
@@ -508,8 +490,13 @@ abstract class Module {
508490
}) {
509491
_checkForSafePortName(name);
510492

511-
final inArr = LogicArray(dimensions, elementWidth,
512-
name: name, numUnpackedDimensions: numUnpackedDimensions)
493+
final inArr = LogicArray(
494+
name: name,
495+
dimensions,
496+
elementWidth,
497+
numUnpackedDimensions: numUnpackedDimensions,
498+
naming: Naming.reserved,
499+
)
513500
..gets(x)
514501
// ignore: invalid_use_of_protected_member
515502
..parentModule = this;
@@ -527,7 +514,7 @@ abstract class Module {
527514
Logic addOutput(String name, {int width = 1}) {
528515
_checkForSafePortName(name);
529516

530-
final outPort = Port(name, width)
517+
final outPort = Logic(name: name, width: width, naming: Naming.reserved)
531518
// ignore: invalid_use_of_protected_member
532519
..parentModule = this;
533520

@@ -550,8 +537,13 @@ abstract class Module {
550537
}) {
551538
_checkForSafePortName(name);
552539

553-
final outArr = LogicArray(dimensions, elementWidth,
554-
name: name, numUnpackedDimensions: numUnpackedDimensions)
540+
final outArr = LogicArray(
541+
name: name,
542+
dimensions,
543+
elementWidth,
544+
numUnpackedDimensions: numUnpackedDimensions,
545+
naming: Naming.reserved,
546+
)
555547
// ignore: invalid_use_of_protected_member
556548
..parentModule = this;
557549

lib/src/modules/bus.dart

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class BusSubset extends Module with InlineSystemVerilog {
3232
/// End index of the subset.
3333
final int endIndex;
3434

35+
@override
36+
List<String> get expressionlessInputs => [_original];
37+
3538
/// Constructs a [Module] that accesses a subset from [bus] which ranges
3639
/// from [startIndex] to [endIndex] (inclusive of both).
3740
/// When, [bus] has a width of '1', [startIndex] and [endIndex] are ignored
@@ -52,13 +55,9 @@ class BusSubset extends Module with InlineSystemVerilog {
5255
' than ${bus.width}');
5356
}
5457

55-
// original name can't be unpreferred because you cannot do a bit slice
56-
// on expressions in SystemVerilog, and other expressions could have
57-
// been in-lined
58-
_original = 'original_${bus.name}';
59-
58+
_original = Naming.unpreferredName('original_${bus.name}');
6059
_subset =
61-
Module.unpreferredName('subset_${endIndex}_${startIndex}_${bus.name}');
60+
Naming.unpreferredName('subset_${endIndex}_${startIndex}_${bus.name}');
6261

6362
addInput(_original, bus, width: bus.width);
6463
final newWidth = (endIndex - startIndex).abs() + 1;
@@ -92,13 +91,19 @@ class BusSubset extends Module with InlineSystemVerilog {
9291
}
9392
}
9493

94+
/// A regular expression that will have matches if an expression is included.
95+
static final RegExp _expressionRegex = RegExp("[()']");
96+
9597
@override
9698
String inlineVerilog(Map<String, String> inputs) {
9799
if (inputs.length != 1) {
98100
throw Exception('BusSubset has exactly one input, but saw $inputs.');
99101
}
100102
final a = inputs[_original]!;
101103

104+
assert(!a.contains(_expressionRegex),
105+
'Inputs to bus swizzle cannot contain any expressions.');
106+
102107
// When, input width is 1, ignore startIndex and endIndex
103108
if (original.width == 1) {
104109
return a;
@@ -127,7 +132,7 @@ class BusSubset extends Module with InlineSystemVerilog {
127132
/// You can use convenience functions [swizzle()] or [rswizzle()] to more easily
128133
/// use this [Module].
129134
class Swizzle extends Module with InlineSystemVerilog {
130-
final String _out = Module.unpreferredName('swizzled');
135+
final String _out = Naming.unpreferredName('swizzled');
131136

132137
/// The output port containing concatenated signals.
133138
late final Logic out = output(_out);
@@ -140,7 +145,7 @@ class Swizzle extends Module with InlineSystemVerilog {
140145
var outputWidth = 0;
141146
for (final signal in signals.reversed) {
142147
//reverse so bit 0 is the last thing in the input list
143-
final inputName = Module.unpreferredName('in${idx++}');
148+
final inputName = Naming.unpreferredName('in${idx++}');
144149
_swizzleInputs.add(
145150
addInput(inputName, signal, width: signal.width),
146151
);

lib/src/modules/conditional.dart

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ abstract class _Always extends Module with CustomSystemVerilog {
112112
for (final driver in conditional.drivers) {
113113
if (!_assignedDriverToInputMap.containsKey(driver)) {
114114
final inputName = _portUniquifier.getUniqueName(
115-
initialName: Module.unpreferredName(
115+
initialName: Naming.unpreferredName(
116116
Sanitizer.sanitizeSV('in${idx}_${driver.name}')));
117117
addInput(inputName, driver, width: driver.width);
118118
_assignedDriverToInputMap[driver] = input(inputName);
@@ -122,7 +122,7 @@ abstract class _Always extends Module with CustomSystemVerilog {
122122
for (final receiver in conditional.receivers) {
123123
if (!_assignedReceiverToOutputMap.containsKey(receiver)) {
124124
final outputName = _portUniquifier.getUniqueName(
125-
initialName: Module.unpreferredName(
125+
initialName: Naming.unpreferredName(
126126
Sanitizer.sanitizeSV('out${idx}_${receiver.name}')));
127127
addOutput(outputName, width: receiver.width);
128128
_assignedReceiverToOutputMap[receiver] = output(outputName);
@@ -437,7 +437,7 @@ class Sequential extends _Always {
437437
_clks.add(addInput(
438438
_portUniquifier.getUniqueName(
439439
initialName: Sanitizer.sanitizeSV(
440-
Module.unpreferredName('clk${i}_${clk.name}'))),
440+
Naming.unpreferredName('clk${i}_${clk.name}'))),
441441
clk));
442442
_preTickClkValues.add(null);
443443
}
@@ -1612,22 +1612,22 @@ Logic flop(
16121612
/// Represents a single flip-flop with no reset.
16131613
class FlipFlop extends Module with CustomSystemVerilog {
16141614
/// Name for the enable input of this flop
1615-
final String _enName = Module.unpreferredName('en');
1615+
final String _enName = Naming.unpreferredName('en');
16161616

16171617
/// Name for the clk of this flop.
1618-
final String _clkName = Module.unpreferredName('clk');
1618+
final String _clkName = Naming.unpreferredName('clk');
16191619

16201620
/// Name for the input of this flop.
1621-
final String _dName = Module.unpreferredName('d');
1621+
final String _dName = Naming.unpreferredName('d');
16221622

16231623
/// Name for the output of this flop.
1624-
final String _qName = Module.unpreferredName('q');
1624+
final String _qName = Naming.unpreferredName('q');
16251625

16261626
/// Name for the reset of this flop.
1627-
final String _resetName = Module.unpreferredName('reset');
1627+
final String _resetName = Naming.unpreferredName('reset');
16281628

16291629
/// Name for the reset value of this flop.
1630-
final String _resetValueName = Module.unpreferredName('resetValue');
1630+
final String _resetValueName = Naming.unpreferredName('resetValue');
16311631

16321632
/// The clock, posedge triggered.
16331633
late final Logic _clk = input(_clkName);

0 commit comments

Comments
 (0)