diff --git a/README.md b/README.md index c1c9bd602..49eb54e0b 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Some examples of component categories include: - Standard interfaces - Models ----------------- +----------------- Copyright (C) 2023-2025 Intel Corporation SPDX-License-Identifier: BSD-3-Clause diff --git a/confapp/lib/hcl/view/screen/content_widget.dart b/confapp/lib/hcl/view/screen/content_widget.dart index 03609215f..859844f93 100644 --- a/confapp/lib/hcl/view/screen/content_widget.dart +++ b/confapp/lib/hcl/view/screen/content_widget.dart @@ -35,7 +35,8 @@ class SVGenerator extends StatefulWidget { State createState() => _SVGeneratorState(); } -class _SVGeneratorState extends State { +class _SVGeneratorState extends State + with SingleTickerProviderStateMixin { final GlobalKey _formKey = GlobalKey(); final ButtonStyle btnStyle = ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)); @@ -148,7 +149,44 @@ class _SVGeneratorState extends State { ], ), for (final (index, subKnob) in knob.knobs.indexed) - _generateKnobControl('$index', subKnob), + // Animate size and add/remove transitions for each generated item. + // AnimatedSize handles height changes; AnimatedSwitcher provides + // a fade/size transition when items are added/removed. + AnimatedSize( + key: ValueKey('${knob.name}-$index-anim'), + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ), + child: Container( + key: ValueKey('${knob.name}-$index'), + child: subKnob is GroupOfKnobs + ? Column(children: [ + _containerOfKnobs( + title: '${subKnob.name} $index', + children: [ + for (final subKnobEntry + in subKnob.subKnobs.entries) + _generateKnobControl( + subKnobEntry.key, subKnobEntry.value), + ]), + const SizedBox(height: 12), + ]) + : Column(children: [ + _generateKnobControl('${knob.name} $index', subKnob), + const SizedBox(height: 12), + ]), + ), + ), + ), ]); } else if (knob is GroupOfKnobs) { selector = _containerOfKnobs(title: knob.name, children: [ diff --git a/doc/Module.md b/doc/Module.md new file mode 100755 index 000000000..fd85acaf7 --- /dev/null +++ b/doc/Module.md @@ -0,0 +1,157 @@ +# How to Build a Great Component in ROHD + +Since ROHD is an extension of the Dart programming language, please follow all +Dart programming and documentation conventions. + +The `Module` class is the base class used in ROHD to build components, and +calling the constructor the `Module` instantiates the component and connects it +to signals passed into the constructor. + +## Port Construction and Connection + +A `Module` constructor takes `Logic` arguments and parameters to generate a +hardware component. The `Logic` arguments are actually the external signals +being connected to by the `Module`, and so internal copies must be constructed +and connected to these arguments by the constructor and only then can other +logic signals be connected to these copies. If you do not do this, a trace error +will occur, but that only happens when this module is instantiated in another -- +it will not show up while testing which just instantiates the module in a test +environment. See [Modules](https://intel.github.io/rohd-website/docs/modules/) +for more detail. + +A key pattern used in ROHD-HCL is to have the constructor take only input +signals as arguments and generate the output signal widths based on these +signals and other parameters. + +## Port Types + +Signals can take various forms in ROHD and it is important to consider what form +you want for the API of the `Module` you are building. ROHD supports the basic +`Logic` signal which has its width encode (therefore you should not be using +width as a parameter to a `Module`). ROHD provides basic cloning and accessor +helper functions like `addInput` and `input`. + +`LogicArray` is a uniform multi-dimensional array of leaf `Logic` signals. Using +this for input/output will require special routines like `addInputArray` to +connect external and internal signals. Examples of using `LogicArray` are in the +`Serializer` and `Deserializer` components. + +`LogicStructure` is a hierarchical concatenation of named `Logic` fields, where +the `FloatingPoint` arithmetic type is an example used in the +`FloatingPointMultiplierSimple` module. We can also pass in `LogicStructure` as +a type for certain components so that the field structure is not lost on input +and output. A good example of this is `Fifo`, which is templatized on +`LogicType` to allow for us to generate a `Fifo` for a particular +`LogicStructure` to use when pushing and popping the data in and out. Here, +`addTypedInput` is a method used to help with creating the internal signals. + +`Interface` is similar to `LogicStructure` yet it provides an ability to define +directionality to the internal fields, useful in connecting modules that share a +common protocol such as the `ApbInterface`. See +[Interfaces](https://intel.github.io/rohd-website/docs/interfaces). A few +examples of key general interface types that you can inherit from are the +`PairInterface` and the `DataPortInterface`. the `Memory` module has a good +example of how `DataPortInterface`s are cloned internally using its `connectIO` +method. +The `Fifo` has a good example of using an `Interface` to wrap a `LogicStructure`. + +When wrapping `LogicStructure` with `Interface`, don't name the `LogicStructure` +as `Interface` will need to uniquify (a known bug in `Interface`). + +An important kind of `Interface` is the `PairInterface` which is designed for +bidirectional communication and provides a `pairConnectIO` method for connecting +external and internal ports based on producer/consumer filtering. + +## Logic Internals + +Signal logic is constructed in a ROHD component by assignment and simple logic +operations like and (`&`) and or (`|`) as well as multiplexing (`mux`) and +flopping (`flop`). See +[operations](https://intel.github.io/rohd-website/docs/logic-math-compare/) for +more detail. + + More complex logic can be constructed using + [`Sequental`](https://intel.github.io/rohd-website/docs/sequentials/) and + [`Combinational`](https://intel.github.io/rohd-website/docs/conditionals/) + blocks similar to SystemVerilog `always` blocks. There is also a + [`FiniteStateMachine`](https://intel.github.io/rohd-website/docs/fsm/) + construct for state machines and a + [`Pipeline`](https://intel.github.io/rohd-website/docs/pipelines/) construct + for assisting with pipelined logic. + + Try to minimize the addition of new internal signals, by just reusing the + signals created by the ports or by subcomponents. Use `.named` to create clean + SystemVerilog names. + +### Debug + + If you want to expose internal signals onto the interface of a `Module` for debug, a simple method is to declare them as a field in the class (Use @protected in case this is exposed so it doesn't become part of the API). This signal will be available in tests as module.field. + +## Unit Testing + + A good component has unit tests to validate the component and provide examples + of use. We use the Dart testing framework which requires that tests are stored + in the `test/` directory and are named ending in `_test.dart`. An example of + unit tests for a component is shown below. Note that grouping of tests can + reuse a common component built for multiple tests. Also note that each test + with sequential logic will need a `SimpleClockGenerator`, a `Simulator.run()` + and an `endSimulation`. Some helper methods (like `.waitCycles`) are available + in the rohd-fv package. + + ```dart +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('test narrow component', () { + final input = Logic(width: 5); + final component = MyComponent(input); + final output = component.out; + + test('MyComponent smoke test', () async { + final clk = SimpleClockGenerator(10).clk; + + unawaited(Simulator.run()); + reset.inject(1); + await clk.waitCycles(3); + reset.inject(0); + await clk.waitCycles(1); + input.inject(1); + await clk.waitCycles(3); + expect(output.value, equals(Const(1, width: output.width))); + + await Simulator.endSimulation(); + }); + + test('MyComponent second test', () async { + final clk = SimpleClockGenerator(10).clk; + + unawaited(Simulator.run()); + reset.inject(1); + await clk.waitCycles(3); + reset.inject(0); + await clk.waitCycles(1); + input.inject(6); + await clk.waitCycles(3); + expect(output.value, equals(Const(6, width: output.width))); + + await Simulator.endSimulation(); + }); + }); +} +``` + +Prefer using `waitCycles` instead of `nextPosedge` and use `inject` instead of +`put` when working with sequential tests. + +When testing a combinational path, and you `inject` inputs after a positive +clock edge, if you sample at the next clock edge, you will miss the +combinational value. Instead, use the output `previousValue` at the next clock +edge, or sample the output at `nextNegEdge` to look at the value mid-way through +the clock cycle. + +While creating unit tests, you can just run the tests for your component instead +of running the entire suite of ROHD-HCL tests. The entire regression suite +takes quite a long time and is only necessary if you make changes to some core +functionality. diff --git a/doc/README.md b/doc/README.md index 8386eb8f4..4a3a74235 100644 --- a/doc/README.md +++ b/doc/README.md @@ -109,8 +109,9 @@ Some in-development items will have opened issues, as well. Feel free to create - Memory - [Register File](./components/memory.md#register-files) - [Masking](./components/memory.md#masks) - - Replacement Policies - - LRU + - [Caches](./components/memory.md#caches) + - [Replacement Policies](./components/memory.md#replacement-policy) + - [Cams](./components/memory.md#basic-cam) - [Memory Model](./components/memory.md#memory-models) - [Control/Status Registers (CSRs)](./components/csr.md) - Standard interfaces @@ -133,6 +134,10 @@ Some in-development items will have opened issues, as well. Feel free to create - Gaskets - [SPI](./components/spi_gaskets.md) +## Adding a New Component + +Please refer to [Module](./Module.md) for the best practices for creating new components. + ---------------- Copyright (C) 2023-2025 Intel Corporation diff --git a/doc/components/memory.md b/doc/components/memory.md index 4e1204216..5ba2fddc9 100644 --- a/doc/components/memory.md +++ b/doc/components/memory.md @@ -4,11 +4,11 @@ ROHD-HCL provides a generic `abstract` [`Memory`](https://intel.github.io/rohd-h ## Masks -A sub-class of `DataPortInterface` is the[`MaskedDataPortInterface`](https://intel.github.io/rohd-hcl/rohd_hcl/MaskedDataPortInterface-class.html), which adds `mask` to the `data` group of signals. The `mask` signal is a byte-enable signal, where each bit of `mask` controls one byte of `data`. +A subclass of `DataPortInterface` is the[`MaskedDataPortInterface`](https://intel.github.io/rohd-hcl/rohd_hcl/MaskedDataPortInterface-class.html), which adds `mask` to the `data` group of signals. The `mask` signal is a byte-enable signal, where each bit of `mask` controls one byte of `data`. ## Register Files -A sub-class of `Memory` is the [`RegisterFile`](https://intel.github.io/rohd-hcl/rohd_hcl/RegisterFile-class.html), which inherits the same flexible interface from `Memory`. It has a configurable number of entries via `numEntries`. +A subclass of `Memory` is the [`RegisterFile`](https://intel.github.io/rohd-hcl/rohd_hcl/RegisterFile-class.html), which inherits the same flexible interface from `Memory`. It has a configurable number of entries via `numEntries`. The `RegisterFile` accepts masks on writes, but not on reads. @@ -20,8 +20,183 @@ The `RegisterFile` can be initialized with data on reset using `resetValue` foll [RegisterFile Schematic](https://intel.github.io/rohd-hcl/RegisterFile.html) +## First-In First-Out (FIFO) Buffers + +Please see [`Fifo`](./fifo.md) + ## Memory Models The `MemoryModel` has the same interface as a `Memory`, but is non-synthesizable and uses a software-based `SparseMemoryStorage` as a backing for data storage. This is a useful tool for testing systems that have relatively large memories. -The `MemoryStorage` class also provides utilities for reading (`loadMemString`) and writing (`dumpMemString`) verilog-compliant memory files (e.g. for `readmemh`). +The `MemoryStorage` class also provides utilities for reading (`loadMemString`) and writing (`dumpMemString`) Verilog-compliant memory files (e.g. for `readmemh`). + +## Cam + +A content-addressable memory or `Cam` is provided which allows for associative lookup of an index to help with building specialized forms of caches where the data is stored in a separate register file. In this case the `tag` is matched during a read and the position in memory is returned. For the fill ports, the user can simply write a new tag at a given index location. This means the `Cam` is a fine-grained component for use in building associative look of positions of objects in another memory. + +Another form of `Cam` or `CamInvalidate` provides a read interface with a read-with-invalidate feature if the invalidate port is set on the interface. + +An example use is: + +```dart +const tagWidth = 8; + const numEntries = 4; + const idWidth = 2; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final writePort = DataPortInterface(tagWidth, idWidth); + final lookupPort = TagInvalidateInterface(idWidth, tagWidth); + + final cam = CamInvalidate( + clk, + reset, + [writePort], + [lookupPort], + numEntries: numEntries, + ); + // Write tag 0x99 to entry 1 + writePort.addr.inject(1); + writePort.data.inject(0x99); + await clk.nextPosedge; + + // Lookup tag 0x42 without invalidate + lookupPort.tag.inject(0x99); + await clk.nextPosedge; + expect(lookupPort.idx.value.toInt(), equals(1), + reason: 'Should return index 0'); +``` + +## Caches + +ROHD-HCL provides cache implementations for different architectural needs, from simple direct-mapped caches to sophisticated multi-ported set-associative designs. The base `Cache` interface provides a set of fill ports, read ports, and eviction ports. + +Cache ports are all `ValidDataPortInterface`s, where a `valid` signal is used on the read side to indicate a `hit`, and it is used on the fill side (set to false) to invalidate a cache entry. This port type also can be created with `readWithInvalidate` so that a read request can invalidate the entry after the read. + + The eviction ports provide address and data for evicted cache elements, where eviction happens on a fill that needs to find space in the cache. Note that means the number of eviction ports, if supplied, must match the number of fill ports. + +### Read-with-Invalidate Feature + +The `Cache` supports an advanced read-with-invalidate operation that allows atomic read and invalidation of cache entries. This feature is particularly useful for implementing request/response tracking systems where you need to read data and immediately mark the entry as invalid. + +The read-with-invalidate functionality is enabled automatically when using `ValidDataPortInterface` with the `readWithInvalidate` extension: + +```dart +// Create read port with read-with-invalidate capability +final readPort = ValidDataPortInterface(dataWidth: 32, addrWidth: 8) + ..readWithInvalidate = Logic(name: 'readWithInvalidate'); + +final cache = FullyAssociativeCache( + clk, reset, + [fillPort], + [readPort], // This port now supports read-with-invalidate + ways: 8, +); + +// To perform a read-with-invalidate operation: +// 1. Set the address and enable the read +readPort.addr <= targetAddress; +readPort.en <= Const(1); +// 2. Assert readWithInvalidate to invalidate on hit +readPort.readWithInvalidate <= shouldInvalidate; + +// The cache will: +// - Return valid data if hit occurs (readPort.valid will be high) +// - Automatically invalidate the entry on the next clock cycle if readWithInvalidate was asserted +``` + +### Replacement Policy + +For supporting set-associative caching, the `Cache` interface provides a way to provide a replacement policy via a `Function` parameter: + +```dart + final ReplacementPolicy Function( + Logic clk, + Logic reset, + List hits, + List misses, + List invalidates, + {int ways, + String name}) replacement; +``` + +Here, the `AccessInterface` simply carries the `access` flag and the `way` that is being read or written. + +A pseudo-LRU `ReplacementPolicy` called `PseudoLRUReplacement` is provided as default for use in set-associative caches. + +### DirectMappedCache + +The [`DirectMappedCache`] provides a direct-mapped cache with multiple read and fill ports. + +### Fully Associative Cache + +ROHD-HCL provides fully-associative cache implementations that enable lookup by content rather than address. This is useful for building efficient caches, translation look-aside buffers (TLBs), and request tracking systems. + +The [`FullyAssociativeCache`] implements eviction if the eviction ports (parallel to the fill ports) are provided. Note that there is only 1 line in a fully-associative cache as every way stores a unique tag. + +```dart +final cache = FullyAssociativeCache( + clk, reset, + [fillPort1, fillPort2], // Fill ports for cache line writes + [readPort1, readPort2], // Read ports for cache lookups + ways: 4, // 4-way set associative +); +``` + +## Example: Request/Response Matching + +```dart +// CAM for tracking pending requests - stores request ID as tag, address as data +final pendingRequests = FullyAssociativeCache( + clk, reset, + [fillPort], // Add new pending requests + [lookupPort], // Look up and remove completed requests + ways: 16, +); + +// When a response arrives, look up the request and invalidate the entry +lookupPort.addr <= responseId; // Use response ID as lookup key +lookupPort.en <= responseValid; // Enable lookup when response is valid +lookupPort.readWithInvalidate <= Const(1); // Always invalidate on hit + +// If hit occurs: +// - lookupPort.valid will be high +// - lookupPort.data contains the original request address +// - Entry is automatically invalidated for future requests +``` + +### Occupancy Tracking + +The `FullyAssociativeCache` can optionally provide occupancy tracking signals by setting `generateOccupancy: true`: + +```dart +final cache = FullyAssociativeCache( + clk, reset, + [fillPort], + [readPort], + ways: 8, + generateOccupancy: true, // Enable occupancy tracking +); + +// Access occupancy signals +final currentOccupancy = cache.occupancy!; // Number of valid entries (0 to ways) +final isFull = cache.full!; // High when all ways are occupied +final isEmpty = cache.empty!; // High when no entries are valid +``` + +This is particularly useful for flow control and back-pressure management in systems that need to track cache utilization. + +### Set Associative Cache + +The [`SetAssociativeCache`] provides a set-associative cache with multiple read and fill ports and a replacement policy parameter to specify what type of way replacement the cache should use. + +```dart +final cache = SetAssociativeCache( + clk, reset, + [fillPort1, fillPort2], // Fill ports for cache line writes + [readPort1, readPort2], // Read ports for cache lookups + ways: 4, // 4-way set associative + lines: 256, // 256 cache lines +); +``` diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index bc82c3402..aee2a3fa4 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -12,7 +12,6 @@ export 'src/encodings/encodings.dart'; export 'src/error_checking/error_checking.dart'; export 'src/exceptions.dart'; export 'src/extrema.dart'; -export 'src/fifo.dart'; export 'src/find.dart'; export 'src/find_pattern.dart'; export 'src/gaskets/spi/spi_gaskets.dart'; @@ -22,10 +21,8 @@ export 'src/models/models.dart'; export 'src/priority_encoder.dart'; export 'src/reduction_tree.dart'; export 'src/reduction_tree_generator.dart'; -export 'src/resettable_entries.dart'; export 'src/rotate.dart'; export 'src/serialization/serialization.dart'; -export 'src/shift_register.dart'; export 'src/signed_shifter.dart'; export 'src/sort.dart'; export 'src/static_or_runtime_parameter.dart'; diff --git a/lib/src/arithmetic/fixed_sqrt.dart b/lib/src/arithmetic/fixed_sqrt.dart index 94716f69b..4e05c51b4 100644 --- a/lib/src/arithmetic/fixed_sqrt.dart +++ b/lib/src/arithmetic/fixed_sqrt.dart @@ -23,7 +23,7 @@ abstract class FixedPointSqrtBase extends Module { late final FixedPoint a; /// getter for the computed output. - late final FixedPoint sqrt = a.clone(name: 'sqrt')..gets(output('sqrt')); + late final FixedPoint sqrt; /// Square root a fixed point number [a], returning result in [sqrt]. FixedPointSqrtBase(FixedPoint a, @@ -35,9 +35,9 @@ abstract class FixedPointSqrtBase extends Module { super( definitionName: definitionName ?? 'FixedPointSquareRoot${a.width}') { - this.a = a.clone(name: 'a')..gets(addInput('a', a, width: a.width)); + this.a = addTypedInput('a', a); - addOutput('sqrt', width: width); + sqrt = addTypedOutput('sqrt', a.clone); } } diff --git a/lib/src/arithmetic/fixed_to_float.dart b/lib/src/arithmetic/fixed_to_float.dart index db47f3893..f1d3d5711 100644 --- a/lib/src/arithmetic/fixed_to_float.dart +++ b/lib/src/arithmetic/fixed_to_float.dart @@ -18,7 +18,9 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// support infinity. class FixedToFloat extends Module { /// Output port [float] - late final FloatingPoint float = outFloat.clone()..gets(output('float')); + // Expose typed FloatingPoint output via addTypedOutput and wire internal + // converted float into it. This avoids relying on plain output('float'). + late final FloatingPoint float; /// Internal representation of the output port. @protected @@ -47,16 +49,19 @@ class FixedToFloat extends Module { definitionName: definitionName ?? 'Fixed${fixed.width}ToFloat_E${outFloat.exponent.width}' 'M${outFloat.mantissa.width}') { - fixed = fixed.clone(name: 'fixed') - ..gets(addInput('fixed', fixed, width: fixed.width)); + fixed = addTypedInput('fixed', fixed); final fixedAsLogic = fixed.packed; final exponentWidth = outFloat.exponent.width; final mantissaWidth = outFloat.mantissa.width; _convertedFloat = FloatingPoint( exponentWidth: exponentWidth, mantissaWidth: mantissaWidth); - addOutput('float', width: outFloat.width) <= _convertedFloat; - outFloat <= output('float'); + // Create a typed output and drive it from the internal converted float. + final typedFloatOut = addTypedOutput('float', _convertedFloat.clone); + typedFloatOut <= _convertedFloat; + // Also set the public `float` handle to the typed output instance so + // consumers that expect a FloatingPoint object can use `float` directly. + float = typedFloatOut; leadingDigitPredict = (leadingDigitPredict != null) ? addInput('leadingDigitPredict', leadingDigitPredict, diff --git a/lib/src/arithmetic/float_to_fixed.dart b/lib/src/arithmetic/float_to_fixed.dart index 14729e951..c422a9b77 100644 --- a/lib/src/arithmetic/float_to_fixed.dart +++ b/lib/src/arithmetic/float_to_fixed.dart @@ -38,8 +38,8 @@ class FloatToFixed extends Module { late final FixedPoint _fixed = FixedPoint(integerWidth: integerWidth, fractionWidth: fractionWidth); - /// Output fixed point port - late final FixedPoint fixed = _fixed.clone()..gets(output('fixed')); + /// Output fixed point port (exposed as a typed output) + late final FixedPoint fixed; /// Build a [FloatingPoint] to [FixedPoint] converter. /// - if [integerWidth] and [fractionWidth] are supplied, an m.n fixed-point @@ -61,7 +61,7 @@ class FloatToFixed extends Module { definitionName: definitionName ?? 'FloatE${float.exponent.width}' 'M${float.mantissa.width}ToFixed') { - float = float.clone()..gets(addInput('float', float, width: float.width)); + float = addTypedInput('float', float); final bias = float.floatingPointValue.bias; // E4M3 expands the max exponent by 1. @@ -138,7 +138,9 @@ class FloatToFixed extends Module { .named('number'); _fixed <= mux(float.sign, ~number + 1, number).named('signedNumber'); - addOutput('fixed', width: outputWidth) <= _fixed; + final typedFixedOut = addTypedOutput('fixed', _fixed.clone); + typedFixedOut <= _fixed; + fixed = typedFixedOut; } } diff --git a/lib/src/arithmetic/floating_point/floating_point_adder.dart b/lib/src/arithmetic/floating_point/floating_point_adder.dart index 7142555fc..9f71cf56f 100644 --- a/lib/src/arithmetic/floating_point/floating_point_adder.dart +++ b/lib/src/arithmetic/floating_point/floating_point_adder.dart @@ -44,8 +44,7 @@ abstract class FloatingPointAdder