Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions lib/rohd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export 'src/external.dart';
export 'src/finite_state_machine.dart';
export 'src/interfaces/interfaces.dart';
export 'src/module.dart';
export 'src/module_parameter.dart';
export 'src/modules/modules.dart';
export 'src/selection.dart';
export 'src/signals/signals.dart';
Expand Down
64 changes: 55 additions & 9 deletions lib/src/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@ abstract class Module {
/// An internal mapping of inOut names to their sources to this [Module].
late final Map<String, Logic> _inOutSources = {};

/// Registered [ModuleParameter]s for this [Module].
///
/// These are used during SystemVerilog generation to emit `parameter` or
/// `localparam` declarations in the module definition header.
///
/// Use [addModuleParameter] to register parameters.
final List<ModuleParameter<dynamic>> _moduleParameters = [];

/// An unmodifiable view of the registered [ModuleParameter]s.
List<ModuleParameter<dynamic>> get moduleParameters =>
List.unmodifiable(_moduleParameters);

/// Registers a [ModuleParameter] on this [Module].
///
/// The parameter will appear in generated SystemVerilog as a `parameter` or
/// `localparam` declaration in the module definition header.
void addModuleParameter(ModuleParameter<dynamic> param) {
_moduleParameters.add(param);
}

/// The parent [Module] of this [Module].
///
/// This only gets populated after its parent [Module], if it exists, has
Expand Down Expand Up @@ -660,7 +680,8 @@ abstract class Module {
/// The return value is the same as what is returned by [input] and should
/// only be used within this [Module]. The provided [source] is accessible via
/// [inputSource].
Logic addInput(String name, Logic source, {int width = 1}) {
Logic addInput(String name, Logic source,
{int width = 1, ParameterExpression? widthExpression}) {
_checkForSafePortName(name);
if (source.width != width) {
throw PortWidthMismatchException(source, width);
Expand All @@ -670,7 +691,11 @@ abstract class Module {
source = source.packed;
}

final inPort = Logic(name: name, width: width, naming: Naming.reserved)
final inPort = Logic(
name: name,
width: width,
naming: Naming.reserved,
widthExpression: widthExpression)
..gets(source)
..parentModule = this;

Expand Down Expand Up @@ -741,7 +766,8 @@ abstract class Module {
/// The return value is the same as what is returned by [inOut] and should
/// only be used within this [Module]. The provided [source] is accessible via
/// [inOutSource].
LogicNet addInOut(String name, Logic source, {int width = 1}) {
LogicNet addInOut(String name, Logic source,
{int width = 1, ParameterExpression? widthExpression}) {
_checkForSafePortName(name);
if (source.width != width) {
throw PortWidthMismatchException(source, width);
Expand Down Expand Up @@ -773,10 +799,13 @@ abstract class Module {
_inOutDrivers.add(source.packed);
}

final inOutPort =
LogicNet(name: name, width: width, naming: Naming.reserved)
..parentModule = this
..gets(source);
final inOutPort = LogicNet(
name: name,
width: width,
naming: Naming.reserved,
widthExpression: widthExpression)
..parentModule = this
..gets(source);

_inOuts[name] = inOutPort;

Expand Down Expand Up @@ -868,6 +897,8 @@ abstract class Module {
List<int> dimensions = const [1],
int elementWidth = 1,
int numUnpackedDimensions = 0,
List<ParameterExpression?>? dimensionExpressions,
ParameterExpression? elementWidthExpression,
}) {
_checkForSafePortName(name);

Expand All @@ -877,6 +908,8 @@ abstract class Module {
elementWidth,
numUnpackedDimensions: numUnpackedDimensions,
naming: Naming.reserved,
dimensionExpressions: dimensionExpressions,
elementWidthExpression: elementWidthExpression,
)
..gets(source)
..setAllParentModule(this);
Expand All @@ -892,10 +925,15 @@ abstract class Module {
/// can be driven by this [Module] or consumed outside of it.
///
/// The return value is the same as what is returned by [output].
Logic addOutput(String name, {int width = 1}) {
Logic addOutput(String name,
{int width = 1, ParameterExpression? widthExpression}) {
_checkForSafePortName(name);

final outPort = Logic(name: name, width: width, naming: Naming.reserved)
final outPort = Logic(
name: name,
width: width,
naming: Naming.reserved,
widthExpression: widthExpression)
..parentModule = this;

_outputs[name] = outPort;
Expand Down Expand Up @@ -992,6 +1030,8 @@ abstract class Module {
List<int> dimensions = const [1],
int elementWidth = 1,
int numUnpackedDimensions = 0,
List<ParameterExpression?>? dimensionExpressions,
ParameterExpression? elementWidthExpression,
}) {
_checkForSafePortName(name);

Expand All @@ -1001,6 +1041,8 @@ abstract class Module {
elementWidth,
numUnpackedDimensions: numUnpackedDimensions,
naming: Naming.reserved,
dimensionExpressions: dimensionExpressions,
elementWidthExpression: elementWidthExpression,
)..setAllParentModule(this);

_outputs[name] = outArr;
Expand All @@ -1022,6 +1064,8 @@ abstract class Module {
List<int> dimensions = const [1],
int elementWidth = 1,
int numUnpackedDimensions = 0,
List<ParameterExpression?>? dimensionExpressions,
ParameterExpression? elementWidthExpression,
}) {
_checkForSafePortName(name);

Expand All @@ -1046,6 +1090,8 @@ abstract class Module {
elementWidth,
numUnpackedDimensions: numUnpackedDimensions,
naming: Naming.reserved,
dimensionExpressions: dimensionExpressions,
elementWidthExpression: elementWidthExpression,
)
..gets(source)
..setAllParentModule(this);
Expand Down
227 changes: 227 additions & 0 deletions lib/src/module_parameter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// module_parameter.dart
// Definition of configurable module parameters for SV generation.
//
// 2026 June
// Author: Joel Kimmel

import 'package:rohd/rohd.dart';

/// Represents a configurable parameter on a [Module] that maps to a
/// SystemVerilog `parameter` or `localparam` declaration.
///
/// In simulation, [ModuleParameter] wraps a concrete Dart value of type [T]
/// and behaves identically to that value. In generated SystemVerilog, it
/// appears as a named parameter in the module definition.
///
/// Example:
/// ```dart
/// final width = ModuleParameter<int>('WIDTH', defaultValue: 8);
/// final depth = ModuleParameter<int>('DEPTH',
/// defaultValue: 256, isLocalParam: true);
/// ```
class ModuleParameter<T> {
/// The SystemVerilog name for this parameter (e.g., `'WIDTH'`).
final String name;

/// The concrete value of this parameter used during simulation.
final T defaultValue;

/// The SystemVerilog type string for this parameter (e.g., `'int'`).
///
/// If not provided, it is inferred from [T]:
/// - `int` → `'int'`
/// - `bool` → `'bit'`
/// - Otherwise, must be provided explicitly.
final String svType;

/// Whether this parameter is a `localparam` (cannot be overridden at
/// instantiation) rather than a `parameter`.
final bool isLocalParam;

/// A SystemVerilog expression for the default value.
///
/// If not provided, it is generated from [defaultValue] using
/// [_defaultSvValue].
final String? svDefaultValue;

/// Creates a [ModuleParameter] with the given [name] and [defaultValue].
///
/// The [svType] is inferred from [T] if not provided. Set [isLocalParam]
/// to `true` for a `localparam` declaration.
ModuleParameter(
this.name, {
required this.defaultValue,
String? svType,
this.isLocalParam = false,
this.svDefaultValue,
}) : svType = svType ?? _inferSvType<T>();

/// Infers a SystemVerilog type string from the Dart type [U].
static String _inferSvType<U>() {
if (U == int) {
return 'int';
}
if (U == bool) {
return 'bit';
}
throw ArgumentError('Cannot infer SV type for Dart type $U. '
'Provide an explicit svType.');
}

/// Returns the SV default value string for this parameter.
String get svDefault => svDefaultValue ?? _defaultSvValue();

/// Generates a default SV value string from [defaultValue].
String _defaultSvValue() {
final v = defaultValue;
if (v is int) {
return '$v';
}
if (v is bool) {
return v ? "1'b1" : "1'b0";
}
return '$v';
}

/// Converts this parameter to a [SystemVerilogParameterDefinition] suitable
/// for inclusion in a module definition header.
SystemVerilogParameterDefinition toSvParameterDefinition() =>
SystemVerilogParameterDefinition(
name,
type: svType,
defaultValue: svDefault,
isLocalParam: isLocalParam,
);

/// Creates a [ParameterExpression] from this parameter.
///
/// Only valid for `int` parameters.
ParameterExpression toExpression() {
if (defaultValue is! int) {
throw StateError('toExpression() is only supported for int parameters, '
'but $name has type $T.');
}
return ParameterExpression.ofParam(this as ModuleParameter<int>);
}

@override
String toString() => 'ModuleParameter($name=$defaultValue)';
}

/// Represents a value that has both a concrete Dart [int] value (for
/// simulation) and a SystemVerilog expression string (for code generation).
///
/// This is used wherever widths or integer values flow into SV generation
/// and need to remain symbolic (e.g., `WIDTH - 1` instead of `7`).
///
/// Example:
/// ```dart
/// final widthParam = ModuleParameter<int>('WIDTH', defaultValue: 8);
/// final widthExpr = ParameterExpression.ofParam(widthParam);
/// final rangeExpr = widthExpr - 1; // value=7, svExpression='WIDTH - 1'
/// ```
class ParameterExpression {
/// The concrete Dart value, used during simulation.
final int value;

/// The SystemVerilog expression string, used during code generation.
final String svExpression;

/// Creates a [ParameterExpression] with explicit [value] and
/// [svExpression].
const ParameterExpression(this.value, this.svExpression);

/// Creates a [ParameterExpression] from a [ModuleParameter].
///
/// The [svExpression] is set to the parameter's [ModuleParameter.name].
ParameterExpression.ofParam(ModuleParameter<int> param)
: value = param.defaultValue,
svExpression = param.name;

/// Creates a [ParameterExpression] from a plain integer constant.
///
/// The [svExpression] is the decimal string representation of [value].
ParameterExpression.ofInt(this.value) : svExpression = '$value';

/// Addition.
ParameterExpression operator +(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value + other.value, '$svExpression + ${other.svExpression}');
}
if (other is int) {
return ParameterExpression(value + other, '$svExpression + $other');
}
throw ArgumentError('Cannot add $runtimeType and ${other.runtimeType}');
}

/// Subtraction.
ParameterExpression operator -(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value - other.value, '$svExpression - ${other.svExpression}');
}
if (other is int) {
return ParameterExpression(value - other, '$svExpression - $other');
}
throw ArgumentError(
'Cannot subtract $runtimeType and ${other.runtimeType}');
}

/// Multiplication.
ParameterExpression operator *(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value * other.value, '($svExpression) * (${other.svExpression})');
}
if (other is int) {
return ParameterExpression(value * other, '($svExpression) * $other');
}
throw ArgumentError(
'Cannot multiply $runtimeType and ${other.runtimeType}');
}

/// Integer division.
ParameterExpression operator ~/(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value ~/ other.value, '($svExpression) / (${other.svExpression})');
}
if (other is int) {
return ParameterExpression(value ~/ other, '($svExpression) / $other');
}
throw ArgumentError('Cannot divide $runtimeType and ${other.runtimeType}');
}

/// Left shift.
ParameterExpression operator <<(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value << other.value, '($svExpression) << (${other.svExpression})');
}
if (other is int) {
return ParameterExpression(value << other, '($svExpression) << $other');
}
throw ArgumentError(
'Cannot left-shift $runtimeType by ${other.runtimeType}');
}

/// Right shift.
ParameterExpression operator >>(Object other) {
if (other is ParameterExpression) {
return ParameterExpression(
value >> other.value, '($svExpression) >> (${other.svExpression})');
}
if (other is int) {
return ParameterExpression(value >> other, '($svExpression) >> $other');
}
throw ArgumentError(
'Cannot right-shift $runtimeType by ${other.runtimeType}');
}

@override
String toString() => 'ParameterExpression($value, "$svExpression")';
}
Loading
Loading