Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve MemoryStorage read and add dump for verilog-compliant memory files #176

Merged
merged 22 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c3bd305
Start destroying the FloatingPointValue factories
mkorbel1 Feb 7, 2025
d77b74d
compiles and run FPV: exhaustive round-trip, but hack and does not ru…
desmonddak Feb 7, 2025
a2f786f
WIP refactor to remove factories and static methods
mkorbel1 Feb 8, 2025
96c8929
populators
mkorbel1 Feb 11, 2025
89464f6
compile clean, some test failures
mkorbel1 Feb 12, 2025
d204bd2
lint cleanup, doc comments
mkorbel1 Feb 13, 2025
63fd224
add more testig around ops and constant creation
mkorbel1 Feb 13, 2025
8f785a1
cleanup compile errors after rebase
mkorbel1 Feb 13, 2025
9bba82c
fix infinity checks for e4m3
mkorbel1 Feb 13, 2025
31e1903
update docs
mkorbel1 Feb 13, 2025
6719de0
got memory storage read and write more generic and working
mkorbel1 Feb 18, 2025
16d749a
round trip test works
mkorbel1 Feb 18, 2025
b99d35e
all tests now pass: bug in mantissa64 handling in ofDouble, and a bug…
desmonddak Feb 19, 2025
c090c3e
more fixes
mkorbel1 Feb 19, 2025
e70308b
Merge branch 'fpv_destroy_factories' of https://github.com/intel/rohd…
mkorbel1 Feb 19, 2025
03ec284
got comments and weird alignments working better
mkorbel1 Feb 20, 2025
56d3091
test binary data
mkorbel1 Feb 20, 2025
f301b6f
cleanup, add doc
mkorbel1 Feb 20, 2025
825d5a2
Merge branch 'main' of https://github.com/intel/rohd-hcl into dumpmem
mkorbel1 Feb 21, 2025
3b82c50
remove redundant line
mkorbel1 Feb 21, 2025
4f22d76
fix doc and comments
mkorbel1 Feb 24, 2025
7ef3301
Merge branch 'main' of https://github.com/intel/rohd-hcl into dumpmem
mkorbel1 Feb 24, 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
2 changes: 1 addition & 1 deletion doc/components/floating_point.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Other special widths of floating-point values supported are:
- [FloatingPointBF16Value](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointBF16Value-class.html)
- [FloatingPointTF32Value](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointTF32Value-class.html)

