Skip to content
Open
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
46 changes: 45 additions & 1 deletion doc/user_guide/_docs/A20-logic-arrays.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Logic Arrays"
permalink: /docs/logic-arrays/
last_modified_at: 2022-6-5
last_modified_at: 2026-7-21
toc: true
---

Expand All @@ -22,6 +22,48 @@ LogicArray([5, 5, 5], 128);

As long as the total width of a `LogicArray` and another type of `Logic` (including `Logic`, `LogicStructure`, and another `LogicArray`) are the same, assignments and bitwise operations will work in per-element order. This means you can assign two `LogicArray`s of different dimensions to each other as long as the total width matches.

## Typed and value-domain arrays

`LogicArrayOf` extends `LogicArray` when every leaf should have the same specialized `Logic` type. It preserves the normal array dimensions while exposing typed leaves with `typedLeafElements` and `elementAt`. For example, this creates a two-dimensional array of samples with separate data and valid fields:

```dart
class Sample extends LogicStructure {
final Logic data;
final Logic valid;

factory Sample({String? name}) => Sample._(
Logic(name: 'data', width: 8),
Logic(name: 'valid'),
name: name ?? 'sample',
);

Sample._(this.data, this.valid, {required String name})
: super([data, valid], name: name);

@override
Sample clone({String? name}) => Sample(name: name ?? this.name);
}

final samples = LogicArrayOf<Sample>(
[2, 3],
Sample.new,
dimensionNames: ['row_', 'column_'],
);

final bottomRightData = samples.elementAt([1, 2]).data;
```

Use `LogicValueArray` for fixed-width array data outside the hardware graph. It keeps values in row-major order and supports indexing, reshaping, transposition, and slice operations. `LogicValueArrayOf` adds a codec so application-level values can use the same operations while converting to and from packed `LogicValue`s.

```dart
final values = LogicValueArray.fromInts([2, 3], 8, [1, 2, 3, 4, 5, 6]);
final transposed = values.transpose2D(); // Dimensions: [3, 2]

final signals = values.toLogicArray(name: 'values');
```

`LogicValueArray.putInto` drives a compatible `LogicArray`, while `LogicArrayOf.logicValues` captures its current packed values. Use `LogicArrayOf.valueArrayOf` and `putValueArrayOf` when a `LogicValueCodec` converts typed value-domain data at the hardware boundary.

## Unpacked arrays

