Skip to content
Draft
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
05d77fe
new components: cam and replacement
desmonddak Sep 19, 2025
41bf98e
test/memory added
desmonddak Sep 23, 2025
be4dfbf
smoke test for Cache working
desmonddak Sep 23, 2025
11f056f
cleanup and more file moves
desmonddak Sep 23, 2025
b1771d7
updated code docs for cache
desmonddak Sep 23, 2025
fa52418
updated code docs for cache
desmonddak Sep 23, 2025
26bf9e2
Cache passes a fill then read exhaustive test avoiding evictions
desmonddak Sep 25, 2025
8513ff1
Cache cleanup: still need to figure out TagRF valid bit storage
desmonddak Sep 25, 2025
6bf67b3
Cache cleanup -- use different interfaces for read vs write
desmonddak Sep 25, 2025
e6ead6f
Cache cleanup -- remove vcd generation
desmonddak Sep 25, 2025
ac75a69
updated replacement to be cleaner
desmonddak Sep 27, 2025
22432a1
added valid-bit handling for tags
desmonddak Sep 28, 2025
853afe2
forgot to remove wavedumper
desmonddak Sep 29, 2025
b8a10fa
working with valid bit but not yet invalidating
desmonddak Sep 29, 2025
08727d2
invalidate logic in, needs tests
desmonddak Sep 29, 2025
ff381bd
invalidate working
desmonddak Sep 29, 2025
9ef67c2
write to a hit, double-write tests
desmonddak Sep 29, 2025
5f99bf4
Better SV output, cleaner ROHD
desmonddak Sep 29, 2025
3389438
Cleaner ROHD, should pass CI
desmonddak Sep 30, 2025
104e6aa
Update cache_test.dart
desmonddak Sep 30, 2025
b29d48c
better organization of cache files, cleanup of comments
desmonddak Sep 30, 2025
ad869e5
use flop vs Sequential and reduce loops
desmonddak Oct 1, 2025
0637d7c
better naming for the ReadCache
desmonddak Oct 1, 2025
0021f24
simplified rohd_hcl.dart and udpated cam to remove enable
desmonddak Oct 1, 2025
d8b4a45
proposed Cache interface for handling evictions
desmonddak Oct 2, 2025
79b755b
Merge branch 'main' into cache
desmonddak Oct 4, 2025
fee2be5
prepare for invalidate output
desmonddak Oct 8, 2025
a3391d7
Reproduced combinational RF read issue
desmonddak Oct 9, 2025
aa090f2
working on cache bug fix
desmonddak Oct 10, 2025
0df1d26
overlap read and write testing
desmonddak Oct 13, 2025
a48cc2b
Added Cache and Cam documentation
desmonddak Oct 13, 2025
11c3d95
pull the vcd generation
desmonddak Oct 13, 2025
85a7966
cache componentry
desmonddak Oct 17, 2025
f0a387b
doc update
desmonddak Oct 17, 2025
2194e40
new caching components
desmonddak Oct 29, 2025
98c2d10
minor fixes that blocked CI
desmonddak Oct 29, 2025
e9f01ba
compute cache hit without request.valid
desmonddak Nov 3, 2025
8fe97c3
directory reorg for cache components
desmonddak Nov 4, 2025
a0694a8
updated cache components with eviction, etc
desmonddak Nov 4, 2025
94c195a
add logicstructure components using new addTyped protocol
desmonddak Nov 4, 2025
3aae40a
better docs for caches
desmonddak Nov 5, 2025
10aeb52
new primer on creating a good module
desmonddak Nov 5, 2025
b0e5c84
removed unused module
desmonddak Nov 5, 2025
d3b507f
combinational dependency ready->valid
desmonddak Nov 6, 2025
1e0a5e6
cache configuration
desmonddak Nov 6, 2025
463a48d
cleanup
desmonddak Nov 6, 2025
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
156 changes: 156 additions & 0 deletions doc/Module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# 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, use `nextNegEdge` to look at the value mid-way through the clock
cycle, because if you wait for the next positive edge, then you will miss this
output as it will be whatever is triggered by the next clk edge.

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.
9 changes: 7 additions & 2 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
177 changes: 176 additions & 1 deletion doc/components/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccessInterface> hits,
List<AccessInterface> misses,
List<AccessInterface> 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 lookaside 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 backpressure 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
);
```
3 changes: 0 additions & 3 deletions lib/rohd_hcl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
Loading