Finally, we have a [random value constructor](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointValue/FloatingPointValue.random.html) generator for testing purposes, generating valid [FloatingPointValue](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointValue-class.html) types, optionally constrained to normal range (mantissa in $[1, 2)$).
Finally, we have a [random value constructor](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointValuePopulator/random.html) generator for testing purposes, generating valid [FloatingPointValue](https://intel.github.io/rohd-hcl/rohd_hcl/FloatingPointValue-class.html) types, optionally constrained to normal range (mantissa in $[1, 2)$).

### Populators

Expand Down
2 changes: 2 additions & 0 deletions doc/components/memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ The read path is combinational, so data is provided immediately according to the
## 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`).
3 changes: 3 additions & 0 deletions lib/src/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ class RohdHclException implements Exception {

/// Creates an [Exception] for the ROHD Hardware Component Library.
RohdHclException(this.message);

@override
String toString() => 'RohdHclException($message)';
}
6 changes: 4 additions & 2 deletions lib/src/models/memory_model.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2024 Intel Corporation
// Copyright (C) 2021-2025 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// memory_model.dart
Expand Down Expand Up @@ -130,7 +130,9 @@ class MemoryModel extends Memory {

/// Updates read data for [rdPort] after [readLatency] time.
Future<void> _updateRead(DataPortInterface rdPort, LogicValue data) async {
await clk.waitCycles(readLatency - 1);
if (readLatency > 1) {
await clk.waitCycles(readLatency - 1);
}
rdPort.data.inject(data);
}
}
211 changes: 165 additions & 46 deletions lib/src/models/sparse_memory_storage.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright (C) 2023-2024 Intel Corporation
// Copyright (C) 2023-2025 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// sparse_memory_storage.dart
// Implementation of memory storage.
//
// 2023 June 12

import 'package:collection/collection.dart';
import 'package:rohd/rohd.dart';
import 'package:rohd_hcl/src/exceptions.dart';
import 'package:rohd_hcl/src/utils.dart';

/// A storage for memory models.
abstract class MemoryStorage {
Expand Down Expand Up @@ -69,7 +71,16 @@ abstract class MemoryStorage {
onInvalidRead = onInvalidRead ?? _defaultOnInvalidRead,
alignAddress = alignAddress ?? _defaultAlignAddress;

/// Reads a verilog-compliant hex file and preloads memory with it.
/// Reads a verilog-compliant mem file and preloads memory with it.
///
/// The loaded address will increment each [dataWidth] bits of data between
/// address `@` annotations. Data is parsed according to the provided
/// [radix], which must be a positive power of 2 up to 16. The address for
/// data will increment by 1 for each [bitsPerAddress] bits of data, which
/// must be a power of 2 less than [dataWidth].
///
/// Line comments (`//`) are supported and any whitespace is supported as a
/// separator between data. Block comments (`/* */`) are not supported.
///
/// Example input format:
/// ```
Expand All @@ -81,79 +92,167 @@ abstract class MemoryStorage {
/// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
/// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
/// ```
void loadMemHex(String hexMemContents) {
/// The number of bytes per cacheline.
const lineBytes = 4;
void loadMemString(String memContents,
{int radix = 16, int bitsPerAddress = 8}) {
if (radix <= 0 || (radix & (radix - 1) != 0) || radix > 16) {
throw RohdHclException('Radix must be a positive power of 2 (max 16).');
}

if (dataWidth % bitsPerAddress != 0) {
throw RohdHclException('dataWidth must be a multiple of bitsPerAddress.');
}

final bitsPerChar = log2Ceil(radix);
final charsPerLine = dataWidth ~/ bitsPerChar;
final addrIncrPerLine = dataWidth ~/ bitsPerAddress;

var address = 0;
var bytes = <String>[];
final chunks = <String>[];
var chunksLength = 0;

void addChunk([String? chunk]) {
if (chunk != null) {
// ignore: parameter_assignments
chunk = chunk.trim();

chunks.add(chunk);
chunksLength += chunk.length;
}

while (chunksLength >= charsPerLine) {
final pendingData = chunks.reversed.join();
final cutPoint = chunksLength - charsPerLine;
final thisData = pendingData.substring(cutPoint);
chunks.clear();

final remaining = pendingData.substring(0, cutPoint);

chunksLength = 0;

if (remaining.isNotEmpty) {
chunks.add(remaining);
chunksLength = remaining.length;
}

void addByte(String byte) {
bytes.add(byte);
if (bytes.length == lineBytes) {
final lvData = LogicValue.ofBigInt(
BigInt.parse(bytes.reversed.join(), radix: 16), lineBytes * 8);
final addr = LogicValue.ofInt(address - address % lineBytes, addrWidth);
BigInt.parse(thisData, radix: radix), dataWidth);
final addr =
LogicValue.ofInt(address - (address % addrIncrPerLine), addrWidth);
setData(addr, lvData);
bytes = [];

address += addrIncrPerLine;
}
}

List<String> reconstruct(LogicValue data) {
final out = <String>[];
for (var counter = 0; counter < lineBytes; counter++) {
out.add(data
.getRange(counter * 8, (counter + 1) * 8)
.toInt()
.toRadixString(16)
.padLeft(2, '0'));
void padToAlignment() {
addChunk();
while (chunksLength != 0) {
addChunk('0');
}

return out;
}

for (var line in hexMemContents.split('\n')) {
for (var line in memContents.split('\n')) {
// if there's a `//` comment on this line, ditch everything after it
final commentIdx = line.indexOf('//');
if (commentIdx != -1) {
line = line.substring(0, commentIdx);
}

line = line.trim();

if (line.isEmpty) {
continue;
}

if (line.startsWith('@')) {
// pad out remaining bytes as 0
// add that many to address
if (bytes.isNotEmpty) {
final thres = lineBytes - bytes.length;
for (var i = 0; i < thres; i++) {
addByte('00');
address++;
}
padToAlignment();

// if it doesn't match the format, throw
if (!RegExp('@([0-9a-fA-F]+)').hasMatch(line)) {
throw RohdHclException('Invalid address format: $line');
}

// check to see if this block already exists in memory
final lineAddr = int.parse(line.substring(1), radix: 16);
final lineAddrLv =
LogicValue.ofInt(lineAddr - lineAddr % lineBytes, addrWidth);
LogicValue.ofInt(lineAddr - lineAddr % addrIncrPerLine, addrWidth);
if (getData(lineAddrLv) != null) {
// must reconstruct the bytes array ending at the provided address
final endOff = lineAddr % lineBytes;
final endOff = lineAddr % addrIncrPerLine;
final origData = getData(lineAddrLv);
final newData = reconstruct(origData!).sublist(0, endOff);
bytes = newData;
chunks
..clear()
..add(origData!
.getRange(0, endOff * bitsPerAddress)
.toInt()
.toRadixString(radix));
}

address = lineAddr;
address = lineAddrLv.toInt();
} else {
for (final byte in line.split(RegExp(r'\s'))) {
addByte(byte);
address++;
}
line.split(RegExp(r'\s')).forEach(addChunk);
}
}
// pad out remaining bytes as 0
// add that many to address
final thres = lineBytes - bytes.length;
if (bytes.isNotEmpty) {
for (var i = 0; i < thres; i++) {
addByte('00');
address++;
padToAlignment();
}

/// Dumps the contents of memory to a verilog-compliant hex file.
///
/// The address will increment each [bitsPerAddress] bits of data between
/// address `@` annotations. Data is output according to the provided [radix],
/// which must be a positive power of 2 up to 16.
String dumpMemString({int radix = 16, int bitsPerAddress = 8}) {
if (radix <= 0 || (radix & (radix - 1) != 0) || radix > 16) {
throw RohdHclException('Radix must be a positive power of 2 (max 16).');
}

if (isEmpty) {
return '';
}

final bitsPerChar = log2Ceil(radix);

final addrs = addresses.sorted();

final memString = StringBuffer();

LogicValue? currentAddr;

for (final addr in addrs) {
if (currentAddr != addr) {
memString.writeln('@${addr.toInt().toRadixString(16)}');
currentAddr = addr;
}

final data = getData(addr)!;
memString.writeln(data
.toInt()
.toRadixString(radix)
.padLeft(data.width ~/ bitsPerChar, '0'));
currentAddr = currentAddr! +
LogicValue.ofInt(dataWidth ~/ bitsPerAddress, addrWidth);
}

return memString.toString();
}

/// Reads a verilog-compliant hex file and preloads memory with it.
///
/// Example input format:
/// ```
/// @80000000
/// B3 02 00 00 33 05 00 00 B3 05 00 00 13 05 F5 1F
/// 6F 00 40 00 93 02 10 00 17 03 00 00 13 03 83 02
/// 23 20 53 00 6F 00 00 00
/// @80000040
/// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
/// 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
/// ```
@Deprecated('Use `loadMemString` instead.')
void loadMemHex(String hexMemContents) {
loadMemString(hexMemContents);
}

/// Resets all memory to initial state.
Expand Down Expand Up @@ -195,6 +294,9 @@ abstract class MemoryStorage {

/// Returns true if there is no data stored in this memory.
bool get isEmpty;

/// A list of [addresses] which have data stored in this memory.
List<LogicValue> get addresses;
}

/// A sparse storage for memory models.
Expand All @@ -217,15 +319,32 @@ class SparseMemoryStorage extends MemoryStorage {
throw RohdHclException('Can only write to valid addresses.');
}

if (addr.width != addrWidth) {
throw RohdHclException('Address width must be $addrWidth.');
}

if (data.width != dataWidth) {
throw RohdHclException('Data width must be $dataWidth.');
}

_memory[addr] = data;
}

@override
LogicValue? getData(LogicValue addr) => _memory[addr];
LogicValue? getData(LogicValue addr) {
if (addr.width != addrWidth) {
throw RohdHclException('Address width must be $addrWidth.');
}

return _memory[addr];
}

@override
void reset() => _memory.clear();

@override
bool get isEmpty => _memory.isEmpty;

@override
List<LogicValue> get addresses => _memory.keys.toList();
}
Loading