In SystemVerilog, there is a concept of "packed" vs. "unpacked" arrays which have different use cases and capabilities. In ROHD, all arrays act the same and you get the best of both worlds. You can indicate when constructing a `LogicArray` that some number of the dimensions should be "unpacked" as a hint to `Synthesizer`s. Marking an array with a non-zero `numUnpackedDimensions`, for example, will make that many of the dimensions "unpacked" in generated SystemVerilog signal declarations.
Expand All @@ -43,6 +85,8 @@ You can declare ports of `Module`s as being arrays (including with some dimensio

Array ports in generated SystemVerilog will match dimensions (including unpacked) as specified when the port is created.

Use `addTypedInput` and `addTypedOutput` for `LogicArrayOf` ports. These methods preserve the array's specialized leaf type, allowing the module to access fields such as `samples.elementAt([1, 2]).data` directly.

## Elements of arrays

To iterate through or access elements of a `LogicArray` (or bits of a simple `Logic`), use [`elements`](https://intel.github.io/rohd/rohd/Logic/elements.html). Using the normal `[n]` accessors will return the `n`th bit regardless for `LogicArray` and `Logic` to maintain API consistency.
Expand Down
92 changes: 90 additions & 2 deletions lib/src/signals/logic_array.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023-2024 Intel Corporation
// Copyright (C) 2023-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// logic_array.dart
Expand All @@ -24,6 +24,15 @@ class LogicArray extends LogicStructure {
/// [elementWidth] is always 0.
final int elementWidth;

/// Elements at the leaf dimension of this array.
///
/// Unlike [LogicStructure.leafElements], traversal stops at the configured
/// array leaf. This distinction matters when an array leaf is itself a
/// [LogicStructure], such as a typed floating-point value.
late final List<Logic> arrayElements = UnmodifiableListView(
_calculateArrayElements(),
);

@override
final Naming naming;

Expand Down Expand Up @@ -90,6 +99,73 @@ class LogicArray extends LogicStructure {
isNet: true,
);

/// Creates an array from pre-built [elements].
///
/// This constructor supports subclasses whose leaf dimension contains a
/// specialized [Logic] or [LogicStructure]. For arrays with more than one
/// dimension, [elements] must be [LogicArray]s matching the remaining
/// dimensions. For a one-dimensional array, each element must have
/// [elementWidth] bits.
@protected
LogicArray.structured(
super.elements, {
required List<int> dimensions,
required this.elementWidth,
String? name,
this.numUnpackedDimensions = 0,
Naming? naming,
this.isNet = false,
}) : dimensions = List<int>.unmodifiable(dimensions),
naming = Naming.chooseNaming(name, naming),
super(
name: Naming.chooseName(name, naming, nullStarter: 'a'),
) {
if (dimensions.isEmpty) {
throw LogicConstructionException(
'Arrays must have at least 1 dimension.',
);
}
if (dimensions.any((dimension) => dimension < 0)) {
throw LogicConstructionException(
'Array dimensions must be non-negative.',
);
}
if (numUnpackedDimensions > dimensions.length) {
throw LogicConstructionException(
'Cannot unpack more than all of the dimensions.',
);
}
if (elements.length != dimensions.first) {
throw LogicConstructionException(
'Array elements must match the first dimension.',
);
}

if (dimensions.length == 1) {
if (elements.any((element) => element.width != elementWidth)) {
throw LogicConstructionException(
'Array leaves must match elementWidth.',
);
}
} else {
final childDimensions = dimensions.sublist(1);
if (elements.any(
(element) =>
element is! LogicArray ||
!_sameDimensions(element.dimensions, childDimensions) ||
element.elementWidth != elementWidth,
)) {
throw LogicConstructionException(
'Child arrays must match the remaining dimensions and width.',
);
}
}

for (final (index, element) in elements.indexed) {
element._arrayIndex = index;
}
}

/// Internal factory constructor.
///
/// Creates an array with specified [dimensions] and [elementWidth] named
Expand Down Expand Up @@ -226,6 +302,13 @@ class LogicArray extends LogicStructure {
required this.isNet,
});

List<Logic> _calculateArrayElements() => dimensions.length == 1
? elements
: elements
.cast<LogicArray>()
.expand((element) => element.arrayElements)
.toList(growable: false);

/// Constructs a new [LogicArray] with a more convenient constructor signature
/// for when many ports in an interface are declared together. Also performs
/// some basic checks on the legality of the array as a port of a [Module].
Expand All @@ -239,7 +322,8 @@ class LogicArray extends LogicStructure {

return LogicArray(
dimensions, elementWidth,
numUnpackedDimensions: numUnpackedDimensions, name: name,
numUnpackedDimensions: numUnpackedDimensions,
name: name,

// make port names mergeable so we don't duplicate the ports
// when calling connectIO
Expand Down Expand Up @@ -270,3 +354,7 @@ class LogicArray extends LogicStructure {
);
}
}

bool _sameDimensions(List<int> left, List<int> right) =>
left.length == right.length &&
left.indexed.every((entry) => entry.$2 == right[entry.$1]);
163 changes: 163 additions & 0 deletions lib/src/signals/logic_array_of.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// logic_array_of.dart
// Definition of typed logic arrays.
//
// 2026 July 21
// Author: Desmond A. Kirkpatrick <desmond.a.kirkpatrick@intel.com>

part of 'signals.dart';

/// Builds one leaf element of a [LogicArrayOf].
typedef LogicArrayElementBuilder<T extends Logic> = T Function({String? name});

/// A multidimensional logic array with leaves of type [T].
///
/// Intermediate dimensions are [LogicArray]s, while the configured array leaf
/// can be any [Logic], including a [LogicStructure] such as a floating-point
/// signal. Packed conversion happens only at that leaf boundary.
class LogicArrayOf<T extends Logic> extends LogicArray {
/// Labels used to name elements at each dimension.
final List<String> dimensionNames;

final LogicArrayElementBuilder<T> _elementBuilder;

late final List<T> _typedLeafElements = List<T>.unmodifiable(
arrayElements.cast<T>(),
);

/// Creates an array with [dimensions] and typed leaves from [elementBuilder].
LogicArrayOf(
List<int> dimensions,
LogicArrayElementBuilder<T> elementBuilder, {
List<String>? dimensionNames,
String? name,
}) : this._(
_LogicArrayOfBuild.build(dimensions, elementBuilder,
dimensionNames: dimensionNames),
elementBuilder,
name: name);

LogicArrayOf._(
_LogicArrayOfBuild<T> build,
this._elementBuilder, {
String? name,
}) : dimensionNames = build.dimensionNames,
super.structured(build.elements,
dimensions: build.dimensions,
elementWidth: build.elementWidth,
name: name);

/// Typed leaves in row-major order.
List<T> get typedLeafElements => UnmodifiableListView<T>(_typedLeafElements);

/// Typed leaves paired with their multidimensional indices.
Iterable<(List<int>, T)> get indexedElements =>
indexedLeaves.map((entry) => (entry.$1, entry.$2 as T));

/// Returns the typed leaf at multidimensional [indices].
T elementAt(List<int> indices) => at(indices) as T;

/// Current packed leaves in the value domain.
LogicValueArray get logicValues => LogicValueArray.fromLogicArray(this);

/// Decodes current packed leaves into semantic values using [codec].
LogicValueArrayOf<U> valueArrayOf<U>(LogicValueCodec<U> codec) =>
LogicValueArrayOf.fromLogicValues(logicValues, codec: codec);

/// Drives typed leaves from [values].
void putLogicValues(LogicValueArray values) => values.putInto(this);

/// Drives typed logic leaves from semantic [values].
void putValueArrayOf<U>(LogicValueArrayOf<U> values) =>
putLogicValues(values.logicValues);

/// Packs typed leaves into a conventional [LogicArray].
LogicArray toLogicArray({String? name}) =>
LogicArray(dimensions, elementWidth, name: name)
..getsEach(_typedLeafElements.map((element) => element.packed));

/// Drives typed leaves from a packed [LogicArray].
void getsPackedValues(LogicArray packedValues) {
_validateShape(packedValues.dimensions, packedValues.elementWidth);
for (final (target, source)
in _typedLeafElements.zipExact(packedValues.arrayElements)) {
target <= source;
}
}

void _validateShape(List<int> dimensions, int elementWidth) {
if (!_sameDimensions(this.dimensions, dimensions) ||
this.elementWidth != elementWidth) {
throw LogicConstructionException(
'Values must have dimensions ${this.dimensions} and '
'elementWidth ${this.elementWidth}.');
}
}

@override
LogicArrayOf<T> clone({String? name}) =>
LogicArrayOf(dimensions, _elementBuilder,
dimensionNames: dimensionNames, name: name ?? this.name);

@override
LogicArrayOf<T> named(String name, {Naming? naming}) =>
clone(name: name)..gets(this);
}

class _LogicArrayOfBuild<T extends Logic> {
final List<int> dimensions;
final List<String> dimensionNames;
final List<Logic> elements;
final int elementWidth;

_LogicArrayOfBuild._(
this.dimensions, this.dimensionNames, this.elements, this.elementWidth);

factory _LogicArrayOfBuild.build(
List<int> dimensions, LogicArrayElementBuilder<T> elementBuilder,
{List<String>? dimensionNames}) {
final normalizedDimensions = List<int>.unmodifiable(dimensions);
if (normalizedDimensions.isEmpty ||
normalizedDimensions.any((dimension) => dimension <= 0)) {
throw LogicConstructionException(
'LogicArrayOf dimensions must all be positive.');
}

final normalizedNames = List<String>.unmodifiable(
dimensionNames ??
Iterable.generate(
normalizedDimensions.length, (dimension) => 'd${dimension}_'),
);
if (normalizedNames.length != normalizedDimensions.length) {
throw LogicConstructionException(
'dimensionNames must match the number of dimensions.');
}

final elements = List<Logic>.generate(normalizedDimensions.first, (index) {
final elementName = '${normalizedNames.first}$index';
return normalizedDimensions.length == 1
? elementBuilder(name: elementName)
: LogicArrayOf<T>(normalizedDimensions.sublist(1), elementBuilder,
dimensionNames: normalizedNames.sublist(1), name: elementName);
}, growable: false);
final typedLeaves = normalizedDimensions.length == 1
? elements.cast<T>().toList(growable: false)
: elements
.cast<LogicArrayOf<T>>()
.expand((element) => element.typedLeafElements)
.toList(growable: false);
return _LogicArrayOfBuild._(normalizedDimensions, normalizedNames, elements,
_validateElementWidths(typedLeaves));
}

static int _validateElementWidths<T extends Logic>(List<T> elements) {
final width = elements.first.width;
if (elements.any((element) => element.width != width)) {
throw LogicConstructionException(
'All LogicArrayOf leaves must have the same width.');
}
return width;
}
}
Loading
Loading