Skip to content

Commit

Permalink
LogicNets, inOuts, and TriStateBuffer (support for bidirectiona…
Browse files Browse the repository at this point in the history
…l wires) (#485)
  • Loading branch information
mkorbel1 authored Jun 4, 2024
1 parent d1c79eb commit b39abb2
Show file tree
Hide file tree
Showing 52 changed files with 2,673 additions and 390 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## Next release

- Added `LogicNet`, `inOut`s, and `TriStateBuffer` to enable multi-directional wires, ports, and drivers.
- Deprecated `CustomSystemVerilog` in favor of `SystemVerilog`, which has similar functionality but supports `inOut` ports and collapses all ports into a single `ports` argument.
- Breaking: `ExternalSystemVerilogModule` and `InlineSystemVerilog` now extend `SystemVerilog` instead of `CustomSystemVerilog`, meaning the `instantiationVerilog` API arguments have been modified.
- Breaking: Increased minimum Dart SDK version to 3.0.0.
- Breaking: `Interface.connectIO` has an additional optional named argument for `inOutTags`. Implementations of `Interface` which override `connectIO` will need to be updated.
- Fixed a bug where `expressionlessInputs` may not have been honored in non-inline custom SystemVerilog modules.
- Fixed a bug where in some cases an `xor` between two `LogicValue`s could cause an exception due to a false width mismatch.
- Added better checking, error handling, and message when module hierarchy cannot be properly resolved (e.g. self-containing modules, modules within multiple hierarchies).
- Breaking: Updated APIs for `Synthesizer.synthesize` and down the stack to use a `Function` to calculate the instance type of a module instead of a `Map` look-up table.
- Added `srcConnections` API to `Logic` to make it easier to trace drivers of subtypes of `Logic` which contain multiple drivers.

## 0.5.3

- Added beta version of the ROHD DevTools Extension to aid in ROHD hardware debug by displaying module hierarchy and signal information visually and interactively (<https://github.com/intel/rohd/pull/435>).
Expand Down
7 changes: 1 addition & 6 deletions benchmark/many_seq_and_comb_benchmark.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 Intel Corporation
// Copyright (C) 2023-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// many_seq_and_comb_benchmark.dart
Expand Down Expand Up @@ -140,7 +140,6 @@ class _CombinationalWrapper extends Module {
mcu.address <= intf.address;
mcu.inputData <= intf.inputData;
intf.outputData <= mcu.outputData;
break;
case ManySeqAndCombCombConnectionType.manyCombs:
Combinational([mcu.clock < intf.clock]);
Combinational([mcu.enable < intf.enable]);
Expand All @@ -149,7 +148,6 @@ class _CombinationalWrapper extends Module {
Combinational([mcu.address < intf.address]);
Combinational([mcu.inputData < intf.inputData]);
Combinational([intf.outputData < mcu.outputData]);
break;
case ManySeqAndCombCombConnectionType.oneComb:
Combinational([
mcu.clock < intf.clock,
Expand All @@ -160,7 +158,6 @@ class _CombinationalWrapper extends Module {
mcu.inputData < intf.inputData,
intf.outputData < mcu.outputData,
]);
break;
case ManySeqAndCombCombConnectionType.manySsaCombs:
Combinational.ssa((s) => [mcu.clock < intf.clock]);
Combinational.ssa((s) => [mcu.enable < intf.enable]);
Expand All @@ -169,7 +166,6 @@ class _CombinationalWrapper extends Module {
Combinational.ssa((s) => [mcu.address < intf.address]);
Combinational.ssa((s) => [mcu.inputData < intf.inputData]);
Combinational.ssa((s) => [intf.outputData < mcu.outputData]);
break;
case ManySeqAndCombCombConnectionType.oneSsaComb:
Combinational.ssa((s) => [
mcu.clock < intf.clock,
Expand All @@ -180,7 +176,6 @@ class _CombinationalWrapper extends Module {
mcu.inputData < intf.inputData,
intf.outputData < mcu.outputData,
]);
break;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ A `LogicValue` represents a multi-bit (including 0-bit and 1-bit) 4-value (`1`,

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`.

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.
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 `SystemVerilog` or `InlineSystemVerilog`). Any time the input of a custom functionality `Module` toggles, the outputs should correspondingly change, if necessary.

### Simulator

Expand Down
5 changes: 1 addition & 4 deletions doc/tutorials/chapter_2/answers/exercise_2.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2023 Intel Corporation
// Copyright (C) 2023-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// exercise_2.dart
Expand All @@ -20,13 +20,10 @@ void gate(Logic a, Logic b, Logic c) {
switch (answer) {
case 'or':
c <= a | b;
break;
case 'nor':
c <= ~(a | b);
break;
case 'xor':
c <= a ^ b;
break;
}
}

Expand Down
2 changes: 1 addition & 1 deletion doc/user_guide/_docs/A02-logical_signals.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var bus = Logic(name: 'b', width: 8)

#### The value of a signal

You can access the current value of a signal using `value`. You cannot access this as part of synthesizable ROHD code. ROHD supports X and Z values and propogation. If the signal is valid (no X or Z in it), you can also convert it to an `int` with `value.toInt()` (ROHD will throw an exception otherwise). If the signal has more bits than a dart `int` (64 bits, usually), you need to use `value.toBigInt()` to get a `BigInt` (again, ROHD will throw an exception otherwise).
You can access the current value of a signal using `value`. You cannot access this as part of synthesizable ROHD code. ROHD supports X and Z values and propagation. If the signal is valid (no X or Z in it), you can also convert it to an `int` with `value.toInt()` (ROHD will throw an exception otherwise). If the signal has more bits than a dart `int` (64 bits, usually), you need to use `value.toBigInt()` to get a `BigInt` (again, ROHD will throw an exception otherwise).

The value of a `Logic` is of type [`LogicValue`](https://intel.github.io/rohd/rohd/LogicValue-class.html), with pre-defined constant bit values `x`, `z`, `one`, and `zero`. `LogicValue` has a number of built-in logical operations (like `&`, `|`, `^`, `+`, `-`, etc.).

Expand Down
18 changes: 9 additions & 9 deletions doc/user_guide/_docs/A08-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@
title: "Modules"
permalink: /docs/modules/
excerpt: "Modules"
last_modified_at: 2022-12-06
last_modified_at: 2024-6-3
toc: true
---

[`Module`](https://intel.github.io/rohd/rohd/Module-class.html)s are similar to modules in SystemVerilog. They have inputs and outputs and logic that connects them. There are a handful of rules that *must* be followed when implementing a module.

1. All logic within a `Module` must consume only inputs (from the `input` or `addInput` methods) to the Module either directly or indirectly.
2. Any logic outside of a `Module` must consume the signals only via outputs (from the `output` or `addOutput` methods) of the Module.
3. Logic must be defined *before* the call to `super.build()`, which *always* must be called at the end of the `build()` method if it is overidden.
1. Inputs and inOuts (from `input`, `addInput`, `inOut` or `addInOut` methods, or their array equivalents) return *internal* copies of ports that should be used inside a `Module`. Signals should not be consumed directly from outside a `Module`. Internal module logic should consume the internal versions. Logic outside the `Module` can drive to (or receive from, in the case of inOut) that `Module` only through the external copies, i.e. arguments passed to `addInput`, `addInOut`, etc.
2. Outputs (from `output` or `addOutput`, or their array equivalents) are the only way logic outside of a `Module` can consume signals from that `Module`. There are no internal vs. external versions of `output`s, so they may be consumed inside of `Module`s as well.
3. All logic must be defined *before* the call to `super.build()`. Logic should not be further defined after build.

The reasons for these rules have to do with how ROHD is able to determine which logic and `Module`s exist within a given Module and how ROHD builds connectivity. If these rules are not followed, generated outputs (including waveforms and SystemVerilog) may be unpredictable.

You should strive to build logic within the constructor of your `Module` (directly or via method calls within the constructor). This way any code can utilize your `Module` immediately after creating it. **Be careful** to consume the registered `input`s and drive the registered `output`s of your module, and not the "raw" parameters.
You should strive to build logic within the constructor of your `Module` (directly or via method calls within the constructor). This way any code can utilize your `Module` immediately after creating it. **Be careful** to consume the registered `input`s and drive the registered `output`s of your module, and not the "raw" external arguments passed to the constructor.

It is legal to put logic within an override of the `build` function, but that forces users of your module to always call `build` before it will be functionally usable for simple simulation. If you put logic in `build()`, ensure you put the call to `super.build()` *at the end* of the method.

Note that the `build()` method returns a `Future<void>`, not just `void`. This is because the `build()` method is permitted to consume real wallclock time in some cases, for example for setting up cosimulation with another simulator. If you expect your build to consume wallclock time, make sure the `Simulator` is aware it needs to wait before proceeding.
Note that the `build()` method returns a `Future<void>`, not just `void`. This is because the `build()` method is permitted to consume real wall clock time in some cases, for example for setting up cosimulation with another simulator. Make sure the `build` completes before the simulation begins.

It is not necessary to put all logic directly within a class that extends Module. You can put synthesizable logic in other functions and classes, as long as the logic eventually connects to an input or output of a module if you hope to convert it to SystemVerilog. Except where there is a desire for the waveforms and SystemVerilog generated to have module hierarchy, it is not necessary to use submodules within modules instead of plain classes or functions.
It is not necessary to put all logic directly within a class that extends Module. You can put synthesizable logic in other functions and classes, as long as the logic eventually connects to an input or output of a module if you hope to convert it to SystemVerilog. Except where there is a desire for the debug hierarchy, waveforms, SystemVerilog generated, etc. to have equivalent module hierarchy, it is not necessary to use submodules within modules instead of plain classes or functions.

The `Module` base class has an optional String argument 'name' which is an instance name.
The `Module` base class has an optional String argument `name` which is an instance name.

`Module`s have the below basic structure:

Expand All @@ -31,7 +31,7 @@ The `Module` base class has an optional String argument 'name' which is an insta
class MyModule extends Module {
// constructor
MyModule(Logic in1, {String name='mymodule'}) : super(name: name) {
MyModule(Logic in1) {
// add inputs in the constructor, passing in the Logic it is connected to
// it's a good idea to re-set the input parameters,
// so you don't accidentally use the wrong one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
title: "Custom module behavior with custom in-line SystemVerilog representation"
permalink: /docs/custom-module-behavior-inline-system-verilog/
excerpt: "Custom module behavior with custom in-line SystemVerilog representation"
last_modified_at: 2022-12-06
last_modified_at: 2024-6-3
toc: true
---

Many of the basic built-in gates in Dart implement custom behavior. An implementation of the NotGate is shown below as an example. There is different syntax for functions which can be inlined versus those which cannot (the ~ can be inlined). In this case, the `InlineSystemVerilog` mixin is used, but if it were not inlineable, you could use `CustomSystemVerilog`. Note that it is mandatory to provide an initial value computation when the module is first created for non-sequential modules.
Many of the basic built-in gates in Dart implement custom behavior. An implementation of the `NotGate` is shown below as an example. There is different syntax for functions which can be inlined versus those which cannot (the `~` can be inlined). In this case, the `InlineSystemVerilog` mixin is used, but if it were not inlineable, you could use the `SystemVerilog` mixin instead. Note that it is mandatory to provide an initial value computation when the module is first created for non-sequential modules.

```dart
/// A gate [Module] that performs bit-wise inversion.
Expand All @@ -18,17 +18,17 @@ class NotGate extends Module with InlineSystemVerilog {
late final String _outName;
/// The input to this [NotGate].
Logic get _in => input(_inName);
late final Logic _in = input(_inName);
/// The output of this [NotGate].
Logic get out => output(_outName);
late final Logic out = output(_outName);
/// Constructs a [NotGate] with [in_] as its input.
///
/// You can optionally set [name] to name this [Module].
NotGate(Logic in_, {super.name = 'not'}) {
_inName = Module.unpreferredName(in_.name);
_outName = Module.unpreferredName('${in_.name}_b');
_inName = Naming.unpreferredName(in_.name);
_outName = Naming.unpreferredName('${in_.name}_b');
addInput(_inName, in_, width: in_.width);
addOutput(_outName, width: in_.width);
_setup();
Expand All @@ -49,9 +49,8 @@ class NotGate extends Module with InlineSystemVerilog {
@override
String inlineVerilog(Map<String, String> inputs) {
if (inputs.length != 1) {
throw Exception('Gate has exactly one input.');
}
assert(inputs.length == 1, 'Gate has exactly one input.');
final a = inputs[_inName]!;
return '~$a';
}
Expand Down
41 changes: 41 additions & 0 deletions doc/user_guide/_docs/A22-logic-nets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: "Logic Nets, In/Outs, and Tri-state Buffers"
permalink: /docs/logic-nets/
last_modified_at: 2024-6-3
toc: true
---

## Logic Nets

The `LogicNet` class is a type of `Logic` which supports multiple drivers and bidirectional signal propagation. If multiple drivers disagree on the signal value, then an `x` (contention) value will be generated on that signal. Therefore, typically only one driver on a set of connected `LogicNet`s should be non-floating. One should use [tri-state buffers](#tri-state-buffers) to control which driver is actively driving on a net. In general, usage of `LogicNet` can be used for places where external IO and analog driver behavior needs to be modelled. For example, an external IO may support one data bus for both read and write directions, so the IOs on either side would use tristate drivers to control whether each is reading from or writing to that bus.

Assignments between `LogicNet`s use the same `<=` or `gets` API as normal `Logic`s, but it applies automatically in both directions. Assignments from `LogicNet` to `Logic` will only apply in the direction of driving the `Logic`, and conversely, assignments from `Logic` to `LogicNet` will only apply in the direction of driving the `LogicNet`.

`LogicArray`s can also be nets by using the `LogicArray.net` constructor. The `isNet` accessor provides information on any `Logic` (including `LogicArray`s) about whether the signal will behave like a net (supporting multiple drivers).

## In/Out Ports

`Module`s support `inOut` ports via `addInOut`, which return objects of type `LogicNet`. There are also equivalent versions for `LogicArray`s. The API for `inOut` is similar to that of `input` -- there's an internal version to be used within a module, and the external version used for outside of the module.

## Tri-state Buffers

A tri-state buffer allows a driver to consider driving one of three states: driving 1, driving 0, and not driving (Z). This is useful for when you may want multiple things to drive the same net at different times. The `TriStateBuffer` module provides this capability in ROHD.

## Example

The below example shows a `Module` with an `inOut` port that is conditionally driven using a `TriStateBuffer`.

```dart
class ModWithInout extends Module {
/// A module which drives [toDrive] if [isDriver] is high onto [io], or
/// else leaves [io] floating (undriven) for others to drive it.
ModWithInout(Logic isDriver, Logic toDrive, LogicNet io)
: super(name: 'modwithinout') {
isDriver = addInput('isDriver', isDriver);
toDrive = addInput('toDrive', toDrive, width: toDrive.width);
io = addInOut('io', io, width: toDrive.width);
io <= TriStateBuffer(toDrive, enable: isDriver).out;
}
}
```
21 changes: 12 additions & 9 deletions lib/src/diagnostics/inspector_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension _ModuleDevToolUtils on Module {
/// Returns a JSON map representing the [Module] and its properties.
///
/// If [skipCustomModules] is set to `true` (default), sub-modules that are
/// instances of [CustomSystemVerilog] will be excluded from the JSON schema.
/// instances of [SystemVerilog] will be excluded from the JSON schema.
Map<String, dynamic> toJson({bool skipCustomModules = true}) {
final json = {
'name': name,
Expand All @@ -39,12 +39,15 @@ extension _ModuleDevToolUtils on Module {
'outputs': outputs.map((key, value) => MapEntry(key, value.toMap())),
};

final isCustomModule = this is CustomSystemVerilog;
// ignore: deprecated_member_use_from_same_package
final isCustomModule = this is CustomSystemVerilog || this is SystemVerilog;

if (!isCustomModule || !skipCustomModules) {
json['subModules'] = subModules
.where(
(module) => !(module is CustomSystemVerilog && skipCustomModules))
.where((module) =>
// ignore: deprecated_member_use_from_same_package
!((module is CustomSystemVerilog || module is SystemVerilog) &&
skipCustomModules))
.map((module) => module.toJson(skipCustomModules: skipCustomModules))
.toList();
}
Expand All @@ -58,12 +61,12 @@ extension _ModuleDevToolUtils on Module {
/// The [module] parameter is the root [Module] object for which the JSON
/// schema is generated.
///
/// By default, sub-modules that are instances of [CustomSystemVerilog] will
/// be excluded from the schema.
/// Pass [skipCustomModules] as `false` to include them in the schema.
/// By default, sub-modules that are instances of [SystemVerilog] will be
/// excluded from the schema. Pass [skipCustomModules] as `false` to include
/// them in the schema.
///
/// Returns a JSON string representing the schema of the [Module] object
/// and its sub-modules.
/// Returns a JSON string representing the schema of the [Module] object and
/// its sub-modules.
String buildModuleTreeJsonSchema(Module module,
{bool skipCustomModules = true}) =>
jsonEncode(toJson(skipCustomModules: skipCustomModules));
Expand Down
16 changes: 16 additions & 0 deletions lib/src/exceptions/module/invalid_hierarchy_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// invalid_hierarchy_exception.dart
// Definition for exception when an invalid hierarchy is detected.
//
// 2024 April 12
// Author: Max Korbel <[email protected]>

import 'package:rohd/rohd.dart';

/// An [Exception] thrown when a constructed hierarchy is illegal.
class InvalidHierarchyException extends RohdException {
/// Constructs a new [Exception] for when a constructed hierarchy is illegal.
InvalidHierarchyException(super.message);
}
Loading

0 comments on commit b39abb2

Please sign in to comment.