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
5 changes: 4 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ analyzer:
strict-raw-types: true
exclude:
- doc/tutorials/chapter_9/rohd_vf_example
- packages/rohd_hierarchy
- rohd_devtools_extension

# keep up to date, matching https://dart.dev/tools/linter-rules/all
Expand Down Expand Up @@ -129,7 +130,9 @@ linter:
- overridden_fields
- package_names
- package_prefixed_library_names
- parameter_assignments
# parameter_assignments - disabled; ROHD idiomatically reassigns
# constructor parameters via addInput/addOutput.
# - parameter_assignments
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_asserts_with_message
Expand Down
3 changes: 0 additions & 3 deletions lib/src/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,6 @@ abstract class Module {
}

if (source is LogicStructure) {
// ignore: parameter_assignments
source = source.packed;
}

Expand Down Expand Up @@ -704,7 +703,6 @@ abstract class Module {
String name, LogicType source) {
_checkForSafePortName(name);

// ignore: parameter_assignments
source = _validateType<LogicType>(source, isOutput: false, name: name);

if (source.isNet || (source is LogicStructure && source.hasNets)) {
Expand Down Expand Up @@ -813,7 +811,6 @@ abstract class Module {
throw PortTypeException(source, 'Typed inOuts must be nets.');
}

// ignore: parameter_assignments
source = _validateType<LogicType>(source, isOutput: false, name: name);

_inOutDrivers.add(source);
Expand Down
1 change: 0 additions & 1 deletion lib/src/signals/logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,6 @@ class Logic {
// If we are connecting a `LogicStructure` to this simple `Logic`,
// then pack it first.
if (other is LogicStructure) {
// ignore: parameter_assignments
other = other.packed;
}

Expand Down
1 change: 0 additions & 1 deletion lib/src/signals/wire_net.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ class _WireNetBlasted extends _Wire implements _WireNet {
other as _WireNet;

if (other is! _WireNetBlasted) {
// ignore: parameter_assignments
other = other.toBlasted();
}

Expand Down
1 change: 0 additions & 1 deletion lib/src/utilities/simcompare.dart
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ abstract class SimCompare {
: 'logic');

if (adjust != null) {
// ignore: parameter_assignments
signalName = adjust(signalName);
}

Expand Down
3 changes: 0 additions & 3 deletions lib/src/values/logic_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ abstract class LogicValue implements Comparable<LogicValue> {

if (val.width == 1 && (!val.isValid || fill)) {
if (!val.isValid) {
// ignore: parameter_assignments
width ??= 1;
}
if (width == null) {
Expand All @@ -243,7 +242,6 @@ abstract class LogicValue implements Comparable<LogicValue> {

if (val.length == 1 && (val == 'x' || val == 'z' || fill)) {
if (val == 'x' || val == 'z') {
// ignore: parameter_assignments
width ??= 1;
}
if (width == null) {
Expand All @@ -269,7 +267,6 @@ abstract class LogicValue implements Comparable<LogicValue> {
if (val.length == 1 &&
(val.first == LogicValue.x || val.first == LogicValue.z || fill)) {
if (!val.first.isValid) {
// ignore: parameter_assignments
width ??= 1;
}
if (width == null) {
Expand Down
205 changes: 205 additions & 0 deletions packages/rohd_hierarchy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# rohd_hierarchy

An incremental design dictionary for hardware module hierarchies.

## Motivation

A remote agent — a debugger, a waveform viewer, a schematic renderer, an
AI assistant — needs to understand the structure of a hardware design in order
to ask useful questions about it. Transferring the full design every time is
wasteful. What both sides of a link really need is a shared **dictionary** of
the design: the modules, instances, ports, and signals that make it up, plus
a compact way to refer to any object by address.

Once both sides share the same dictionary, communication becomes cheap:
either side can request data about a specific object by its address alone,
without re-transmitting structural context.

### What is a design dictionary?

A design dictionary captures the **hierarchy and connectivity** of a
hardware design:

- **Modules** — the reusable definitions (e.g. `Counter`, `ALU`).
- **Instances** — placed copies of modules within a parent.
- **Ports** — directional signals on a module boundary (input, output, inout).
- **Signals** — internal wires and registers.

The full "unfolded" view of a design is its **address space**: every instance,
every port, every signal reachable by walking the hierarchy tree.

### Compact, canonical addressing

`rohd_hierarchy` assigns each object a **canonical address** — a short
sequence of child indices (e.g. `0.2.4`) that uniquely identifies it within
the tree.

Addresses are **relative within each module**: a module's address table
maps local indices to its children and signals without relying on any
global namespace. This locality property is what makes the dictionary
**incrementally expandable** — a remote agent can:

1. Request the top-level dictionary table (the root module's children and
signals).
2. Drill into any child by requesting that child's dictionary table.
3. Continue expanding only the parts of the hierarchy it actually needs.

At each step, both sides agree on the addresses, so subsequent data
requests (waveform samples, signal values, schematic fragments) carry
only the compact address, not the full path or structural description.

## Package overview

`rohd_hierarchy` is a source-agnostic Dart package that implements this
dictionary model. It provides data models, search utilities, and adapter
interfaces that work independently of any particular HDL toolchain or
transport layer.

### Data models

- **`HierarchyNode`** — A tree node representing a module or instance,
with children, signals, name, kind, and a primitive flag. Call
`buildAddresses()` to assign a canonical `HierarchyAddress` to every
node and signal in O(n).
- **`HierarchyAddress`** — An immutable, index-based path through the
tree (e.g. `[0, 2, 4]`). Supports conversion to/from dot-separated
strings. Works as an O(1) cache key.
- **`Signal` / `Port`** — Signal metadata: name, width, type, direction,
full path. `Port` extends `Signal` with a required direction.

### Services & adapters

- **`HierarchyService`** — A mixin providing tree-walking search and
navigation: `searchSignals()`, `searchModules()`,
`autocompletePaths()`, glob-star regex search, and address↔pathname
conversion.
- **`BaseHierarchyAdapter`** — An abstract class wrapping a
`HierarchyNode` tree with `HierarchyService`. Use
`BaseHierarchyAdapter.fromTree()` to wrap an existing tree.
- **`NetlistHierarchyAdapter`** — A concrete adapter that parses Yosys
JSON netlists into a `HierarchyNode` tree.

### Search controller

- **`HierarchySearchController<R>`** — A pure-Dart controller for
keyboard-navigable search result lists, with `updateQuery()`,
`selectNext()` / `selectPrevious()`, `tabComplete()`, and scroll-offset
helpers. Factories `forSignals()` and `forModules()` cover the common
cases.

## Usage

### Building a dictionary from a Yosys netlist

```dart
import 'package:rohd_hierarchy/rohd_hierarchy.dart';

final dict = NetlistHierarchyAdapter.fromJson(yosysJsonString);
final root = dict.root; // the top-level dictionary table
```

### Wrapping an existing tree

When you already have a `HierarchyNode` tree (e.g. from a VCD parser, a
ROHD simulation, or any other source), wrap it to gain search and address
resolution:

```dart
final dict = BaseHierarchyAdapter.fromTree(rootNode);
```

### Incremental expansion by a remote agent

A remote agent does not need the full tree up front. It can expand the
dictionary one level at a time:

```dart
// Agent receives the root table
final root = dict.root;

// Agent picks a child to expand (e.g. child 2)
final child = root.children[2];

// The child's own children and signals are its local dictionary table.
// The agent now knows addresses 2.0, 2.1, ... for that subtree.
```

### Compact address-based communication

Once both sides share the dictionary, data requests use addresses only:

```dart
// Resolve a human-readable pathname to a canonical address
final addr = dict.pathnameToAddress('Counter.clk');

// Send the compact address over the wire: "0.1"
final wire = addr!.toDotString();

// The other side resolves it back
final resolved = dict.nodeByAddress(HierarchyAddress.fromDotString(wire));
final pathname = dict.addressToPathname(addr!);
```

### Searching the dictionary

```dart
final signals = dict.searchSignals('clk');
final modules = dict.searchModules('counter');
final completions = dict.autocompletePaths('top.cpu.');
```

### Constructing nodes manually

```dart
final root = HierarchyNode(
id: 'Counter',
name: 'Counter',
kind: HierarchyKind.module,
type: 'Counter',
signals: [
Port(
id: 'Counter.clk', name: 'clk', direction: 'input',
width: 1, type: 'bin', fullPath: 'Counter.clk', scopeId: 'Counter',
),
Port(
id: 'Counter.count', name: 'count', direction: 'output',
width: 8, type: 'bin', fullPath: 'Counter.count', scopeId: 'Counter',
),
],
children: [
HierarchyNode(
id: 'Counter.adder', name: 'adder',
kind: HierarchyKind.instance, type: 'Adder',
signals: [], children: [],
),
],
);
```

### Enriching signals from another source

Signals from one source (e.g. a VCD file) can be upgraded with metadata
from the design dictionary:

```dart
final designSignal = dict.signalByAddress(addr);
if (designSignal is Port) {
node.signals[i] = Port(
id: vcdSignal.id, name: vcdSignal.name,
type: vcdSignal.type, width: vcdSignal.width,
direction: designSignal.direction!,
fullPath: vcdSignal.fullPath, scopeId: vcdSignal.scopeId,
);
}
```

## Design principles

| Principle | How it is achieved |
|---|---|
| **Source-agnostic** | The data model is independent of any HDL toolchain. `NetlistHierarchyAdapter` handles Yosys JSON; `BaseHierarchyAdapter.fromTree()` wraps any tree. |
| **Incremental** | Addresses are relative within each module. A remote agent expands only the subtrees it needs, one dictionary table at a time. |
| **Compact** | `HierarchyAddress` is a short index path (e.g. `0.2.4`), not a full dotted pathname. Both sides resolve it locally. |
| **Canonical** | `buildAddresses()` assigns deterministic indices in tree order. The same design always produces the same addresses. |
| **No global namespace** | Each module's address table is self-contained. Adding or removing a sibling subtree does not invalidate addresses in unrelated parts of the tree. |
| **Transport-independent** | The package defines the dictionary model, not the wire protocol. Any transport (VM service, JSON-RPC, gRPC, WebSocket) can carry the compact addresses. |
5 changes: 5 additions & 0 deletions packages/rohd_hierarchy/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
analyzer:
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
52 changes: 52 additions & 0 deletions packages/rohd_hierarchy/lib/rohd_hierarchy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// rohd_hierarchy.dart
// Main library export for rohd_hierarchy package.
//
// 2026 January
// Author: Desmond Kirkpatrick <desmond.a.kirkpatrick@intel.com>

/// Generic hierarchy data models for hardware module navigation.
///
/// This library provides source-agnostic data models for representing
/// hardware module hierarchies:
///
/// ## Core Data Models
/// - [HierarchyAddress] - Efficient index-based addressing for tree navigation
/// - [HierarchyNode] - A node in the module hierarchy (module or instance)
/// - [HierarchyKind] - Enum for node types (module, instance)
/// - [Signal] - A signal in the hierarchy (wire, reg, port)
/// - [Port] - An I/O port on a module (Signal subclass)
///
/// ## Search & Navigation
/// - [SignalSearchResult] - Result of a signal search with enriched metadata
/// - [ModuleSearchResult] - Result of a module search with enriched metadata
/// - [HierarchyService] - Abstract interface for hierarchy navigation
/// - [HierarchySearchController] - Pure Dart search state controller
///
/// ## Adapters
/// - [BaseHierarchyAdapter] - Base class with shared adapter implementation
/// - [NetlistHierarchyAdapter] - Adapter for netlist format (Yosys JSON / ROHD)
///
/// This package has no dependencies and can be used standalone by any
/// application that needs to navigate hardware hierarchies.
///
/// ## Quick Start
/// ```dart
/// // 1. Create hierarchy
/// final root = HierarchyNode(id: 'top', name: 'top',
/// kind: HierarchyKind.module);
/// root.buildAddresses(); // Enable address-based navigation
///
/// // 2. Search
/// final service = BaseHierarchyAdapter.fromTree(root);
/// final results = service.searchSignals('clk');
/// ```
library;

export 'src/base_hierarchy_adapter.dart';
export 'src/hierarchy_models.dart';
export 'src/hierarchy_search_controller.dart';
export 'src/hierarchy_service.dart';
export 'src/netlist_hierarchy_adapter.dart';
Loading
Loading