Skip to content

Commit c626fd5

Browse files
authored
Cache2 subset (#281)
* Reorganized filesystem, especially memories/ * Added `ReadyValidInterface` and `ReadyValidFifo` * Added `Cam` component * Documentation 'How to build a great component' * Migrated to `addTypedInput` on components
1 parent 9d7b50b commit c626fd5

38 files changed

Lines changed: 1846 additions & 107 deletions

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,5 @@ Some examples of component categories include:
5050
- Standard interfaces
5151
- Models
5252

53-
----------------
54-
5553
Copyright (C) 2023-2025 Intel Corporation
5654
SPDX-License-Identifier: BSD-3-Clause

confapp/lib/hcl/view/screen/content_widget.dart

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class SVGenerator extends StatefulWidget {
3535
State createState() => _SVGeneratorState();
3636
}
3737

38-
class _SVGeneratorState extends State<SVGenerator> {
38+
class _SVGeneratorState extends State<SVGenerator>
39+
with SingleTickerProviderStateMixin {
3940
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
4041
final ButtonStyle btnStyle =
4142
ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20));
@@ -148,7 +149,44 @@ class _SVGeneratorState extends State<SVGenerator> {
148149
],
149150
),
150151
for (final (index, subKnob) in knob.knobs.indexed)
151-
_generateKnobControl('$index', subKnob),
152+
// Animate size and add/remove transitions for each generated item.
153+
// AnimatedSize handles height changes; AnimatedSwitcher provides
154+
// a fade/size transition when items are added/removed.
155+
AnimatedSize(
156+
key: ValueKey('${knob.name}-$index-anim'),
157+
duration: const Duration(milliseconds: 250),
158+
curve: Curves.easeInOut,
159+
child: AnimatedSwitcher(
160+
duration: const Duration(milliseconds: 200),
161+
transitionBuilder: (child, animation) => FadeTransition(
162+
opacity: animation,
163+
child: SizeTransition(
164+
sizeFactor: animation,
165+
axisAlignment: 0.0,
166+
child: child,
167+
),
168+
),
169+
child: Container(
170+
key: ValueKey('${knob.name}-$index'),
171+
child: subKnob is GroupOfKnobs
172+
? Column(children: [
173+
_containerOfKnobs(
174+
title: '${subKnob.name} $index',
175+
children: [
176+
for (final subKnobEntry
177+
in subKnob.subKnobs.entries)
178+
_generateKnobControl(
179+
subKnobEntry.key, subKnobEntry.value),
180+
]),
181+
const SizedBox(height: 12),
182+
])
183+
: Column(children: [
184+
_generateKnobControl('${knob.name} $index', subKnob),
185+
const SizedBox(height: 12),
186+
]),
187+
),
188+
),
189+
),
152190
]);
153191
} else if (knob is GroupOfKnobs) {
154192
selector = _containerOfKnobs(title: knob.name, children: [

doc/Component.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# How to Build a Great Component in ROHD
2+
3+
Since ROHD is an extension of the Dart programming language, please follow all
4+
Dart programming and documentation conventions.
5+
6+
The `Module` class is the base class used in ROHD to build components, and
7+
calling the constructor the `Module` instantiates the component and connects it
8+
to signals passed into the constructor.
9+
10+
Since ROHD is written in Dart, you should use the Dart best practices, such as
11+
camel-case variable naming, commenting patterns for all public APIs, and take
12+
advantage of dart format and dart analyze tools.
13+
14+
## Port Construction and Connection
15+
16+
A `Module` constructor takes `Logic` arguments and parameters to generate a
17+
hardware component. The `Logic` arguments are actually the external signals
18+
being connected to by the `Module`, and so internal copies must be constructed
19+
and connected to these arguments by the constructor and only then can other
20+
logic signals be connected to these copies. If you do not do this, a trace error
21+
will occur, but that only happens when this module is instantiated in another --
22+
it will not show up while testing which just instantiates the module in a test
23+
environment. See [Modules](https://intel.github.io/rohd-website/docs/modules/)
24+
for more detail.
25+
26+
A key pattern used in ROHD-HCL is to have the constructor take only input
27+
signals as arguments and generate the output signal widths based on these
28+
signals and other parameters.
29+
30+
## Port Types
31+
32+
Signals can take various forms in ROHD, and it is important to consider what form
33+
you want for the API of the `Module` you are building. ROHD supports the basic
34+
`Logic` signal which has its width encode (therefore you should not be using
35+
width as a parameter to a `Module`). ROHD provides basic cloning and accessor
36+
helper functions like `addInput` and `input`.
37+
38+
`LogicArray` is a uniform multidimensional array of leaf `Logic` signals. Using
39+
this for input/output will require special routines like `addInputArray` to
40+
connect external and internal signals. Examples of using `LogicArray` are in the
41+
`Serializer` and `Deserializer` components.
42+
43+
`LogicStructure` is a hierarchical concatenation of named `Logic` fields, where
44+
the `FloatingPoint` arithmetic type is an example used in the
45+
`FloatingPointMultiplierSimple` module. We can also pass in `LogicStructure` as
46+
a type for certain components so that the field structure is not lost on input
47+
and output. A good example of this is `Fifo`, which is templatized on
48+
`LogicType` to allow for us to generate a `Fifo` for a particular
49+
`LogicStructure` to use when pushing and popping the data in and out. Here,
50+
`addTypedInput` is a method used to help with creating the internal signals.
51+
52+
`Interface` is similar to `LogicStructure`, yet it provides an ability to define
53+
directionality to the internal fields, useful in connecting modules that share a
54+
common protocol such as the `ApbInterface`. See
55+
[Interfaces](https://intel.github.io/rohd-website/docs/interfaces). A few
56+
examples of key general interface types that you can inherit from are the
57+
`PairInterface` and the `DataPortInterface`. the `Memory` module has a good
58+
example of how `DataPortInterface`s are cloned internally using its `connectIO`
59+
method.
60+
The `Fifo` has a good example of using an `Interface` to wrap a `LogicStructure`.
61+
62+
When wrapping `LogicStructure` with `Interface`, don't name the `LogicStructure`
63+
as `Interface` will need to uniquify (a known bug in `Interface`).
64+
65+
An important kind of `Interface` is the `PairInterface` which is designed for
66+
bidirectional communication and provides a `pairConnectIO` method for connecting
67+
external and internal ports based on producer/consumer filtering.
68+
69+
## Logic Internals
70+
71+
Signal logic is constructed in a ROHD component by assignment and simple logic
72+
operations like and (`&`) and or (`|`) as well as multiplexing (`mux`) and
73+
flopping (`flop`). See
74+
[operations](https://intel.github.io/rohd-website/docs/logic-math-compare/) for
75+
more detail.
76+
77+
More complex logic can be constructed using
78+
[`Sequental`](https://intel.github.io/rohd-website/docs/sequentials/) and
79+
[`Combinational`](https://intel.github.io/rohd-website/docs/conditionals/)
80+
blocks similar to SystemVerilog `always` blocks. There is also a
81+
[`FiniteStateMachine`](https://intel.github.io/rohd-website/docs/fsm/)
82+
construct for state machines and a
83+
[`Pipeline`](https://intel.github.io/rohd-website/docs/pipelines/) construct
84+
for assisting with pipelined logic.
85+
86+
Try to minimize the addition of new internal signals, by just reusing the
87+
signals created by the ports or by subcomponents. Use `.named` to create clean
88+
SystemVerilog names.
89+
90+
### Debug
91+
92+
If you want to expose internal signals onto the interface of a `Module` for
93+
debug, a simple method is to declare them as a field in the class (Use
94+
`@protected` in case this is exposed, so it doesn't become part of the API). This
95+
signal will be available in tests as module.field.
96+
97+
## Unit Testing
98+
99+
A good component has unit tests to validate the component and provide examples
100+
of use. We use the Dart testing framework which requires that tests are stored
101+
in the `test/` directory and are named ending in `_test.dart`. An example of
102+
unit tests for a component is shown below. Note that grouping of tests can
103+
reuse a common component built for multiple tests. Also note that each test
104+
with sequential logic will need a `SimpleClockGenerator`, a `Simulator.run()`
105+
and an `endSimulation`. Some helper methods (like `.waitCycles`) are available
106+
in the rohd-fv package.
107+
108+
```dart
109+
void main() {
110+
tearDown(() async {
111+
await Simulator.reset();
112+
});
113+
114+
group('test narrow component', () {
115+
final input = Logic(width: 5);
116+
final component = MyComponent(input);
117+
final output = component.out;
118+
119+
test('MyComponent smoke test', () async {
120+
final clk = SimpleClockGenerator(10).clk;
121+
122+
unawaited(Simulator.run());
123+
reset.inject(1);
124+
await clk.waitCycles(3);
125+
reset.inject(0);
126+
await clk.waitCycles(1);
127+
input.inject(1);
128+
await clk.waitCycles(3);
129+
expect(output.value, equals(Const(1, width: output.width)));
130+
131+
await Simulator.endSimulation();
132+
});
133+
134+
test('MyComponent second test', () async {
135+
final clk = SimpleClockGenerator(10).clk;
136+
137+
unawaited(Simulator.run());
138+
reset.inject(1);
139+
await clk.waitCycles(3);
140+
reset.inject(0);
141+
await clk.waitCycles(1);
142+
input.inject(6);
143+
await clk.waitCycles(3);
144+
expect(output.value, equals(Const(6, width: output.width)));
145+
146+
await Simulator.endSimulation();
147+
});
148+
});
149+
}
150+
```
151+
152+
Prefer using `waitCycles` instead of `nextPosedge` and use `inject` instead of
153+
`put` when working with sequential tests.
154+
155+
When testing a combinational path, and you `inject` inputs after a positive
156+
clock edge, if you sample at the next clock edge, you will miss the
157+
combinational value. Instead, use the output `previousValue` at the next clock
158+
edge, or sample the output at `nextNegEdge` to look at the value midway through
159+
the clock cycle.
160+
161+
While creating unit tests, you can just run the tests for your component instead
162+
of running the entire suite of ROHD-HCL tests. The entire regression suite
163+
takes quite a long time and is only necessary if you make changes to some core
164+
functionality.

doc/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ Some in-development items will have opened issues, as well. Feel free to create
109109
- Memory
110110
- [Register File](./components/memory.md#register-files)
111111
- [Masking](./components/memory.md#masks)
112-
- Replacement Policies
113-
- LRU
112+
- [Cams](./components/memory.md#basic-cam)
114113
- [Memory Model](./components/memory.md#memory-models)
115114
- [Control/Status Registers (CSRs)](./components/csr.md)
116115
- Standard interfaces
@@ -136,6 +135,10 @@ Some in-development items will have opened issues, as well. Feel free to create
136135
- Gaskets
137136
- [SPI](./components/spi_gaskets.md)
138137

138+
## Adding a New Component
139+
140+
Please refer to [Component](./Component.md.md) for the best practices for creating new components.
141+
139142
----------------
140143

141144
Copyright (C) 2023-2025 Intel Corporation

doc/components/memory.md

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ ROHD-HCL provides a generic `abstract` [`Memory`](https://intel.github.io/rohd-h
44

55
## Masks
66

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

99
## Register Files
1010

11-
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`.
11+
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`.
1212

1313
The `RegisterFile` accepts masks on writes, but not on reads.
1414

@@ -20,8 +20,82 @@ The `RegisterFile` can be initialized with data on reset using `resetValue` foll
2020

2121
[RegisterFile Schematic](https://intel.github.io/rohd-hcl/RegisterFile.html)
2222

23+
## First-In First-Out (FIFO) Buffers
24+
25+
Please see [`Fifo`](./fifo.md)
26+
2327
## Memory Models
2428

2529
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.
2630

27-
The `MemoryStorage` class also provides utilities for reading (`loadMemString`) and writing (`dumpMemString`) verilog-compliant memory files (e.g. for `readmemh`).
31+
The `MemoryStorage` class also provides utilities for reading (`loadMemString`) and writing (`dumpMemString`) Verilog-compliant memory files (e.g. for `readmemh`).
32+
33+
## Cam
34+
35+
A content-addressable memory or `Cam` is provided which allows for associative lookup using a `tag` that produces an index to help with building specialized forms of caches where the actual data is stored in a separate register file. The index is to be separately used as a linear address in another component (like a `RegisterFile`) to find the associated data. In this case the `tag` is matched during a read and the position in memory is returned, which is the index. 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.
36+
37+
Both write and lookup ports use the `TagInterface`, which provides a consistent interface for CAM operations:
38+
39+
- For **writes**: `en` enables the write, `idx` specifies the destination address, `tag` is the data to write, and `hit` sets/clears the valid bit for the entry (hit=1 marks entry valid, hit=0 marks entry invalid).
40+
- For **lookups**: `tag` is the query, `idx` returns the matching index, and `hit` indicates whether a valid match was found. Only entries with their valid bit set will match.
41+
42+
Each CAM entry has a valid bit that must be set for the entry to participate in lookups. This allows distinguishing between "entry contains tag 0x00" and "entry is empty/invalid".
43+
44+
### Read-with-Invalidate Pattern
45+
46+
A read-with-invalidate operation can be implemented by using a dedicated write port wired to the lookup port's outputs:
47+
48+
- Wire `invalidatePort.idx <= lookupPort.idx` to target the found entry
49+
- Wire `invalidatePort.tag <= lookupPort.tag` to match the lookup
50+
- Set `invalidatePort.hit = 0` (always invalidate, never validate)
51+
- Set `invalidatePort.en = lookupPort.hit` (only invalidate if found)
52+
53+
This pattern allows atomic "find and remove" operations where the lookup returns the matching index while simultaneously invalidating that entry.
54+
55+
An example use is:
56+
57+
```dart
58+
const tagWidth = 8;
59+
const numEntries = 4;
60+
const idWidth = 2;
61+
62+
final clk = SimpleClockGenerator(10).clk;
63+
final reset = Logic();
64+
65+
final writePort = TagInterface(idWidth, tagWidth);
66+
final invalidatePort = TagInterface(idWidth, tagWidth);
67+
final lookupPort = TagInterface(idWidth, tagWidth);
68+
69+
final cam = Cam(
70+
clk,
71+
reset,
72+
[writePort, invalidatePort],
73+
[lookupPort],
74+
numEntries: numEntries,
75+
);
76+
77+
// Wire invalidatePort to use lookupPort's combinational output
78+
invalidatePort.idx <= lookupPort.idx;
79+
invalidatePort.tag <= lookupPort.tag;
80+
invalidatePort.hit.inject(0); // Always clear valid bit
81+
82+
// Write tag 0x99 to index position 1 (hit=1 marks it valid)
83+
writePort.en.inject(1);
84+
writePort.hit.inject(1);
85+
writePort.idx.inject(1);
86+
writePort.tag.inject(0x99);
87+
await clk.nextPosedge;
88+
89+
// Lookup tag 0x99 without invalidate
90+
lookupPort.tag.inject(0x99);
91+
invalidatePort.en.inject(0);
92+
await clk.nextPosedge;
93+
// We found our matching tag at index 1 where we stored it!
94+
expect(lookupPort.idx.value.toInt(), equals(1),
95+
reason: 'Should return index 1');
96+
97+
// Lookup and invalidate: enable invalidatePort when hit
98+
invalidatePort.en.inject(1); // Invalidate on hit
99+
await clk.nextPosedge;
100+
// Entry is now invalidated - subsequent lookup will miss
101+
```

lib/rohd_hcl.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export 'src/encodings/encodings.dart';
1313
export 'src/error_checking/error_checking.dart';
1414
export 'src/exceptions.dart';
1515
export 'src/extrema.dart';
16-
export 'src/fifo.dart';
1716
export 'src/find.dart';
1817
export 'src/find_pattern.dart';
1918
export 'src/gaskets/gaskets.dart';
@@ -23,10 +22,8 @@ export 'src/models/models.dart';
2322
export 'src/priority_encoder.dart';
2423
export 'src/reduction_tree.dart';
2524
export 'src/reduction_tree_generator.dart';
26-
export 'src/resettable_entries.dart';
2725
export 'src/rotate.dart';
2826
export 'src/serialization/serialization.dart';
29-
export 'src/shift_register.dart';
3027
export 'src/signed_shifter.dart';
3128
export 'src/sort.dart';
3229
export 'src/static_or_runtime_parameter.dart';

lib/src/arithmetic/fixed_sqrt.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ abstract class FixedPointSqrtBase extends Module {
2323
late final FixedPoint a;
2424

2525
/// getter for the computed output.
26-
late final FixedPoint sqrt = a.clone(name: 'sqrt')..gets(output('sqrt'));
26+
late final FixedPoint sqrt;
2727

2828
/// Square root a fixed point number [a], returning result in [sqrt].
2929
FixedPointSqrtBase(FixedPoint a,
@@ -35,9 +35,9 @@ abstract class FixedPointSqrtBase extends Module {
3535
super(
3636
definitionName:
3737
definitionName ?? 'FixedPointSquareRoot${a.width}') {
38-
this.a = a.clone(name: 'a')..gets(addInput('a', a, width: a.width));
38+
this.a = addTypedInput('a', a);
3939

40-
addOutput('sqrt', width: width);
40+
sqrt = addTypedOutput('sqrt', a.clone);
4141
}
4242
}
4343

0 commit comments

Comments
 (0)