From 1bd1db9c42832f52f8ff51042464acbd5f4ac667 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 10:42:46 -0700 Subject: [PATCH 01/41] sparse memory storage added with basic test --- lib/rohd_hcl.dart | 1 + lib/src/models/memory_model.dart | 5 + lib/src/models/models.dart | 4 + lib/src/models/sparse_memory_storage.dart | 132 ++++++++++++++++++++++ test/example1.hex | 7 ++ test/sparse_memory_storage_test.dart | 24 ++++ 6 files changed, 173 insertions(+) create mode 100644 lib/src/models/memory_model.dart create mode 100644 lib/src/models/models.dart create mode 100644 lib/src/models/sparse_memory_storage.dart create mode 100644 test/example1.hex create mode 100644 test/sparse_memory_storage_test.dart diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index c76c1259..5dd1c62a 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -8,6 +8,7 @@ export 'src/exceptions.dart'; export 'src/fifo.dart'; export 'src/interfaces/interfaces.dart'; export 'src/memory.dart'; +export 'src/models/models.dart'; export 'src/multiplier.dart'; export 'src/one_hot.dart'; export 'src/ripple_carry_adder.dart'; diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart new file mode 100644 index 00000000..fbd61b1d --- /dev/null +++ b/lib/src/models/memory_model.dart @@ -0,0 +1,5 @@ +import 'package:rohd_hcl/rohd_hcl.dart'; + +class MemoryModel extends Memory { + MemoryModel(super.clk, super.reset, super.writePorts, super.readPorts); +} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart new file mode 100644 index 00000000..6e063f44 --- /dev/null +++ b/lib/src/models/models.dart @@ -0,0 +1,4 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'sparse_memory_storage.dart'; diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart new file mode 100644 index 00000000..b98f79c4 --- /dev/null +++ b/lib/src/models/sparse_memory_storage.dart @@ -0,0 +1,132 @@ +import 'dart:collection'; +import 'dart:io'; + +import 'package:rohd/rohd.dart'; + +/// A storage for memory models. +abstract class MemoryStorage { + /// 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 + /// ``` + void loadMemHex(String hexMemContents); + + /// Resets all memory to initial state. + void reset(); + + /// Loads [data] into [addr]. + void preload(LogicValue addr, LogicValue data); + + /// Returns the data at [addr], or `null` if it is not present. + LogicValue? getData(LogicValue addr); +} + +/// A sparse storage for memory models. +class SparseMemoryStorage extends MemoryStorage { + /// The number of bytes per cacheline. + static const int lineBytes = 4; + + final Map _memory = {}; + + /// The width of addresses. + final int addrWidth; + + /// Constructs a new sparse memory storage with specified [addrWidth] for all + /// addresses. + SparseMemoryStorage({required this.addrWidth}); + + @override + void loadMemHex(String hexMemContents) { + var address = 0; + var bytes = []; + + 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); + preload(addr, lvData); + bytes = []; + } + } + + List reconstruct(LogicValue data) { + final out = []; + for (var counter = 0; counter < lineBytes; counter++) { + out.add(data + .getRange(counter * 8, (counter + 1) * 8) + .toInt() + .toRadixString(16) + .padLeft(2, '0')); + } + + return out; + } + + for (var line in hexMemContents.split('\n')) { + line = line.trim(); + 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++; + } + } + + // 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); + if (_memory.containsKey(lineAddrLv)) { + // must reconstruct the bytes array ending at the provided address + final endOff = lineAddr % lineBytes; + final origData = getData(lineAddrLv); + final newData = reconstruct(origData!).sublist(0, endOff); + bytes = newData; + } + + address = lineAddr; + } else { + for (final byte in line.split(RegExp(r'\s'))) { + addByte(byte); + address++; + } + } + } + // 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++; + } + } + } + + /// Dumps a full view of the memory. + Map dumpMemory() => UnmodifiableMapView(_memory); + + @override + void preload(LogicValue addr, LogicValue data) { + _memory[addr] = data; + } + + @override + LogicValue? getData(LogicValue addr) => _memory[addr]; + + @override + void reset() => _memory.clear(); +} diff --git a/test/example1.hex b/test/example1.hex new file mode 100644 index 00000000..59f210fd --- /dev/null +++ b/test/example1.hex @@ -0,0 +1,7 @@ + @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 \ No newline at end of file diff --git a/test/sparse_memory_storage_test.dart b/test/sparse_memory_storage_test.dart new file mode 100644 index 00000000..c4c11e0a --- /dev/null +++ b/test/sparse_memory_storage_test.dart @@ -0,0 +1,24 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sparse_memory_storage_test.dart +// Tests for sparse memory storage +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:io'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/models/sparse_memory_storage.dart'; +import 'package:test/test.dart'; + +void main() { + test('sparse memory storage can load a simple file', () { + final hex = File('test/example1.hex').readAsStringSync(); + final storage = SparseMemoryStorage(addrWidth: 32)..loadMemHex(hex); + + expect(storage.getData(LogicValue.ofInt(0x8000000c, 32))!.toInt(), + equals(0x1ff50513)); + }); +} From b7bbec13bb0147f50bb0fcc7b96b3ca9e961413e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 12:15:49 -0700 Subject: [PATCH 02/41] memory model and tests --- lib/rohd_hcl.dart | 2 +- lib/src/memory/memories.dart | 5 + lib/src/{ => memory}/memory.dart | 94 ++-------- lib/src/memory/register_file.dart | 83 +++++++++ lib/src/models/memory_model.dart | 166 +++++++++++++++++- lib/src/models/sparse_memory_storage.dart | 29 +++- test/memory_test.dart | 201 ++++++++++++++++++++++ test/rf_test.dart | 162 ----------------- 8 files changed, 494 insertions(+), 248 deletions(-) create mode 100644 lib/src/memory/memories.dart rename lib/src/{ => memory}/memory.dart (61%) create mode 100644 lib/src/memory/register_file.dart create mode 100644 test/memory_test.dart delete mode 100644 test/rf_test.dart diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index 5dd1c62a..4d42445d 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -7,7 +7,7 @@ export 'src/carry_save_mutiplier.dart'; export 'src/exceptions.dart'; export 'src/fifo.dart'; export 'src/interfaces/interfaces.dart'; -export 'src/memory.dart'; +export 'src/memory/memories.dart'; export 'src/models/models.dart'; export 'src/multiplier.dart'; export 'src/one_hot.dart'; diff --git a/lib/src/memory/memories.dart b/lib/src/memory/memories.dart new file mode 100644 index 00000000..dc51cd89 --- /dev/null +++ b/lib/src/memory/memories.dart @@ -0,0 +1,5 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'memory.dart'; +export 'register_file.dart'; diff --git a/lib/src/memory.dart b/lib/src/memory/memory.dart similarity index 61% rename from lib/src/memory.dart rename to lib/src/memory/memory.dart index 56d3588b..ba13656c 100644 --- a/lib/src/memory.dart +++ b/lib/src/memory/memory.dart @@ -2,12 +2,13 @@ // SPDX-License-Identifier: BSD-3-Clause // // memory.dart -// Memory interfaces and modules, including RF. +// Memory interfaces and modules. // // 2021 November 3 // Author: Max Korbel // +import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/exceptions.dart'; @@ -95,14 +96,24 @@ abstract class Memory extends Module { /// The data width. final int dataWidth; - final List _wrPorts = []; - final List _rdPorts = []; + /// The number of cycles before data is returned after a read. + int get readLatency; + + /// Internal write ports. + @protected + final List wrPorts = []; + + /// Internal read ports. + @protected + final List rdPorts = []; /// Internal clock. - Logic get _clk => input('clk'); + @protected + Logic get clk => input('clk'); /// Internal reset. - Logic get _reset => input('reset'); + @protected + Logic get reset => input('reset'); /// Construct a new memory. Memory(Logic clk, Logic reset, List writePorts, @@ -133,14 +144,14 @@ abstract class Memory extends Module { addInput('reset', reset); for (var i = 0; i < numReads; i++) { - _rdPorts.add(readPorts[i].clone() + rdPorts.add(readPorts[i].clone() ..connectIO(this, readPorts[i], inputTags: {DataPortGroup.control}, outputTags: {DataPortGroup.data}, uniquify: (original) => 'rd_${original}_$i')); } for (var i = 0; i < numWrites; i++) { - _wrPorts.add(writePorts[i].clone() + wrPorts.add(writePorts[i].clone() ..connectIO(this, writePorts[i], inputTags: {DataPortGroup.control, DataPortGroup.data}, outputTags: {}, @@ -148,72 +159,3 @@ abstract class Memory extends Module { } } } - -/// A flop-based memory. -class RegisterFile extends Memory { - /// Accesses the read data for the provided [index]. - Logic rdData(int index) => _rdPorts[index].data; - - /// The number of entries in the RF. - final int numEntries; - - /// Constructs a new RF. - /// - /// [MaskedDataPortInterface]s are supported on `writePorts`, but not on - /// `readPorts`. - RegisterFile(super.clk, super.reset, super.writePorts, super.readPorts, - {this.numEntries = 8, super.name = 'rf'}) { - _buildLogic(); - } - - /// A testbench hook to access data at a given address. - LogicValue? getData(LogicValue addr) => _storageBank[addr.toInt()].value; - - /// Flop-based storage of all memory. - late final List _storageBank; - - void _buildLogic() { - // create local storage bank - _storageBank = List.generate( - numEntries, (i) => Logic(name: 'storageBank_$i', width: dataWidth)); - - Sequential(_clk, [ - If(_reset, then: [ - // zero out entire storage bank on reset - ..._storageBank.map((e) => e < 0) - ], orElse: [ - for (var entry = 0; entry < numEntries; entry++) - ..._wrPorts.map((wrPort) => - // set storage bank if write enable and pointer matches - If(wrPort.en & wrPort.addr.eq(entry), then: [ - _storageBank[entry] < - (wrPort is MaskedDataPortInterface - ? [ - for (var index = 0; index < dataWidth ~/ 8; index++) - mux( - wrPort.mask[index], - wrPort.data - .getRange(index * 8, (index + 1) * 8), - _storageBank[entry] - .getRange(index * 8, (index + 1) * 8)) - ].rswizzle() - : wrPort.data), - ])), - ]), - ]); - - Combinational([ - ..._rdPorts.map((rdPort) => If(_reset | ~rdPort.en, then: [ - rdPort.data < Const(0, width: dataWidth) - ], orElse: [ - Case(rdPort.addr, [ - for (var entry = 0; entry < numEntries; entry++) - CaseItem(Const(LogicValue.ofInt(entry, addrWidth)), - [rdPort.data < _storageBank[entry]]) - ], defaultItem: [ - rdPort.data < Const(0, width: dataWidth) - ]) - ])) - ]); - } -} diff --git a/lib/src/memory/register_file.dart b/lib/src/memory/register_file.dart new file mode 100644 index 00000000..d8dfbe93 --- /dev/null +++ b/lib/src/memory/register_file.dart @@ -0,0 +1,83 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// register_file.dart +// A register file. +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// A flop-based memory. +class RegisterFile extends Memory { + /// Accesses the read data for the provided [index]. + Logic rdData(int index) => rdPorts[index].data; + + /// The number of entries in the RF. + final int numEntries; + + /// Constructs a new RF. + /// + /// [MaskedDataPortInterface]s are supported on `writePorts`, but not on + /// `readPorts`. + RegisterFile(super.clk, super.reset, super.writePorts, super.readPorts, + {this.numEntries = 8, super.name = 'rf'}) { + _buildLogic(); + } + + /// A testbench hook to access data at a given address. + LogicValue? getData(LogicValue addr) => _storageBank[addr.toInt()].value; + + /// Flop-based storage of all memory. + late final List _storageBank; + + void _buildLogic() { + // create local storage bank + _storageBank = List.generate( + numEntries, (i) => Logic(name: 'storageBank_$i', width: dataWidth)); + + Sequential(clk, [ + If(reset, then: [ + // zero out entire storage bank on reset + ..._storageBank.map((e) => e < 0) + ], orElse: [ + for (var entry = 0; entry < numEntries; entry++) + ...wrPorts.map((wrPort) => + // set storage bank if write enable and pointer matches + If(wrPort.en & wrPort.addr.eq(entry), then: [ + _storageBank[entry] < + (wrPort is MaskedDataPortInterface + ? [ + for (var index = 0; index < dataWidth ~/ 8; index++) + mux( + wrPort.mask[index], + wrPort.data + .getRange(index * 8, (index + 1) * 8), + _storageBank[entry] + .getRange(index * 8, (index + 1) * 8)) + ].rswizzle() + : wrPort.data), + ])), + ]), + ]); + + Combinational([ + ...rdPorts.map((rdPort) => If(reset | ~rdPort.en, then: [ + rdPort.data < Const(0, width: dataWidth) + ], orElse: [ + Case(rdPort.addr, [ + for (var entry = 0; entry < numEntries; entry++) + CaseItem(Const(LogicValue.ofInt(entry, addrWidth)), + [rdPort.data < _storageBank[entry]]) + ], defaultItem: [ + rdPort.data < Const(0, width: dataWidth) + ]) + ])) + ]); + } + + @override + int get readLatency => 0; +} diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index fbd61b1d..1020ed7d 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -1,5 +1,169 @@ +// Copyright (C) 2021-2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// memory_model.dart +// A model for `Memory` +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +/// A model of a [Memory] which uses a software-based [SparseMemoryStorage] to +/// store data. +/// +/// This is useful to mimic a large memory. This is *not* synthesizable, it is +/// only a model. class MemoryModel extends Memory { - MemoryModel(super.clk, super.reset, super.writePorts, super.readPorts); + /// The memory storage underlying this model. + late final MemoryStorage storage = SparseMemoryStorage(addrWidth: addrWidth); + + /// A pre-signal before the output flops of this memory. + late final List _rdDataPre; + + /// If true, a positive edge on reset will reset the memory asynchronously. + final bool asyncReset; + + /// A function called if an invalid write is made when [storage] is not empty. + /// + /// An invalid write will reset the entire memory after calling this function. + /// + /// By default, this will print a warning message. + final void Function() onInvalidWrite; + + /// Default behavior for [onInvalidWrite]. + static void _defaultOnInvalidWrite() { + // ignore: avoid_print + print('WARNING: Memory was cleared by invalid write!'); + } + + /// A function called if a read is made to an address that has no data in + /// [storage]. + /// + /// By default, this will print a warning message and return `x`. + /// + /// This is *not* called when a read's valid or address has invalid bits; in + /// those cases the memory will return `x` always. + final LogicValue Function(LogicValue addr, int dataWidth) onInvalidRead; + + /// Default behavior for [onInvalidRead]. + static LogicValue _defaultOnInvalidRead(LogicValue addr, int dataWidth) { + // ignore: avoid_print + print('WARNING: reading from address $addr that has no data!'); + return LogicValue.filled(dataWidth, LogicValue.x); + } + + /// A function to align addresses when used for transactions. + /// + /// By default, this will align (mask) addresses to a multiple of 4. + final LogicValue Function(LogicValue addr) alignAddress; + + /// Default behavior for [alignAddress]. + static LogicValue _defaultAlignAddress(LogicValue addr) => addr - (addr % 4); + + @override + final int readLatency; + + /// Creates a new [MemoryModel]. + MemoryModel( + super.clk, + super.reset, + super.writePorts, + super.readPorts, { + this.readLatency = 1, + this.asyncReset = true, + this.onInvalidWrite = _defaultOnInvalidWrite, + this.onInvalidRead = _defaultOnInvalidRead, + this.alignAddress = _defaultAlignAddress, + }) { + _buildLogic(); + } + + void _buildLogic() { + _rdDataPre = List.generate( + numReads, (index) => Logic(name: 'rdDataPre$index', width: dataWidth)); + + if (asyncReset) { + reset.posedge.listen((event) { + storage.reset(); + }); + } + + // on posedge of clock, sample write ports and save to memory + clk.posedge.listen((event) { + if (reset.value == LogicValue.one) { + storage.reset(); + return; + } + for (final wrPort in wrPorts) { + if (!wrPort.en.value.isValid || + (wrPort.en.value == LogicValue.one && !wrPort.addr.value.isValid)) { + if (!storage.isEmpty) { + onInvalidWrite(); + } + + storage.reset(); + + return; + } + if (wrPort.en.value == LogicValue.one) { + var addrValue = wrPort.addr.value; + + addrValue = alignAddress(addrValue); + + if (wrPort is MaskedDataPortInterface) { + storage.setData( + addrValue, + List.generate( + dataWidth ~/ 8, + (index) => wrPort.mask.value[index].toBool() + ? wrPort.data.value.getRange(index * 8, (index + 1) * 8) + : (storage.getData(addrValue) ?? + onInvalidRead(addrValue, dataWidth)) + .getRange(index * 8, (index + 1) * 8), + ).rswizzle()); + } else { + storage.setData(addrValue, wrPort.data.value); + } + } + } + }); + + // on any glitch to read controls, change pre-flop version of read data + for (var i = 0; i < rdPorts.length; i++) { + clk.negedge.listen((event) => _updatePreRead(i)); + + // flop out the read data + var delayedData = _rdDataPre[i]; + for (var delay = 0; delay < readLatency; delay++) { + delayedData = FlipFlop(clk, delayedData).q; + } + + rdPorts[i].data <= delayedData; + } + } + + void _updatePreRead(int rdIndex) { + final rdPort = rdPorts[rdIndex]; + final rdPortPre = _rdDataPre[rdIndex]; + + if (!rdPort.en.value.isValid || + (rdPort.en.value == LogicValue.one && !rdPort.addr.value.isValid)) { + rdPortPre.put(LogicValue.x, fill: true); + return; + } + + if (rdPort.en.value == LogicValue.one) { + var addrValue = rdPort.addr.value; + + addrValue = alignAddress(addrValue); + + if (storage.getData(addrValue) == null) { + rdPortPre.put(onInvalidRead(addrValue, dataWidth)); + } else { + rdPortPre.put(storage.getData(addrValue)); + } + } + } } diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index b98f79c4..d554251a 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -1,7 +1,13 @@ -import 'dart:collection'; -import 'dart:io'; +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// sparse_memory_storage.dart +// Implementation of memory storage. +// +// 2023 June 12 import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/exceptions.dart'; /// A storage for memory models. abstract class MemoryStorage { @@ -23,10 +29,13 @@ abstract class MemoryStorage { void reset(); /// Loads [data] into [addr]. - void preload(LogicValue addr, LogicValue data); + void setData(LogicValue addr, LogicValue data); /// Returns the data at [addr], or `null` if it is not present. LogicValue? getData(LogicValue addr); + + /// Returns true if there is no data stored in this memory. + bool get isEmpty; } /// A sparse storage for memory models. @@ -54,7 +63,7 @@ class SparseMemoryStorage extends MemoryStorage { final lvData = LogicValue.ofBigInt( BigInt.parse(bytes.reversed.join(), radix: 16), lineBytes * 8); final addr = LogicValue.ofInt(address - address % lineBytes, addrWidth); - preload(addr, lvData); + setData(addr, lvData); bytes = []; } } @@ -116,11 +125,12 @@ class SparseMemoryStorage extends MemoryStorage { } } - /// Dumps a full view of the memory. - Map dumpMemory() => UnmodifiableMapView(_memory); - @override - void preload(LogicValue addr, LogicValue data) { + void setData(LogicValue addr, LogicValue data) { + if (!addr.isValid) { + throw RohdHclException('Can only write to valid addresses.'); + } + _memory[addr] = data; } @@ -129,4 +139,7 @@ class SparseMemoryStorage extends MemoryStorage { @override void reset() => _memory.clear(); + + @override + bool get isEmpty => _memory.isEmpty; } diff --git a/test/memory_test.dart b/test/memory_test.dart new file mode 100644 index 00000000..a71f5787 --- /dev/null +++ b/test/memory_test.dart @@ -0,0 +1,201 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// rf_test.dart +// Tests for register file +// +// 2023 March 13 +// Author: Max Korbel +// + +// ignore_for_file: avoid_types_on_closure_parameters + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/memory_model.dart'; +import 'package:test/test.dart'; + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('memory accesses', () { + const numEntries = 20; + + final memoriesToTestGenerators = { + 'rf': (Logic clk, Logic reset, List wrPorts, + List rdPorts) => + RegisterFile(clk, reset, wrPorts, rdPorts, numEntries: numEntries), + 'memory model': (Logic clk, Logic reset, List wrPorts, + List rdPorts) => + MemoryModel( + clk, + reset, + wrPorts, + rdPorts, + alignAddress: (addr) => addr, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ) + }; + + Future waitCycles(Logic clk, int numCycles) async { + for (var i = 0; i < numCycles; i++) { + await clk.nextNegedge; + } + } + + for (final memGen in memoriesToTestGenerators.entries) { + final memGenName = memGen.key; + final memGenFunc = memGen.value; + + test('$memGenName simple', () async { + const dataWidth = 32; + const addrWidth = 5; + + const numWr = 3; + const numRd = 3; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + for (var i = 0; i < numWr; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextNegedge; + reset.put(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.put(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x4 on port 0 + wrPorts[0].en.put(1); + wrPorts[0].addr.put(3); + wrPorts[0].data.put(0xdeadbeef); + + await clk.nextNegedge; + wrPorts[0].en.put(0); + await clk.nextNegedge; + + // read it back out on a different port + rdPorts[2].en.put(1); + rdPorts[2].addr.put(3); + await waitCycles(clk, mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[2].data.value.toInt(), 0xdeadbeef); + + await clk.nextNegedge; + rdPorts[2].en.put(0); + await clk.nextNegedge; + + Simulator.endSimulation(); + await Simulator.simulationEnded; + }); + + test('$memGenName wr masked', () async { + const dataWidth = 32; + const addrWidth = 5; + + const numWr = 1; + const numRd = 1; + + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + + final wrPorts = [ + for (var i = 0; i < numWr; i++) + MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + final rdPorts = [ + for (var i = 0; i < numRd; i++) + DataPortInterface(dataWidth, addrWidth)..en.put(0) + ]; + + final mem = memGenFunc(clk, reset, wrPorts, rdPorts); + + await mem.build(); + + unawaited(Simulator.run()); + + // a little reset flow + await clk.nextNegedge; + reset.put(1); + await clk.nextNegedge; + await clk.nextNegedge; + reset.put(0); + await clk.nextNegedge; + await clk.nextNegedge; + + // write to addr 0x4 on port 0 + wrPorts[0].en.put(1); + wrPorts[0].mask.put(bin('1010')); + wrPorts[0].addr.put(4); + wrPorts[0].data.put(0xffffffff); + + await clk.nextNegedge; + wrPorts[0].en.put(0); + await clk.nextNegedge; + + // read it back out on a different port + rdPorts[0].en.put(1); + rdPorts[0].addr.put(4); + await waitCycles(clk, mem.readLatency); + await clk.nextPosedge; + expect(rdPorts[0].data.value.toInt(), 0xff00ff00); + + await clk.nextNegedge; + rdPorts[0].en.put(0); + await clk.nextNegedge; + + Simulator.endSimulation(); + await Simulator.simulationEnded; + }); + } + }); + + test('non-byte-aligned data widths are legal without masks', () { + DataPortInterface(1, 1); + }); + + group('rf exceptions', () { + test('mismatch addr width', () { + expect( + () => RegisterFile( + Logic(), + Logic(), + [DataPortInterface(32, 31)], + [DataPortInterface(32, 32)], + ), + throwsA(const TypeMatcher())); + }); + + test('mismatch data width', () { + expect( + () => RegisterFile( + Logic(), + Logic(), + [DataPortInterface(64, 32)], + [DataPortInterface(32, 32)], + ), + throwsA(const TypeMatcher())); + }); + }); +} diff --git a/test/rf_test.dart b/test/rf_test.dart deleted file mode 100644 index 0caddad7..00000000 --- a/test/rf_test.dart +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (C) 2023 Intel Corporation -// SPDX-License-Identifier: BSD-3-Clause -// -// rf_test.dart -// Tests for register file -// -// 2023 March 13 -// Author: Max Korbel -// - -import 'dart:async'; - -import 'package:rohd/rohd.dart'; -import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:test/test.dart'; - -void main() { - tearDown(() async { - await Simulator.reset(); - }); - - test('rf simple', () async { - const dataWidth = 32; - const addrWidth = 5; - - const numWr = 3; - const numRd = 3; - - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); - - final wrPorts = [ - for (var i = 0; i < numWr; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - final rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - - final rf = RegisterFile(clk, reset, wrPorts, rdPorts, numEntries: 20); - - await rf.build(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextNegedge; - reset.put(1); - await clk.nextNegedge; - await clk.nextNegedge; - reset.put(0); - await clk.nextNegedge; - await clk.nextNegedge; - - // write to addr 0x4 on port 0 - wrPorts[0].en.put(1); - wrPorts[0].addr.put(4); - wrPorts[0].data.put(0xdeadbeef); - - await clk.nextNegedge; - wrPorts[0].en.put(0); - await clk.nextNegedge; - - // read it back out on a different port - rdPorts[2].en.put(1); - rdPorts[2].addr.put(4); - expect(rdPorts[2].data.value.toInt(), 0xdeadbeef); - - await clk.nextNegedge; - rdPorts[2].en.put(0); - await clk.nextNegedge; - - Simulator.endSimulation(); - await Simulator.simulationEnded; - }); - - test('rf wr masked', () async { - const dataWidth = 32; - const addrWidth = 5; - - const numWr = 1; - const numRd = 1; - - final clk = SimpleClockGenerator(10).clk; - final reset = Logic(); - - final wrPorts = [ - for (var i = 0; i < numWr; i++) - MaskedDataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - final rdPorts = [ - for (var i = 0; i < numRd; i++) - DataPortInterface(dataWidth, addrWidth)..en.put(0) - ]; - - final rf = RegisterFile(clk, reset, wrPorts, rdPorts, numEntries: 20); - - await rf.build(); - - unawaited(Simulator.run()); - - // a little reset flow - await clk.nextNegedge; - reset.put(1); - await clk.nextNegedge; - await clk.nextNegedge; - reset.put(0); - await clk.nextNegedge; - await clk.nextNegedge; - - // write to addr 0x4 on port 0 - wrPorts[0].en.put(1); - wrPorts[0].mask.put(bin('1010')); - wrPorts[0].addr.put(4); - wrPorts[0].data.put(0xffffffff); - - await clk.nextNegedge; - wrPorts[0].en.put(0); - await clk.nextNegedge; - - // read it back out on a different port - rdPorts[0].en.put(1); - rdPorts[0].addr.put(4); - expect(rdPorts[0].data.value.toInt(), 0xff00ff00); - - await clk.nextNegedge; - rdPorts[0].en.put(0); - await clk.nextNegedge; - - Simulator.endSimulation(); - await Simulator.simulationEnded; - }); - - test('non-byte-aligned data widths are legal without masks', () { - DataPortInterface(1, 1); - }); - - group('rf exceptions', () { - test('mismatch addr width', () { - expect( - () => RegisterFile( - Logic(), - Logic(), - [DataPortInterface(32, 31)], - [DataPortInterface(32, 32)], - ), - throwsA(const TypeMatcher())); - }); - - test('mismatch data width', () { - expect( - () => RegisterFile( - Logic(), - Logic(), - [DataPortInterface(64, 32)], - [DataPortInterface(32, 32)], - ), - throwsA(const TypeMatcher())); - }); - }); -} From 0448b142b3cb21bcd01221f5029cd26a218f712c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 12:34:39 -0700 Subject: [PATCH 03/41] move constant where its relevant --- lib/src/models/sparse_memory_storage.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index d554251a..3af5d95a 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -40,9 +40,6 @@ abstract class MemoryStorage { /// A sparse storage for memory models. class SparseMemoryStorage extends MemoryStorage { - /// The number of bytes per cacheline. - static const int lineBytes = 4; - final Map _memory = {}; /// The width of addresses. @@ -54,6 +51,9 @@ class SparseMemoryStorage extends MemoryStorage { @override void loadMemHex(String hexMemContents) { + /// The number of bytes per cacheline. + const lineBytes = 4; + var address = 0; var bytes = []; From b3b718692eaced03b8ee60650f27651fbaef1674 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 12:40:23 -0700 Subject: [PATCH 04/41] update doc reference to memory models and storage --- doc/memory.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/memory.md b/doc/memory.md index ff9b2af1..45c19462 100644 --- a/doc/memory.md +++ b/doc/memory.md @@ -17,3 +17,7 @@ Currently, `RegisterFile` only generates flop-based memory (no latches). The read path is combinational, so data is provided immediately according to the control signals. [RegisterFile Schematic](https://intel.github.io/rohd-hcl/RegisterFile.html) + +## 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. From aa70cc90ae2a879baf1b718b601d90f090e6919c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 13:07:35 -0700 Subject: [PATCH 05/41] update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e74db57..86efb180 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,13 @@ Below is a list of components either already or planning to be implemented. - Count - Count bit=X - Count pattern -- [Sort **(WIP)**](https://github.com/intel/rohd-hcl/issues/9) +- [Sort](./doc/sort.md) - Arithmetic - [Prefix Trees **(WIP)**](https://github.com/intel/rohd-hcl/issues/12) - Adders - Subtractors - - Multipliers - - [Pipelined Integer Multiplier **(WIP)**](https://github.com/intel/rohd-hcl/issues/10) + - [Multipliers](./doc/multiplier.md) + - [Pipelined Integer Multiplier](./doc/multiplier.md#carry-save-multiplier) - Dividers - Log - Square root @@ -105,6 +105,7 @@ Below is a list of components either already or planning to be implemented. - Latch-based - Replacement Policies - LRU + - [Memory Models](./doc/memory.md#memory-models) - [Standard interfaces](./doc/standard_interfaces.md) - AXI - [APB](./doc/standard_interfaces.md#apb) From af91ecceeb8491a9646a055b5263691a0255815b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 14:41:38 -0700 Subject: [PATCH 06/41] starting point for apb bfm --- lib/rohd_hcl.dart | 1 + lib/src/interfaces/apb.dart | 41 ++++-- lib/src/models/apb_bfm/abp_completer.dart | 47 +++++++ lib/src/models/apb_bfm/apb_bfm.dart | 5 + .../models/apb_bfm/apb_completer_driver.dart | 0 .../models/apb_bfm/apb_completer_monitor.dart | 1 + .../apb_bfm/apb_compliance_checker.dart | 5 + lib/src/models/apb_bfm/apb_monitor.dart | 0 lib/src/models/apb_bfm/apb_packet.dart | 42 ++++++ lib/src/models/apb_bfm/apb_requester.dart | 0 .../models/apb_bfm/apb_requester_driver.dart | 123 ++++++++++++++++++ lib/src/models/models.dart | 4 + pubspec.yaml | 6 + test/apb_test.dart | 12 +- 14 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 lib/src/models/apb_bfm/abp_completer.dart create mode 100644 lib/src/models/apb_bfm/apb_bfm.dart create mode 100644 lib/src/models/apb_bfm/apb_completer_driver.dart create mode 100644 lib/src/models/apb_bfm/apb_completer_monitor.dart create mode 100644 lib/src/models/apb_bfm/apb_compliance_checker.dart create mode 100644 lib/src/models/apb_bfm/apb_monitor.dart create mode 100644 lib/src/models/apb_bfm/apb_packet.dart create mode 100644 lib/src/models/apb_bfm/apb_requester.dart create mode 100644 lib/src/models/apb_bfm/apb_requester_driver.dart create mode 100644 lib/src/models/models.dart diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index c76c1259..5dd1c62a 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -8,6 +8,7 @@ export 'src/exceptions.dart'; export 'src/fifo.dart'; export 'src/interfaces/interfaces.dart'; export 'src/memory.dart'; +export 'src/models/models.dart'; export 'src/multiplier.dart'; export 'src/one_hot.dart'; export 'src/ripple_carry_adder.dart'; diff --git a/lib/src/interfaces/apb.dart b/lib/src/interfaces/apb.dart index 472ca73e..f7bf4086 100644 --- a/lib/src/interfaces/apb.dart +++ b/lib/src/interfaces/apb.dart @@ -7,23 +7,31 @@ // 2023 May 19 // Author: Max Korbel +import 'dart:collection'; + import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/exceptions.dart'; -/// A grouping of signals on the [Apb] interface based on direction. +/// A grouping of signals on the [ApbInterface] interface based on direction. enum ApbDirection { /// Miscellaneous system-level signals, common inputs to both sides. misc, - /// Signals driven by the requester. + /// Signals driven by the requester (including select signals). fromRequester, + /// Signals driven by the requester except for select signals. + /// + /// This can be helpful if a completer needs to selectively listen to one + /// bit of the select signals. + fromRequesterExceptSelect, + /// Signals driven by the completer. fromCompleter } /// A standard APB interface. -class Apb extends Interface { +class ApbInterface extends Interface { /// The width of address port [addr]. /// /// Equvalent to the `ADDR_WIDTH` parameter in the APB specification. @@ -95,7 +103,9 @@ class Apb extends Interface { /// The Requester generates a select signal for each Completer. Select /// indicates that the Completer is selected and that a data transfer is /// required. - Logic get selX => port('PSELx'); + late final List sel = UnmodifiableListView(List.generate( + numSelects, (index) => port('PSEL$index'), + growable: false)); /// Enable. /// @@ -171,14 +181,21 @@ class Apb extends Interface { /// Width equal to [userRespWidth]. Only exists if [userRespWidth] > 0. Logic? get bUser => userRespWidth != 0 ? port('PBUSER') : null; + /// The number of select signals on this interface. + /// + /// A completer should always set this to `1`, but a requester may have more + /// than `1`. + final int numSelects; + /// Construct a new instance of an APB interface. - Apb({ + ApbInterface({ this.addrWidth = 32, this.dataWidth = 32, this.userReqWidth = 0, this.userDataWidth = 0, this.userRespWidth = 0, this.includeSlvErr = false, + this.numSelects = 1, }) { _validateParameters(); @@ -193,7 +210,6 @@ class Apb extends Interface { Port('PADDR', addrWidth), Port('PPROT', 3), Port('PNSE'), - Port('PSELx'), Port('PENABLE'), Port('PWRITE'), Port('PWDATA', dataWidth), @@ -201,7 +217,14 @@ class Apb extends Interface { if (userReqWidth != 0) Port('PAUSER', userReqWidth), if (userDataWidth != 0) Port('PWUSER', userDataWidth), ], [ - ApbDirection.fromRequester + ApbDirection.fromRequester, + ApbDirection.fromRequesterExceptSelect, + ]); + + setPorts([ + for (var i = 0; i < numSelects; i++) Port('PSEL$i'), + ], [ + ApbDirection.fromRequester, ]); setPorts([ @@ -216,8 +239,8 @@ class Apb extends Interface { ]); } - /// Constructs a new [Apb] with identical parameters to [other]. - Apb.clone(Apb other) + /// Constructs a new [ApbInterface] with identical parameters to [other]. + ApbInterface.clone(ApbInterface other) : this( addrWidth: other.addrWidth, dataWidth: other.dataWidth, diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart new file mode 100644 index 00000000..34618f52 --- /dev/null +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -0,0 +1,47 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_completer.dart +// A completer model for APB. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A model for the completer side of an [ApbInterface]. +class ApbCompleter extends Component { + /// The interface to drive. + final ApbInterface intf; + + /// The index that this is listening to on the [intf]. + final int selectIndex; + + /// A function which returns the data for the requested address. + final LogicValue Function(LogicValue addr) dataProvider; + + //TODO: why not use a memory storage? + + /// Creates a new model [ApbCompleter]. + ApbCompleter( + {required this.intf, + required this.dataProvider, + required Component parent, + this.selectIndex = 0, + String name = 'apbCompleter'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // intf. + + // wait for reset to complete + await intf.resetN.nextPosedge; + } +} diff --git a/lib/src/models/apb_bfm/apb_bfm.dart b/lib/src/models/apb_bfm/apb_bfm.dart new file mode 100644 index 00000000..6c021a8a --- /dev/null +++ b/lib/src/models/apb_bfm/apb_bfm.dart @@ -0,0 +1,5 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'apb_packet.dart'; +export 'apb_requester_driver.dart'; diff --git a/lib/src/models/apb_bfm/apb_completer_driver.dart b/lib/src/models/apb_bfm/apb_completer_driver.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/models/apb_bfm/apb_completer_monitor.dart b/lib/src/models/apb_bfm/apb_completer_monitor.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/lib/src/models/apb_bfm/apb_completer_monitor.dart @@ -0,0 +1 @@ + diff --git a/lib/src/models/apb_bfm/apb_compliance_checker.dart b/lib/src/models/apb_bfm/apb_compliance_checker.dart new file mode 100644 index 00000000..578512b2 --- /dev/null +++ b/lib/src/models/apb_bfm/apb_compliance_checker.dart @@ -0,0 +1,5 @@ + +// Rules: +// - strobe must not be "active" during read transfer (all low during read) +// - the FSM is followed +// - addr, write, and wdata are valid when psel is asserted \ No newline at end of file diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart new file mode 100644 index 00000000..6206e350 --- /dev/null +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -0,0 +1,42 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_packet.dart +// Packet for APB interface. +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A packet on an [ApbInterface]. +abstract class ApbPacket extends SequenceItem { + /// The address for this packet. + final LogicValue addr; + + /// The index of the select this packet should be driven on. + final int selectIndex; + + /// Creates a new packet. + ApbPacket({required this.addr, this.selectIndex = 0}); +} + +/// A write packet on an [ApbInterface]. +class ApbWritePacket extends ApbPacket { + /// The data for this packet. + final LogicValue data; + + /// Creates a write packet. + ApbWritePacket({required super.addr, required this.data, super.selectIndex}); +} + +/// A read packet on an [ApbInterface]. +class ApbReadPacket extends ApbPacket { + /// Data returned by the read. + LogicValue? returnedData; + + /// Creates a read packet. + ApbReadPacket({required super.addr, super.selectIndex}); +} diff --git a/lib/src/models/apb_bfm/apb_requester.dart b/lib/src/models/apb_bfm/apb_requester.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart new file mode 100644 index 00000000..6f861087 --- /dev/null +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -0,0 +1,123 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_requester_driver.dart +// A driver for APB requests. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_hcl/src/models/apb_bfm/apb_packet.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the [ApbInterface] from the requester side. +class ApbRequesterDriver extends PendingDriver { + /// The interface to drive. + final ApbInterface intf; + + /// Number of cycles before triggering a timeout error. + final int timeoutCycles; + + /// Number of cycles to hold an objection even when no packets are pending. + final int dropDelayCycles; + + /// Creates a new [ApbRequesterDriver]. + ApbRequesterDriver({ + required Component parent, + required this.intf, + required super.sequencer, + this.timeoutCycles = 500, + this.dropDelayCycles = 30, + String name = 'apbRequesterDriver', + }) : super( + name, + parent, + timeout: () async { + await _waitCycles(intf.clk, timeoutCycles); + }, + dropDelay: () async { + await _waitCycles(intf.clk, dropDelayCycles); + }, + ); + + static Future _waitCycles(Logic clk, int numCycles) async { + for (var i = 0; i < numCycles; i++) { + await clk.nextPosedge; + } + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + _deselectAll(); + + // wait for reset to complete before driving anything + await intf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + Simulator.injectAction(_deselectAll); + } + } + } + + /// Drives a packet onto the interface. + Future _drivePacket(ApbPacket packet) async { + // first, SETUP + + // if we're not selecting this interface, then we need to select it + if (!intf.sel[packet.selectIndex].value.toBool()) { + _select(packet.selectIndex); + } + + Simulator.injectAction(() { + intf.enable.put(0); + intf.addr.put(packet.addr); + + if (packet is ApbWritePacket) { + intf.write.put(1); + intf.wData.put(1); + } else if (packet is ApbReadPacket) { + intf.write.put(0); + intf.wData.put(0); + } + }); + + await intf.clk.nextNegedge; + + // now, ACCESS + intf.enable.inject(1); + + // wait for ready from completer, if not already asserted + if (!intf.ready.value.toBool()) { + await intf.ready.nextPosedge; + } + + if (packet is ApbReadPacket) { + packet.returnedData = intf.rData.value; + } + + // now we're done, since enable and ready are both high, move on + } + + /// Selects [index] and deselects the rest. + void _select(int index) { + _deselectAll(); + intf.sel[index].put(1); + } + + /// Clears all selects. + void _deselectAll() { + // zero out all the selects, which should mask everything else + for (var i = 0; i < intf.numSelects; i++) { + intf.sel[i].put(0); + } + } +} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart new file mode 100644 index 00000000..d3fefb21 --- /dev/null +++ b/lib/src/models/models.dart @@ -0,0 +1,4 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'apb_bfm/apb_bfm.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 43a05957..a951e659 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,4 +17,10 @@ dependencies: dev_dependencies: logging: ^1.0.1 test: ^1.21.0 + +dependency_overrides: + rohd_vf: + git: + url: https://github.com/mkorbel1/rohd-vf.git + ref: pending_driver \ No newline at end of file diff --git a/test/apb_test.dart b/test/apb_test.dart index 8fc115a2..4c76939b 100644 --- a/test/apb_test.dart +++ b/test/apb_test.dart @@ -12,8 +12,8 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; class ApbController extends Module { - ApbController(Apb intf) { - intf = Apb.clone(intf) + ApbController(ApbInterface intf) { + intf = ApbInterface.clone(intf) ..connectIO(this, intf, inputTags: {ApbDirection.misc, ApbDirection.fromRequester}, outputTags: {ApbDirection.fromCompleter}); @@ -21,8 +21,8 @@ class ApbController extends Module { } class ApbRequester extends Module { - ApbRequester(Apb intf) { - intf = Apb.clone(intf) + ApbRequester(ApbInterface intf) { + intf = ApbInterface.clone(intf) ..connectIO(this, intf, inputTags: {ApbDirection.misc, ApbDirection.fromCompleter}, outputTags: {ApbDirection.fromRequester}); @@ -34,7 +34,7 @@ class ApbPair extends Module { clk = addInput('clk', clk); reset = addInput('reset', reset); - final apb = Apb( + final apb = ApbInterface( includeSlvErr: true, userDataWidth: 10, userReqWidth: 11, @@ -55,7 +55,7 @@ void main() { }); test('abp optional ports null', () async { - final apb = Apb(); + final apb = ApbInterface(); expect(apb.aUser, isNull); expect(apb.bUser, isNull); expect(apb.rUser, isNull); From bee97051e96e3e1e487972f2f08f983c8c434432 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 14:49:39 -0700 Subject: [PATCH 07/41] move the mem hex reading into the abstract class --- lib/src/models/sparse_memory_storage.dart | 56 +++++++++++------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index 3af5d95a..4490bea5 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -11,6 +11,12 @@ import 'package:rohd_hcl/src/exceptions.dart'; /// A storage for memory models. abstract class MemoryStorage { + /// The width of addresses. + final int addrWidth; + + /// Constrcuts a [MemoryStorage] with specified [addrWidth]. + MemoryStorage({required this.addrWidth}); + /// Reads a verilog-compliant hex file and preloads memory with it. /// /// Example input format: @@ -23,33 +29,6 @@ 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); - - /// Resets all memory to initial state. - void reset(); - - /// Loads [data] into [addr]. - void setData(LogicValue addr, LogicValue data); - - /// Returns the data at [addr], or `null` if it is not present. - LogicValue? getData(LogicValue addr); - - /// Returns true if there is no data stored in this memory. - bool get isEmpty; -} - -/// A sparse storage for memory models. -class SparseMemoryStorage extends MemoryStorage { - final Map _memory = {}; - - /// The width of addresses. - final int addrWidth; - - /// Constructs a new sparse memory storage with specified [addrWidth] for all - /// addresses. - SparseMemoryStorage({required this.addrWidth}); - - @override void loadMemHex(String hexMemContents) { /// The number of bytes per cacheline. const lineBytes = 4; @@ -98,7 +77,7 @@ class SparseMemoryStorage extends MemoryStorage { final lineAddr = int.parse(line.substring(1), radix: 16); final lineAddrLv = LogicValue.ofInt(lineAddr - lineAddr % lineBytes, addrWidth); - if (_memory.containsKey(lineAddrLv)) { + if (getData(lineAddrLv) != null) { // must reconstruct the bytes array ending at the provided address final endOff = lineAddr % lineBytes; final origData = getData(lineAddrLv); @@ -125,6 +104,27 @@ class SparseMemoryStorage extends MemoryStorage { } } + /// Resets all memory to initial state. + void reset(); + + /// Loads [data] into [addr]. + void setData(LogicValue addr, LogicValue data); + + /// Returns the data at [addr], or `null` if it is not present. + LogicValue? getData(LogicValue addr); + + /// Returns true if there is no data stored in this memory. + bool get isEmpty; +} + +/// A sparse storage for memory models. +class SparseMemoryStorage extends MemoryStorage { + final Map _memory = {}; + + /// Constructs a new sparse memory storage with specified [addrWidth] for all + /// addresses. + SparseMemoryStorage({required super.addrWidth}); + @override void setData(LogicValue addr, LogicValue data) { if (!addr.isValid) { From 0c4196dafc39e3f06c1c914e3a8d3d040b45b19a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 15:41:12 -0700 Subject: [PATCH 08/41] basic first implementation done maybe --- lib/src/models/apb_bfm/abp_completer.dart | 70 +++++++++++++++++-- lib/src/models/apb_bfm/apb_packet.dart | 12 +++- .../models/apb_bfm/apb_requester_driver.dart | 21 ++---- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 34618f52..eb7a72fa 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -18,20 +18,29 @@ class ApbCompleter extends Component { /// The interface to drive. final ApbInterface intf; + //TODO: slverr + /// The index that this is listening to on the [intf]. final int selectIndex; - /// A function which returns the data for the requested address. - final LogicValue Function(LogicValue addr) dataProvider; + /// A place where the completer should save and retrieve data. + /// + /// The [ApbCompleter] will reset [storage] whenever the `resetN` signal is + /// dropped. + final MemoryStorage storage; - //TODO: why not use a memory storage? + /// A function which delays the response for the given `request`. + /// + /// If none is provided, then the delay will always be `0`. + final int Function(ApbPacket request)? responseDelay; /// Creates a new model [ApbCompleter]. ApbCompleter( {required this.intf, - required this.dataProvider, + required this.storage, required Component parent, this.selectIndex = 0, + this.responseDelay, String name = 'apbCompleter'}) : super(name, parent); @@ -39,9 +48,60 @@ class ApbCompleter extends Component { Future run(Phase phase) async { unawaited(super.run(phase)); - // intf. + intf.resetN.negedge.listen((event) { + storage.reset(); + }); + + intf.ready.put(0); // wait for reset to complete await intf.resetN.nextPosedge; + + while (!Simulator.simulationHasEnded) { + await _receive(); + } + } + + /// Receives one packet (or returns if not selected). + Future _receive() async { + await intf.enable.nextPosedge; + + if (!intf.sel[selectIndex].value.toBool()) { + // we're not selected, wait for the next time + return; + } + + ApbPacket pkt; + if (intf.write.value.toBool()) { + pkt = ApbWritePacket( + addr: intf.addr.value, + data: intf.wData.value, + strobe: intf.strb.value, + ); + } else { + pkt = ApbReadPacket(addr: intf.addr.value); + } + + await waitCycles( + intf.clk, + responseDelay != null ? responseDelay!(pkt) : 0, + edge: Edge.neg, + ); + + if (pkt is ApbWritePacket) { + // store the data + storage.setData(pkt.addr, pkt.data); + intf.ready.inject(1); + } else if (pkt is ApbReadPacket) { + // capture the data + Simulator.injectAction(() { + intf.rData.put(storage.getData(pkt.addr)); + intf.ready.put(1); + }); + } + + // wait a cycle then end the transfer + await intf.clk.nextNegedge; + intf.ready.inject(0); } } diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index 6206e350..7b4b66d4 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -28,8 +28,18 @@ class ApbWritePacket extends ApbPacket { /// The data for this packet. final LogicValue data; + /// The strobe associated with this write. + final LogicValue strobe; + /// Creates a write packet. - ApbWritePacket({required super.addr, required this.data, super.selectIndex}); + /// + /// If no [strobe] is provided, it will default to all high. + ApbWritePacket( + {required super.addr, + required this.data, + LogicValue? strobe, + super.selectIndex}) + : strobe = strobe ?? LogicValue.filled(data.width ~/ 8, LogicValue.one); } /// A read packet on an [ApbInterface]. diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 6f861087..2567b861 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -15,33 +15,26 @@ import 'package:rohd_hcl/src/models/apb_bfm/apb_packet.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A driver for the [ApbInterface] from the requester side. -class ApbRequesterDriver extends PendingDriver { +class ApbRequesterDriver extends PendingClockedDriver { /// The interface to drive. final ApbInterface intf; - /// Number of cycles before triggering a timeout error. - final int timeoutCycles; - - /// Number of cycles to hold an objection even when no packets are pending. - final int dropDelayCycles; + //TODO: wakeup + //TODO: protection + //TODO: user /// Creates a new [ApbRequesterDriver]. ApbRequesterDriver({ required Component parent, required this.intf, required super.sequencer, - this.timeoutCycles = 500, - this.dropDelayCycles = 30, + super.timeoutCycles = 500, + super.dropDelayCycles = 30, String name = 'apbRequesterDriver', }) : super( name, parent, - timeout: () async { - await _waitCycles(intf.clk, timeoutCycles); - }, - dropDelay: () async { - await _waitCycles(intf.clk, dropDelayCycles); - }, + clk: intf.clk, ); static Future _waitCycles(Logic clk, int numCycles) async { From b893b4655f167ae31a58cf9480efcc896758ce6b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 16:21:04 -0700 Subject: [PATCH 09/41] added tracking --- lib/src/models/apb_bfm/abp_completer.dart | 20 +++---- lib/src/models/apb_bfm/apb_bfm.dart | 1 + .../models/apb_bfm/apb_completer_driver.dart | 0 .../models/apb_bfm/apb_completer_monitor.dart | 1 - lib/src/models/apb_bfm/apb_monitor.dart | 55 +++++++++++++++++++ lib/src/models/apb_bfm/apb_packet.dart | 48 +++++++++++++++- lib/src/models/apb_bfm/apb_requester.dart | 51 +++++++++++++++++ .../models/apb_bfm/apb_requester_driver.dart | 11 ++-- lib/src/models/apb_bfm/apb_tracker.dart | 33 +++++++++++ 9 files changed, 201 insertions(+), 19 deletions(-) delete mode 100644 lib/src/models/apb_bfm/apb_completer_driver.dart delete mode 100644 lib/src/models/apb_bfm/apb_completer_monitor.dart create mode 100644 lib/src/models/apb_bfm/apb_tracker.dart diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index eb7a72fa..435bdff3 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -2,7 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause // // apb_completer.dart -// A completer model for APB. +// An agent for completing APB requests. // // 2023 June 12 // Author: Max Korbel @@ -14,7 +14,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A model for the completer side of an [ApbInterface]. -class ApbCompleter extends Component { +class ApbCompleter extends Agent { /// The interface to drive. final ApbInterface intf; @@ -71,31 +71,31 @@ class ApbCompleter extends Component { return; } - ApbPacket pkt; + ApbPacket packet; if (intf.write.value.toBool()) { - pkt = ApbWritePacket( + packet = ApbWritePacket( addr: intf.addr.value, data: intf.wData.value, strobe: intf.strb.value, ); } else { - pkt = ApbReadPacket(addr: intf.addr.value); + packet = ApbReadPacket(addr: intf.addr.value); } await waitCycles( intf.clk, - responseDelay != null ? responseDelay!(pkt) : 0, + responseDelay != null ? responseDelay!(packet) : 0, edge: Edge.neg, ); - if (pkt is ApbWritePacket) { + if (packet is ApbWritePacket) { // store the data - storage.setData(pkt.addr, pkt.data); + storage.setData(packet.addr, packet.data); intf.ready.inject(1); - } else if (pkt is ApbReadPacket) { + } else if (packet is ApbReadPacket) { // capture the data Simulator.injectAction(() { - intf.rData.put(storage.getData(pkt.addr)); + intf.rData.put(storage.getData(packet.addr)); intf.ready.put(1); }); } diff --git a/lib/src/models/apb_bfm/apb_bfm.dart b/lib/src/models/apb_bfm/apb_bfm.dart index 6c021a8a..6c1465af 100644 --- a/lib/src/models/apb_bfm/apb_bfm.dart +++ b/lib/src/models/apb_bfm/apb_bfm.dart @@ -1,5 +1,6 @@ // Copyright (C) 2023 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause +export 'abp_completer.dart'; export 'apb_packet.dart'; export 'apb_requester_driver.dart'; diff --git a/lib/src/models/apb_bfm/apb_completer_driver.dart b/lib/src/models/apb_bfm/apb_completer_driver.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/src/models/apb_bfm/apb_completer_monitor.dart b/lib/src/models/apb_bfm/apb_completer_monitor.dart deleted file mode 100644 index 8b137891..00000000 --- a/lib/src/models/apb_bfm/apb_completer_monitor.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart index e69de29b..0009cf65 100644 --- a/lib/src/models/apb_bfm/apb_monitor.dart +++ b/lib/src/models/apb_bfm/apb_monitor.dart @@ -0,0 +1,55 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_monitor.dart +// A monitor that watches the APB interface. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A monitor for [ApbInterface]s. +class ApbMonitor extends Monitor { + /// The interface to monitor. + final ApbInterface intf; + + /// Creates a new [ApbMonitor] on [intf]. + ApbMonitor( + {required this.intf, + required Component parent, + String name = 'apbMonitor'}) + : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await intf.resetN.nextPosedge; + + intf.clk.posedge.listen((event) { + for (var i = 0; i < intf.numSelects; i++) { + if (intf.sel[i].value.toBool() && intf.enable.value.toBool()) { + if (intf.write.value.toBool()) { + add(ApbWritePacket( + addr: intf.addr.value, + data: intf.wData.value, + strobe: intf.strb.value, + selectIndex: i, + )); + } else { + add( + ApbReadPacket( + addr: intf.addr.value, + selectIndex: i, + )..returnedData = intf.rData.value, + ); + } + } + } + }); + } +} diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index 7b4b66d4..fe74b547 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -9,10 +9,11 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/src/interfaces/interfaces.dart'; +import 'package:rohd_hcl/src/models/apb_bfm/apb_tracker.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A packet on an [ApbInterface]. -abstract class ApbPacket extends SequenceItem { +abstract class ApbPacket extends SequenceItem implements Trackable { /// The address for this packet. final LogicValue addr; @@ -21,6 +22,20 @@ abstract class ApbPacket extends SequenceItem { /// Creates a new packet. ApbPacket({required this.addr, this.selectIndex = 0}); + + @override + String? trackerString(TrackerField field) { + switch (field) { + case ApbTracker.timeField: + return Simulator.time.toString(); + case ApbTracker.addrField: + return addr.toString(); + case ApbTracker.selectField: + return selectIndex.toString(); + } + + return null; + } } /// A write packet on an [ApbInterface]. @@ -40,6 +55,20 @@ class ApbWritePacket extends ApbPacket { LogicValue? strobe, super.selectIndex}) : strobe = strobe ?? LogicValue.filled(data.width ~/ 8, LogicValue.one); + + @override + String? trackerString(TrackerField field) { + switch (field) { + case ApbTracker.typeField: + return 'W'; + case ApbTracker.dataField: + return data.toString(); + case ApbTracker.strobeField: + return strobe.toString(includeWidth: false); + } + + return super.trackerString(field); + } } /// A read packet on an [ApbInterface]. @@ -47,6 +76,23 @@ class ApbReadPacket extends ApbPacket { /// Data returned by the read. LogicValue? returnedData; + /// Error indication returned by the read. + LogicValue? returnedSlvErr; + /// Creates a read packet. ApbReadPacket({required super.addr, super.selectIndex}); + + @override + String? trackerString(TrackerField field) { + switch (field) { + case ApbTracker.typeField: + return 'R'; + case ApbTracker.dataField: + return returnedData?.toString(); + case ApbTracker.slverrField: + return returnedSlvErr?.toString(includeWidth: false); + } + + return super.trackerString(field); + } } diff --git a/lib/src/models/apb_bfm/apb_requester.dart b/lib/src/models/apb_bfm/apb_requester.dart index e69de29b..ef551f99 100644 --- a/lib/src/models/apb_bfm/apb_requester.dart +++ b/lib/src/models/apb_bfm/apb_requester.dart @@ -0,0 +1,51 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_requester.dart +// An agent sending for APB requests. +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An agent for sending requests on an [ApbInterface]. +/// +/// Driven read packets will update the returned data into the same packet. +class ApbRequester extends Agent { + /// The interface to drive. + final ApbInterface intf; + + /// The sequencer where requests should be sent. + late final Sequencer sequencer; + + /// The driver that sends the requests over the interface. + late final ApbRequesterDriver driver; + + /// The number of cycles before timing out if no transactions can be sent. + final int timeoutCycles; + + /// The number of cycles before an objection will be dropped when there are + /// no pending packets to send. + final int dropDelayCycles; + + /// Constructs a new [ApbRequester]. + ApbRequester({ + required this.intf, + required Component parent, + String name = 'apbRequester', + this.timeoutCycles = 500, + this.dropDelayCycles = 30, + }) : super(name, parent) { + sequencer = Sequencer('sequencer', this); + + driver = ApbRequesterDriver( + parent: this, + intf: intf, + sequencer: sequencer, + timeoutCycles: timeoutCycles, + dropDelayCycles: dropDelayCycles, + ); + } +} diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 2567b861..90976f4e 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -15,6 +15,8 @@ import 'package:rohd_hcl/src/models/apb_bfm/apb_packet.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A driver for the [ApbInterface] from the requester side. +/// +/// Driven read packets will update the returned data into the same packet. class ApbRequesterDriver extends PendingClockedDriver { /// The interface to drive. final ApbInterface intf; @@ -37,12 +39,6 @@ class ApbRequesterDriver extends PendingClockedDriver { clk: intf.clk, ); - static Future _waitCycles(Logic clk, int numCycles) async { - for (var i = 0; i < numCycles; i++) { - await clk.nextPosedge; - } - } - @override Future run(Phase phase) async { unawaited(super.run(phase)); @@ -76,7 +72,8 @@ class ApbRequesterDriver extends PendingClockedDriver { if (packet is ApbWritePacket) { intf.write.put(1); - intf.wData.put(1); + intf.wData.put(packet.data); + intf.strb.put(packet.strobe); } else if (packet is ApbReadPacket) { intf.write.put(0); intf.wData.put(0); diff --git a/lib/src/models/apb_bfm/apb_tracker.dart b/lib/src/models/apb_bfm/apb_tracker.dart new file mode 100644 index 00000000..854adfbf --- /dev/null +++ b/lib/src/models/apb_bfm/apb_tracker.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_tracker.dart +// A monitor that watches the APB interface. +// +// 2023 June 12 +// Author: Max Korbel + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A tracker for the [ApbInterface]. +class ApbTracker extends Tracker { + static const timeField = TrackerField('time', columnWidth: 12); + static const selectField = TrackerField('select', columnWidth: 4); + static const typeField = TrackerField('type', columnWidth: 1); + static const addrField = TrackerField('addr', columnWidth: 12); + static const dataField = TrackerField('data', columnWidth: 12); + static const strobeField = TrackerField('strobe', columnWidth: 4); + static const slverrField = TrackerField('slverr', columnWidth: 1); + + /// Creates a new tracker for [ApbInterface]. + ApbTracker({String name = 'apbTracker'}) + : super(name, [ + timeField, + typeField, + addrField, + dataField, + strobeField, + slverrField, + ]); +} From 33d67c3f5382c4032b2f3793f275f971b5fedde4 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 16:58:33 -0700 Subject: [PATCH 10/41] first time transactions run, but not working right yet --- lib/src/models/apb_bfm/apb_bfm.dart | 4 + lib/src/models/apb_bfm/apb_monitor.dart | 2 +- lib/src/models/apb_bfm/apb_packet.dart | 24 ++++- .../models/apb_bfm/apb_requester_driver.dart | 6 +- lib/src/models/apb_bfm/apb_tracker.dart | 13 ++- test/apb_bfm_test.dart | 95 +++++++++++++++++++ 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 test/apb_bfm_test.dart diff --git a/lib/src/models/apb_bfm/apb_bfm.dart b/lib/src/models/apb_bfm/apb_bfm.dart index 6c1465af..5c6f78b0 100644 --- a/lib/src/models/apb_bfm/apb_bfm.dart +++ b/lib/src/models/apb_bfm/apb_bfm.dart @@ -2,5 +2,9 @@ // SPDX-License-Identifier: BSD-3-Clause export 'abp_completer.dart'; +export 'apb_compliance_checker.dart'; +export 'apb_monitor.dart'; export 'apb_packet.dart'; +export 'apb_requester.dart'; export 'apb_requester_driver.dart'; +export 'apb_tracker.dart'; diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart index 0009cf65..2a7a275c 100644 --- a/lib/src/models/apb_bfm/apb_monitor.dart +++ b/lib/src/models/apb_bfm/apb_monitor.dart @@ -45,7 +45,7 @@ class ApbMonitor extends Monitor { ApbReadPacket( addr: intf.addr.value, selectIndex: i, - )..returnedData = intf.rData.value, + )..complete(data: intf.rData.value, slvErr: intf.slvErr?.value), ); } } diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index fe74b547..af18a062 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -7,7 +7,10 @@ // 2023 June 12 // Author: Max Korbel +import 'dart:async'; + import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_hcl/src/interfaces/interfaces.dart'; import 'package:rohd_hcl/src/models/apb_bfm/apb_tracker.dart'; import 'package:rohd_vf/rohd_vf.dart'; @@ -74,10 +77,27 @@ class ApbWritePacket extends ApbPacket { /// A read packet on an [ApbInterface]. class ApbReadPacket extends ApbPacket { /// Data returned by the read. - LogicValue? returnedData; + LogicValue? get returnedData => _returnedData; + LogicValue? _returnedData; /// Error indication returned by the read. - LogicValue? returnedSlvErr; + LogicValue? get returnedSlvErr => _returnedSlvErr; + LogicValue? _returnedSlvErr; + + /// A [Future] that completes once the the read has been completed. + Future get completed => _completer.future; + final Completer _completer = Completer(); + + /// Called by a completer when a transfer is completed. + void complete({required LogicValue data, LogicValue? slvErr}) { + if (_returnedData != null || _returnedSlvErr != null) { + throw RohdHclException('Packet is already completed.'); + } + + _returnedData = data; + _returnedSlvErr = slvErr; + _completer.complete(); + } /// Creates a read packet. ApbReadPacket({required super.addr, super.selectIndex}); diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 90976f4e..20152820 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -53,6 +53,7 @@ class ApbRequesterDriver extends PendingClockedDriver { await _drivePacket(pendingSeqItems.removeFirst()); } else { Simulator.injectAction(_deselectAll); + await intf.clk.nextNegedge; } } } @@ -91,7 +92,10 @@ class ApbRequesterDriver extends PendingClockedDriver { } if (packet is ApbReadPacket) { - packet.returnedData = intf.rData.value; + packet.complete( + data: intf.rData.value, + slvErr: intf.slvErr?.value, + ); } // now we're done, since enable and ready are both high, move on diff --git a/lib/src/models/apb_bfm/apb_tracker.dart b/lib/src/models/apb_bfm/apb_tracker.dart index 854adfbf..00abb6a0 100644 --- a/lib/src/models/apb_bfm/apb_tracker.dart +++ b/lib/src/models/apb_bfm/apb_tracker.dart @@ -12,6 +12,8 @@ import 'package:rohd_vf/rohd_vf.dart'; /// A tracker for the [ApbInterface]. class ApbTracker extends Tracker { + //TODO: make these widths dynamic? + static const timeField = TrackerField('time', columnWidth: 12); static const selectField = TrackerField('select', columnWidth: 4); static const typeField = TrackerField('type', columnWidth: 1); @@ -21,13 +23,18 @@ class ApbTracker extends Tracker { static const slverrField = TrackerField('slverr', columnWidth: 1); /// Creates a new tracker for [ApbInterface]. - ApbTracker({String name = 'apbTracker'}) - : super(name, [ + ApbTracker({ + required ApbInterface intf, + String name = 'apbTracker', + super.dumpJson, + super.dumpTable, + super.outputFolder, + }) : super(name, [ timeField, typeField, addrField, dataField, strobeField, - slverrField, + if (intf.includeSlvErr) slverrField, ]); } diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart new file mode 100644 index 00000000..ef2d62ef --- /dev/null +++ b/test/apb_bfm_test.dart @@ -0,0 +1,95 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_bfm_test.dart +// Tests for the APB BFM. +// +// 2023 June 12 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +class ApbBfmTest extends Test { + late final ApbInterface intf; + late final ApbRequester requester; + + final storage = SparseMemoryStorage(addrWidth: 32); + + final int numTransfers; + + ApbBfmTest({this.numTransfers = 10}) : super('apbBfmTest') { + intf = ApbInterface(); + + intf.clk <= SimpleClockGenerator(10).clk; + + requester = ApbRequester(intf: intf, parent: this); + + ApbCompleter(intf: intf, parent: this, storage: storage); + + final monitor = ApbMonitor(intf: intf, parent: this); + final tracker = ApbTracker(intf: intf); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + }); + + monitor.stream.listen(tracker.record); + } + + int _numReadsCompleted = 0; + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('apbBfmTestObj'); + + await _resetFlow(); + + // normal writes + for (var i = 0; i < numTransfers; i++) { + requester.sequencer.add(ApbWritePacket( + addr: LogicValue.ofInt(i, 32), data: LogicValue.ofInt(i, 32))); + } + + // normal reads that check data + for (var i = 0; i < numTransfers; i++) { + final rdPkt = ApbReadPacket(addr: LogicValue.ofInt(i, 32)); + requester.sequencer.add(rdPkt); + + unawaited(rdPkt.completed.then((value) { + // expect(rdPkt.returnedData!.toInt(), i); + _numReadsCompleted++; + })); + } + + obj.drop(); + } + + Future _resetFlow() async { + await waitCycles(intf.clk, 2); + intf.resetN.inject(0); + await waitCycles(intf.clk, 3); + intf.resetN.inject(1); + } + + @override + void check() { + // expect(_numReadsCompleted, numTransfers); + } +} + +//TODO: with strobe +//TODO: check the tracker works + +void main() { + test('simple writes and reads', () async { + Simulator.setMaxSimTime(3000); + await ApbBfmTest().start(); + }); +} From b382ca8eaea9b59e5c4b7c62e37594f9c82feef9 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 17:13:50 -0700 Subject: [PATCH 11/41] got the ready and enable looking better in waves --- lib/src/models/apb_bfm/abp_completer.dart | 25 +++++++++++-------- lib/src/models/apb_bfm/apb_requester.dart | 6 ++--- .../models/apb_bfm/apb_requester_driver.dart | 2 ++ test/apb_bfm_test.dart | 16 +++++++++--- test/apb_test.dart | 6 ++--- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 435bdff3..7e0c7f01 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -14,7 +14,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A model for the completer side of an [ApbInterface]. -class ApbCompleter extends Agent { +class ApbCompleterAgent extends Agent { /// The interface to drive. final ApbInterface intf; @@ -25,7 +25,7 @@ class ApbCompleter extends Agent { /// A place where the completer should save and retrieve data. /// - /// The [ApbCompleter] will reset [storage] whenever the `resetN` signal is + /// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal is /// dropped. final MemoryStorage storage; @@ -34,8 +34,8 @@ class ApbCompleter extends Agent { /// If none is provided, then the delay will always be `0`. final int Function(ApbPacket request)? responseDelay; - /// Creates a new model [ApbCompleter]. - ApbCompleter( + /// Creates a new model [ApbCompleterAgent]. + ApbCompleterAgent( {required this.intf, required this.storage, required Component parent, @@ -52,7 +52,7 @@ class ApbCompleter extends Agent { storage.reset(); }); - intf.ready.put(0); + intf.ready.inject(0); // wait for reset to complete await intf.resetN.nextPosedge; @@ -65,6 +65,7 @@ class ApbCompleter extends Agent { /// Receives one packet (or returns if not selected). Future _receive() async { await intf.enable.nextPosedge; + logger.info('starting to receive!'); if (!intf.sel[selectIndex].value.toBool()) { // we're not selected, wait for the next time @@ -82,11 +83,13 @@ class ApbCompleter extends Agent { packet = ApbReadPacket(addr: intf.addr.value); } - await waitCycles( - intf.clk, - responseDelay != null ? responseDelay!(packet) : 0, - edge: Edge.neg, - ); + if (responseDelay != null) { + await waitCycles( + intf.clk, + responseDelay!(packet), + edge: Edge.neg, + ); + } if (packet is ApbWritePacket) { // store the data @@ -101,7 +104,7 @@ class ApbCompleter extends Agent { } // wait a cycle then end the transfer - await intf.clk.nextNegedge; + await intf.enable.nextNegedge; intf.ready.inject(0); } } diff --git a/lib/src/models/apb_bfm/apb_requester.dart b/lib/src/models/apb_bfm/apb_requester.dart index ef551f99..1bfc0295 100644 --- a/lib/src/models/apb_bfm/apb_requester.dart +++ b/lib/src/models/apb_bfm/apb_requester.dart @@ -13,7 +13,7 @@ import 'package:rohd_vf/rohd_vf.dart'; /// An agent for sending requests on an [ApbInterface]. /// /// Driven read packets will update the returned data into the same packet. -class ApbRequester extends Agent { +class ApbRequesterAgent extends Agent { /// The interface to drive. final ApbInterface intf; @@ -30,8 +30,8 @@ class ApbRequester extends Agent { /// no pending packets to send. final int dropDelayCycles; - /// Constructs a new [ApbRequester]. - ApbRequester({ + /// Constructs a new [ApbRequesterAgent]. + ApbRequesterAgent({ required this.intf, required Component parent, String name = 'apbRequester', diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 20152820..ee525198 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -62,6 +62,8 @@ class ApbRequesterDriver extends PendingClockedDriver { Future _drivePacket(ApbPacket packet) async { // first, SETUP + await intf.clk.nextNegedge; + // if we're not selecting this interface, then we need to select it if (!intf.sel[packet.selectIndex].value.toBool()) { _select(packet.selectIndex); diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index ef2d62ef..1736a4c9 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -14,9 +14,11 @@ import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:rohd_vf/rohd_vf.dart'; import 'package:test/test.dart'; +import 'apb_test.dart'; + class ApbBfmTest extends Test { late final ApbInterface intf; - late final ApbRequester requester; + late final ApbRequesterAgent requester; final storage = SparseMemoryStorage(addrWidth: 32); @@ -27,9 +29,9 @@ class ApbBfmTest extends Test { intf.clk <= SimpleClockGenerator(10).clk; - requester = ApbRequester(intf: intf, parent: this); + requester = ApbRequesterAgent(intf: intf, parent: this); - ApbCompleter(intf: intf, parent: this, storage: storage); + ApbCompleterAgent(intf: intf, parent: this, storage: storage); final monitor = ApbMonitor(intf: intf, parent: this); final tracker = ApbTracker(intf: intf); @@ -90,6 +92,12 @@ class ApbBfmTest extends Test { void main() { test('simple writes and reads', () async { Simulator.setMaxSimTime(3000); - await ApbBfmTest().start(); + final apbBfmTest = ApbBfmTest(); + + final mod = ApbCompleter(apbBfmTest.intf); + await mod.build(); + WaveDumper(mod); + + await apbBfmTest.start(); }); } diff --git a/test/apb_test.dart b/test/apb_test.dart index 4c76939b..2c344c5c 100644 --- a/test/apb_test.dart +++ b/test/apb_test.dart @@ -11,8 +11,8 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; import 'package:test/test.dart'; -class ApbController extends Module { - ApbController(ApbInterface intf) { +class ApbCompleter extends Module { + ApbCompleter(ApbInterface intf) { intf = ApbInterface.clone(intf) ..connectIO(this, intf, inputTags: {ApbDirection.misc, ApbDirection.fromRequester}, @@ -43,7 +43,7 @@ class ApbPair extends Module { apb.clk <= clk; apb.resetN <= ~reset; - ApbController(apb); + ApbCompleter(apb); ApbRequester(apb); } } From 6c8b11316e7132f5b063f59259ce2be9462248ea Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 17:16:50 -0700 Subject: [PATCH 12/41] cleanup, checks, and strb fix --- lib/src/models/apb_bfm/abp_completer.dart | 1 - lib/src/models/apb_bfm/apb_requester_driver.dart | 1 + test/apb_bfm_test.dart | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 7e0c7f01..a88cb389 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -65,7 +65,6 @@ class ApbCompleterAgent extends Agent { /// Receives one packet (or returns if not selected). Future _receive() async { await intf.enable.nextPosedge; - logger.info('starting to receive!'); if (!intf.sel[selectIndex].value.toBool()) { // we're not selected, wait for the next time diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index ee525198..efdbacea 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -80,6 +80,7 @@ class ApbRequesterDriver extends PendingClockedDriver { } else if (packet is ApbReadPacket) { intf.write.put(0); intf.wData.put(0); + intf.strb.put(0); } }); diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 1736a4c9..7d0e5c11 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -65,7 +65,7 @@ class ApbBfmTest extends Test { requester.sequencer.add(rdPkt); unawaited(rdPkt.completed.then((value) { - // expect(rdPkt.returnedData!.toInt(), i); + expect(rdPkt.returnedData!.toInt(), i); _numReadsCompleted++; })); } @@ -82,7 +82,7 @@ class ApbBfmTest extends Test { @override void check() { - // expect(_numReadsCompleted, numTransfers); + expect(_numReadsCompleted, numTransfers); } } From d16d64b23ce148b875a23866d7be162b223423ac Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 17:48:16 -0700 Subject: [PATCH 13/41] timing bug fix, but strobe still broken --- lib/src/models/apb_bfm/abp_completer.dart | 9 ++++ .../models/apb_bfm/apb_requester_driver.dart | 2 +- test/apb_bfm_test.dart | 52 ++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index a88cb389..815b9dfa 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -62,6 +62,14 @@ class ApbCompleterAgent extends Agent { } } + /// Calculates a strobed version of data. + LogicValue _strobeData(LogicValue originalData, LogicValue strobe) => [ + for (var i = 0; i < strobe.width; i++) + strobe[i].toBool() + ? originalData.getRange(i, i + 8) + : LogicValue.filled(8, LogicValue.zero) + ].rswizzle(); + /// Receives one packet (or returns if not selected). Future _receive() async { await intf.enable.nextPosedge; @@ -92,6 +100,7 @@ class ApbCompleterAgent extends Agent { if (packet is ApbWritePacket) { // store the data + // storage.setData(packet.addr, _strobeData(packet.data, packet.strobe)); storage.setData(packet.addr, packet.data); intf.ready.inject(1); } else if (packet is ApbReadPacket) { diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index efdbacea..6c9d55ba 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -52,8 +52,8 @@ class ApbRequesterDriver extends PendingClockedDriver { if (pendingSeqItems.isNotEmpty) { await _drivePacket(pendingSeqItems.removeFirst()); } else { - Simulator.injectAction(_deselectAll); await intf.clk.nextNegedge; + Simulator.injectAction(_deselectAll); } } } diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 7d0e5c11..e8f6423f 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -8,6 +8,7 @@ // Author: Max Korbel import 'dart:async'; +import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -24,7 +25,10 @@ class ApbBfmTest extends Test { final int numTransfers; - ApbBfmTest({this.numTransfers = 10}) : super('apbBfmTest') { + final bool withStrobes; + + ApbBfmTest({this.numTransfers = 10, this.withStrobes = false}) + : super('apbBfmTest') { intf = ApbInterface(); intf.clk <= SimpleClockGenerator(10).clk; @@ -43,7 +47,7 @@ class ApbBfmTest extends Test { monitor.stream.listen(tracker.record); } - int _numReadsCompleted = 0; + int numTransfersCompleted = 0; @override Future run(Phase phase) async { @@ -53,10 +57,27 @@ class ApbBfmTest extends Test { await _resetFlow(); + final rand = Random(123); + + final randomStrobes = List.generate( + numTransfers, (index) => LogicValue.ofInt(rand.nextInt(16), 4)); + + final randomData = List.generate( + numTransfers, (index) => LogicValue.ofInt(rand.nextInt(1 << 32), 32)); + + LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ + for (var i = 0; i < 4; i++) + strobe[i].toBool() + ? originalData.getRange(i, i + 8) + : LogicValue.filled(8, LogicValue.zero) + ].rswizzle(); + // normal writes for (var i = 0; i < numTransfers; i++) { requester.sequencer.add(ApbWritePacket( - addr: LogicValue.ofInt(i, 32), data: LogicValue.ofInt(i, 32))); + addr: LogicValue.ofInt(i, 32), + data: randomData[i], + strobe: withStrobes ? randomStrobes[i] : null)); } // normal reads that check data @@ -65,8 +86,9 @@ class ApbBfmTest extends Test { requester.sequencer.add(rdPkt); unawaited(rdPkt.completed.then((value) { - expect(rdPkt.returnedData!.toInt(), i); - _numReadsCompleted++; + expect(rdPkt.returnedData, randomData[i]); + // strobedData(randomData[i], randomStrobes[i])); + numTransfersCompleted++; })); } @@ -82,14 +104,21 @@ class ApbBfmTest extends Test { @override void check() { - expect(_numReadsCompleted, numTransfers); + expect(numTransfersCompleted, numTransfers); } } //TODO: with strobe //TODO: check the tracker works +//TODO: check the checker +//TODO: make sure there's no extra transactions detected! (or too few!) +//TODO: check delays void main() { + tearDown(() async { + await Simulator.reset(); + }); + test('simple writes and reads', () async { Simulator.setMaxSimTime(3000); final apbBfmTest = ApbBfmTest(); @@ -100,4 +129,15 @@ void main() { await apbBfmTest.start(); }); + + test('writes with strobes', () async { + Simulator.setMaxSimTime(3000); + final apbBfmTest = ApbBfmTest(numTransfers: 20, withStrobes: true); + + final mod = ApbCompleter(apbBfmTest.intf); + await mod.build(); + WaveDumper(mod); + + await apbBfmTest.start(); + }); } From 1b99d4f9f6471b1c977e3324afec24c850433083 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 12 Jun 2023 19:52:53 -0700 Subject: [PATCH 14/41] drop enable when empty --- lib/src/models/apb_bfm/apb_requester_driver.dart | 5 ++++- test/apb_bfm_test.dart | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 6c9d55ba..bff5901a 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -53,7 +53,10 @@ class ApbRequesterDriver extends PendingClockedDriver { await _drivePacket(pendingSeqItems.removeFirst()); } else { await intf.clk.nextNegedge; - Simulator.injectAction(_deselectAll); + Simulator.injectAction(() { + _deselectAll(); + intf.enable.put(0); + }); } } } diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index e8f6423f..84a4a77b 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -113,6 +113,7 @@ class ApbBfmTest extends Test { //TODO: check the checker //TODO: make sure there's no extra transactions detected! (or too few!) //TODO: check delays +//TODO: strobes need to apply to the same addr multiple times void main() { tearDown(() async { From 5a1aebe904e61f8e1c5fe12201aa474515f07424 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 08:53:45 -0700 Subject: [PATCH 15/41] got strobe working sorta --- lib/src/models/apb_bfm/abp_completer.dart | 21 +++++++++++++++------ lib/src/models/memory_model.dart | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 815b9dfa..4822d8ce 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -63,11 +63,12 @@ class ApbCompleterAgent extends Agent { } /// Calculates a strobed version of data. - LogicValue _strobeData(LogicValue originalData, LogicValue strobe) => [ + LogicValue _strobeData( + LogicValue originalData, LogicValue newData, LogicValue strobe) => + [ for (var i = 0; i < strobe.width; i++) - strobe[i].toBool() - ? originalData.getRange(i, i + 8) - : LogicValue.filled(8, LogicValue.zero) + (strobe[i].toBool() ? newData : originalData) + .getRange(i * 8, i * 8 + 8) ].rswizzle(); /// Receives one packet (or returns if not selected). @@ -100,8 +101,16 @@ class ApbCompleterAgent extends Agent { if (packet is ApbWritePacket) { // store the data - // storage.setData(packet.addr, _strobeData(packet.data, packet.strobe)); - storage.setData(packet.addr, packet.data); + storage.setData( + packet.addr, + _strobeData( + storage.getData(packet.addr) ?? + LogicValue.filled(intf.dataWidth, LogicValue.x), //TODO + packet.data, + packet.strobe, + ), + ); + // storage.setData(packet.addr, packet.data); intf.ready.inject(1); } else if (packet is ApbReadPacket) { // capture the data diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index 1020ed7d..95f9c768 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -10,6 +10,8 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; +//TODO: move the X, addr adjust, etc. over to storage + /// A model of a [Memory] which uses a software-based [SparseMemoryStorage] to /// store data. /// From efec562d7e2180e5c282331a4347bb4fe0a2c6d0 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 09:35:30 -0700 Subject: [PATCH 16/41] made error handling utilities part of storage for reusability --- lib/src/memory/memory.dart | 2 +- lib/src/models/memory_model.dart | 95 ++++++----------------- lib/src/models/sparse_memory_storage.dart | 87 ++++++++++++++++++++- test/memory_test.dart | 18 ++--- test/sparse_memory_storage_test.dart | 3 +- 5 files changed, 119 insertions(+), 86 deletions(-) diff --git a/lib/src/memory/memory.dart b/lib/src/memory/memory.dart index ba13656c..3136760c 100644 --- a/lib/src/memory/memory.dart +++ b/lib/src/memory/memory.dart @@ -120,7 +120,7 @@ abstract class Memory extends Module { List readPorts, {super.name = 'memory'}) : assert(writePorts.isNotEmpty && readPorts.isNotEmpty, - 'Must specify at least one read port and one write port.'), + 'Must specify at least one read port or one write port.'), numWrites = writePorts.length, numReads = readPorts.length, dataWidth = (writePorts.isNotEmpty) diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index 1020ed7d..9a888ddd 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -17,7 +17,7 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// only a model. class MemoryModel extends Memory { /// The memory storage underlying this model. - late final MemoryStorage storage = SparseMemoryStorage(addrWidth: addrWidth); + late final MemoryStorage storage; /// A pre-signal before the output flops of this memory. late final List _rdDataPre; @@ -25,47 +25,12 @@ class MemoryModel extends Memory { /// If true, a positive edge on reset will reset the memory asynchronously. final bool asyncReset; - /// A function called if an invalid write is made when [storage] is not empty. - /// - /// An invalid write will reset the entire memory after calling this function. - /// - /// By default, this will print a warning message. - final void Function() onInvalidWrite; - - /// Default behavior for [onInvalidWrite]. - static void _defaultOnInvalidWrite() { - // ignore: avoid_print - print('WARNING: Memory was cleared by invalid write!'); - } - - /// A function called if a read is made to an address that has no data in - /// [storage]. - /// - /// By default, this will print a warning message and return `x`. - /// - /// This is *not* called when a read's valid or address has invalid bits; in - /// those cases the memory will return `x` always. - final LogicValue Function(LogicValue addr, int dataWidth) onInvalidRead; - - /// Default behavior for [onInvalidRead]. - static LogicValue _defaultOnInvalidRead(LogicValue addr, int dataWidth) { - // ignore: avoid_print - print('WARNING: reading from address $addr that has no data!'); - return LogicValue.filled(dataWidth, LogicValue.x); - } - - /// A function to align addresses when used for transactions. - /// - /// By default, this will align (mask) addresses to a multiple of 4. - final LogicValue Function(LogicValue addr) alignAddress; - - /// Default behavior for [alignAddress]. - static LogicValue _defaultAlignAddress(LogicValue addr) => addr - (addr % 4); - @override final int readLatency; /// Creates a new [MemoryModel]. + /// + /// If no [storage] is provided, a default storage will be created. MemoryModel( super.clk, super.reset, @@ -73,10 +38,11 @@ class MemoryModel extends Memory { super.readPorts, { this.readLatency = 1, this.asyncReset = true, - this.onInvalidWrite = _defaultOnInvalidWrite, - this.onInvalidRead = _defaultOnInvalidRead, - this.alignAddress = _defaultAlignAddress, + MemoryStorage? storage, }) { + this.storage = storage ?? + SparseMemoryStorage(addrWidth: addrWidth, dataWidth: dataWidth); + _buildLogic(); } @@ -97,34 +63,29 @@ class MemoryModel extends Memory { return; } for (final wrPort in wrPorts) { - if (!wrPort.en.value.isValid || - (wrPort.en.value == LogicValue.one && !wrPort.addr.value.isValid)) { - if (!storage.isEmpty) { - onInvalidWrite(); - } - - storage.reset(); - + if (!wrPort.en.value.isValid && !storage.isEmpty) { + // storage doesnt have access to `en`, so check ourselves + storage.invalidWrite(); return; } - if (wrPort.en.value == LogicValue.one) { - var addrValue = wrPort.addr.value; - addrValue = alignAddress(addrValue); + if (wrPort.en.value == LogicValue.one) { + final addrValue = wrPort.addr.value; if (wrPort is MaskedDataPortInterface) { - storage.setData( - addrValue, - List.generate( - dataWidth ~/ 8, - (index) => wrPort.mask.value[index].toBool() + storage.writeData( + addrValue, + [ + for (var index = 0; index < dataWidth ~/ 8; index++) + wrPort.mask.value[index].toBool() ? wrPort.data.value.getRange(index * 8, (index + 1) * 8) - : (storage.getData(addrValue) ?? - onInvalidRead(addrValue, dataWidth)) - .getRange(index * 8, (index + 1) * 8), - ).rswizzle()); + : storage + .readData(addrValue) + .getRange(index * 8, (index + 1) * 8) + ].rswizzle(), + ); } else { - storage.setData(addrValue, wrPort.data.value); + storage.writeData(addrValue, wrPort.data.value); } } } @@ -155,15 +116,7 @@ class MemoryModel extends Memory { } if (rdPort.en.value == LogicValue.one) { - var addrValue = rdPort.addr.value; - - addrValue = alignAddress(addrValue); - - if (storage.getData(addrValue) == null) { - rdPortPre.put(onInvalidRead(addrValue, dataWidth)); - } else { - rdPortPre.put(storage.getData(addrValue)); - } + rdPortPre.put(storage.readData(rdPort.addr.value)); } } } diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index 4490bea5..28123f13 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -14,8 +14,53 @@ abstract class MemoryStorage { /// The width of addresses. final int addrWidth; - /// Constrcuts a [MemoryStorage] with specified [addrWidth]. - MemoryStorage({required this.addrWidth}); + /// The width of data. + final int dataWidth; + + /// A function called if an invalid write is made when not [isEmpty]. + /// + /// An invalid write will reset the entire memory after calling this function. + /// + /// By default, this will print a warning message. + final void Function() onInvalidWrite; + + /// Default behavior for [onInvalidWrite]. + static void _defaultOnInvalidWrite() { + // ignore: avoid_print + print('WARNING: Memory was cleared by invalid write!'); + } + + /// A function called if a read is made to an address that has no data. + /// + /// By default, this will print a warning message and return `x`. + /// + /// This is *not* called when a read's valid or address has invalid bits; in + /// those cases the memory will return `x` always. + final LogicValue Function(LogicValue addr, int dataWidth) onInvalidRead; + + /// Default behavior for [onInvalidRead]. + static LogicValue _defaultOnInvalidRead(LogicValue addr, int dataWidth) { + // ignore: avoid_print + print('WARNING: reading from address $addr that has no data!'); + return LogicValue.filled(dataWidth, LogicValue.x); + } + + /// A function to align addresses when used for transactions. + /// + /// By default, this will align (mask) addresses to a multiple of 4. + final LogicValue Function(LogicValue addr) alignAddress; + + /// Default behavior for [alignAddress]. + static LogicValue _defaultAlignAddress(LogicValue addr) => addr - (addr % 4); + + /// Constrcuts a [MemoryStorage] with specified [addrWidth] and [dataWidth]. + MemoryStorage({ + required this.addrWidth, + required this.dataWidth, + this.onInvalidWrite = _defaultOnInvalidWrite, + this.onInvalidRead = _defaultOnInvalidRead, + this.alignAddress = _defaultAlignAddress, + }); /// Reads a verilog-compliant hex file and preloads memory with it. /// @@ -107,9 +152,37 @@ abstract class MemoryStorage { /// Resets all memory to initial state. void reset(); - /// Loads [data] into [addr]. + /// Triggers behavior associated with an invalid write, including calling + /// [onInvalidWrite] and [reset]ting all of memory. + void invalidWrite() { + onInvalidWrite(); + reset(); + } + + /// Performs some validation on a write, aligns the address with + /// [alignAddress], and then calls [setData]. + void writeData(LogicValue addr, LogicValue data) { + if (!addr.isValid) { + if (!isEmpty) { + invalidWrite(); + } + + return; + } + + setData(alignAddress(addr), data); + } + + /// Loads [data] into [addr] directly into storage. void setData(LogicValue addr, LogicValue data); + /// Aligns the address with [alignAddress], then returns either the [getData] + /// result in storage or else [onInvalidRead]'s result. + LogicValue readData(LogicValue addr) { + final alignedAddr = alignAddress(addr); + return getData(alignedAddr) ?? onInvalidRead(alignedAddr, dataWidth); + } + /// Returns the data at [addr], or `null` if it is not present. LogicValue? getData(LogicValue addr); @@ -123,7 +196,13 @@ class SparseMemoryStorage extends MemoryStorage { /// Constructs a new sparse memory storage with specified [addrWidth] for all /// addresses. - SparseMemoryStorage({required super.addrWidth}); + SparseMemoryStorage({ + required super.addrWidth, + required super.dataWidth, + super.alignAddress, + super.onInvalidRead, + super.onInvalidWrite, + }); @override void setData(LogicValue addr, LogicValue data) { diff --git a/test/memory_test.dart b/test/memory_test.dart index a71f5787..65123bbf 100644 --- a/test/memory_test.dart +++ b/test/memory_test.dart @@ -24,6 +24,8 @@ void main() { group('memory accesses', () { const numEntries = 20; + const dataWidth = 32; + const addrWidth = 5; final memoriesToTestGenerators = { 'rf': (Logic clk, Logic reset, List wrPorts, @@ -36,9 +38,13 @@ void main() { reset, wrPorts, rdPorts, - alignAddress: (addr) => addr, - onInvalidRead: (addr, dataWidth) => - LogicValue.filled(dataWidth, LogicValue.zero), + storage: SparseMemoryStorage( + addrWidth: addrWidth, + dataWidth: dataWidth, + alignAddress: (addr) => addr, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ), ) }; @@ -53,9 +59,6 @@ void main() { final memGenFunc = memGen.value; test('$memGenName simple', () async { - const dataWidth = 32; - const addrWidth = 5; - const numWr = 3; const numRd = 3; @@ -111,9 +114,6 @@ void main() { }); test('$memGenName wr masked', () async { - const dataWidth = 32; - const addrWidth = 5; - const numWr = 1; const numRd = 1; diff --git a/test/sparse_memory_storage_test.dart b/test/sparse_memory_storage_test.dart index c4c11e0a..f7f1edc5 100644 --- a/test/sparse_memory_storage_test.dart +++ b/test/sparse_memory_storage_test.dart @@ -16,7 +16,8 @@ import 'package:test/test.dart'; void main() { test('sparse memory storage can load a simple file', () { final hex = File('test/example1.hex').readAsStringSync(); - final storage = SparseMemoryStorage(addrWidth: 32)..loadMemHex(hex); + final storage = SparseMemoryStorage(addrWidth: 32, dataWidth: 32) + ..loadMemHex(hex); expect(storage.getData(LogicValue.ofInt(0x8000000c, 32))!.toInt(), equals(0x1ff50513)); From 93dda4631fc1d6b7f662083a5961454df187c170 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 09:40:37 -0700 Subject: [PATCH 17/41] some progress --- lib/src/models/apb_bfm/abp_completer.dart | 8 +++----- test/apb_bfm_test.dart | 15 +++++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 4822d8ce..6dcdd6ed 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -101,21 +101,19 @@ class ApbCompleterAgent extends Agent { if (packet is ApbWritePacket) { // store the data - storage.setData( + storage.writeData( packet.addr, _strobeData( - storage.getData(packet.addr) ?? - LogicValue.filled(intf.dataWidth, LogicValue.x), //TODO + storage.readData(packet.addr), packet.data, packet.strobe, ), ); - // storage.setData(packet.addr, packet.data); intf.ready.inject(1); } else if (packet is ApbReadPacket) { // capture the data Simulator.injectAction(() { - intf.rData.put(storage.getData(packet.addr)); + intf.rData.put(storage.readData(packet.addr)); intf.ready.put(1); }); } diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 84a4a77b..b1f07aff 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -21,7 +21,12 @@ class ApbBfmTest extends Test { late final ApbInterface intf; late final ApbRequesterAgent requester; - final storage = SparseMemoryStorage(addrWidth: 32); + final storage = SparseMemoryStorage( + addrWidth: 32, + dataWidth: 32, + onInvalidRead: (addr, dataWidth) => + LogicValue.filled(dataWidth, LogicValue.zero), + ); final int numTransfers; @@ -68,7 +73,7 @@ class ApbBfmTest extends Test { LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ for (var i = 0; i < 4; i++) strobe[i].toBool() - ? originalData.getRange(i, i + 8) + ? originalData.getRange(i * 8, i * 8 + 8) : LogicValue.filled(8, LogicValue.zero) ].rswizzle(); @@ -86,8 +91,10 @@ class ApbBfmTest extends Test { requester.sequencer.add(rdPkt); unawaited(rdPkt.completed.then((value) { - expect(rdPkt.returnedData, randomData[i]); - // strobedData(randomData[i], randomStrobes[i])); + expect( + rdPkt.returnedData, + strobedData(randomData[i], randomStrobes[i]), + ); numTransfersCompleted++; })); } From ea2f2994e7a632e65f793d52e18ac72e8febccea Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 09:43:10 -0700 Subject: [PATCH 18/41] make default not align addresses --- lib/src/models/sparse_memory_storage.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index 28123f13..1397d719 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -47,11 +47,16 @@ abstract class MemoryStorage { /// A function to align addresses when used for transactions. /// - /// By default, this will align (mask) addresses to a multiple of 4. + /// By default, this will perform no modification to the address. + /// + /// As an example, to align (mask) addresses to multiples of 4: + /// ```dart + /// (addr) => addr - (addr % 4) + /// ``` final LogicValue Function(LogicValue addr) alignAddress; /// Default behavior for [alignAddress]. - static LogicValue _defaultAlignAddress(LogicValue addr) => addr - (addr % 4); + static LogicValue _defaultAlignAddress(LogicValue addr) => addr; /// Constrcuts a [MemoryStorage] with specified [addrWidth] and [dataWidth]. MemoryStorage({ From e708485e213ae888c6a0239ada43a68639803758 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 09:57:26 -0700 Subject: [PATCH 19/41] more tests with delays --- test/apb_bfm_test.dart | 52 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index b1f07aff..9f6274a6 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -32,8 +32,13 @@ class ApbBfmTest extends Test { final bool withStrobes; - ApbBfmTest({this.numTransfers = 10, this.withStrobes = false}) - : super('apbBfmTest') { + final int interTxnDelay; + + ApbBfmTest({ + this.numTransfers = 10, + this.withStrobes = false, + this.interTxnDelay = 0, + }) : super('apbBfmTest') { intf = ApbInterface(); intf.clk <= SimpleClockGenerator(10).clk; @@ -83,6 +88,7 @@ class ApbBfmTest extends Test { addr: LogicValue.ofInt(i, 32), data: randomData[i], strobe: withStrobes ? randomStrobes[i] : null)); + await waitCycles(intf.clk, interTxnDelay); } // normal reads that check data @@ -93,10 +99,14 @@ class ApbBfmTest extends Test { unawaited(rdPkt.completed.then((value) { expect( rdPkt.returnedData, - strobedData(randomData[i], randomStrobes[i]), + withStrobes + ? strobedData(randomData[i], randomStrobes[i]) + : randomData[i], ); numTransfersCompleted++; })); + + await waitCycles(intf.clk, interTxnDelay); } obj.drop(); @@ -127,25 +137,39 @@ void main() { await Simulator.reset(); }); - test('simple writes and reads', () async { + Future runTest(ApbBfmTest apbBfmTest, {bool dumpWaves = false}) async { Simulator.setMaxSimTime(3000); - final apbBfmTest = ApbBfmTest(); - final mod = ApbCompleter(apbBfmTest.intf); - await mod.build(); - WaveDumper(mod); + if (dumpWaves) { + final mod = ApbCompleter(apbBfmTest.intf); + await mod.build(); + WaveDumper(mod); + } await apbBfmTest.start(); + } + + test('simple writes and reads', () async { + await runTest(ApbBfmTest()); }); test('writes with strobes', () async { - Simulator.setMaxSimTime(3000); - final apbBfmTest = ApbBfmTest(numTransfers: 20, withStrobes: true); + await runTest(ApbBfmTest(numTransfers: 20, withStrobes: true)); + }); - final mod = ApbCompleter(apbBfmTest.intf); - await mod.build(); - WaveDumper(mod); + test('writes and reads with 1 cycle delays', () async { + await runTest(ApbBfmTest(interTxnDelay: 1)); + }); - await apbBfmTest.start(); + test('writes and reads with 2 cycle delays', () async { + await runTest(ApbBfmTest(interTxnDelay: 2)); + }); + + test('writes and reads with 3 cycle delays', () async { + await runTest(ApbBfmTest(interTxnDelay: 3)); + }); + + test('writes and reads with big delays', () async { + await runTest(ApbBfmTest(interTxnDelay: 5)); }); } From 228184ffbb6efdd1a64f702b1746125faa506488 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 13:23:19 -0700 Subject: [PATCH 20/41] test tracker --- .../models/apb_bfm/apb_requester_driver.dart | 1 + test/apb_bfm_test.dart | 44 +++++++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index bff5901a..4d729f41 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -24,6 +24,7 @@ class ApbRequesterDriver extends PendingClockedDriver { //TODO: wakeup //TODO: protection //TODO: user + //TODO: retry on error? no, not in spec /// Creates a new [ApbRequesterDriver]. ApbRequesterDriver({ diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 9f6274a6..28eb72f9 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -8,6 +8,8 @@ // Author: Max Korbel import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:rohd/rohd.dart'; @@ -34,11 +36,14 @@ class ApbBfmTest extends Test { final int interTxnDelay; - ApbBfmTest({ + String get outFolder => 'tmp_test/apbbfm/$name/'; + + ApbBfmTest( + super.name, { this.numTransfers = 10, this.withStrobes = false, this.interTxnDelay = 0, - }) : super('apbBfmTest') { + }) { intf = ApbInterface(); intf.clk <= SimpleClockGenerator(10).clk; @@ -48,10 +53,25 @@ class ApbBfmTest extends Test { ApbCompleterAgent(intf: intf, parent: this, storage: storage); final monitor = ApbMonitor(intf: intf, parent: this); - final tracker = ApbTracker(intf: intf); + + Directory(outFolder).createSync(recursive: true); + + final tracker = ApbTracker( + intf: intf, + dumpTable: false, + outputFolder: outFolder, + ); Simulator.registerEndOfSimulationAction(() async { await tracker.terminate(); + + final jsonStr = + File('$outFolder/apbTracker.tracker.json').readAsStringSync(); + final jsonContents = json.decode(jsonStr); + // ignore: avoid_dynamic_calls + expect(jsonContents['records'].length, 2 * numTransfers); + + Directory(outFolder).deleteSync(recursive: true); }); monitor.stream.listen(tracker.record); @@ -125,12 +145,8 @@ class ApbBfmTest extends Test { } } -//TODO: with strobe -//TODO: check the tracker works //TODO: check the checker -//TODO: make sure there's no extra transactions detected! (or too few!) -//TODO: check delays -//TODO: strobes need to apply to the same addr multiple times +//TODO: check response delays void main() { tearDown(() async { @@ -150,26 +166,26 @@ void main() { } test('simple writes and reads', () async { - await runTest(ApbBfmTest()); + await runTest(ApbBfmTest('simple')); }); test('writes with strobes', () async { - await runTest(ApbBfmTest(numTransfers: 20, withStrobes: true)); + await runTest(ApbBfmTest('strobes', numTransfers: 20, withStrobes: true)); }); test('writes and reads with 1 cycle delays', () async { - await runTest(ApbBfmTest(interTxnDelay: 1)); + await runTest(ApbBfmTest('delay1', interTxnDelay: 1)); }); test('writes and reads with 2 cycle delays', () async { - await runTest(ApbBfmTest(interTxnDelay: 2)); + await runTest(ApbBfmTest('delay2', interTxnDelay: 2)); }); test('writes and reads with 3 cycle delays', () async { - await runTest(ApbBfmTest(interTxnDelay: 3)); + await runTest(ApbBfmTest('delay3', interTxnDelay: 3)); }); test('writes and reads with big delays', () async { - await runTest(ApbBfmTest(interTxnDelay: 5)); + await runTest(ApbBfmTest('delay5', interTxnDelay: 5)); }); } From f91acf3f0602c9d032f63729eb3b712d0201f9bd Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 13:43:24 -0700 Subject: [PATCH 21/41] test response delays --- lib/src/models/apb_bfm/abp_completer.dart | 13 +++++--- lib/src/models/apb_bfm/apb_monitor.dart | 4 ++- lib/src/models/apb_bfm/apb_packet.dart | 2 -- test/apb_bfm_test.dart | 40 ++++++++++++++++++----- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 6dcdd6ed..1a2d834a 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -92,11 +92,14 @@ class ApbCompleterAgent extends Agent { } if (responseDelay != null) { - await waitCycles( - intf.clk, - responseDelay!(packet), - edge: Edge.neg, - ); + final delayCycles = responseDelay!(packet); + if (delayCycles > 0) { + await waitCycles( + intf.clk, + delayCycles, + edge: Edge.neg, + ); + } } if (packet is ApbWritePacket) { diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart index 2a7a275c..cff81ffc 100644 --- a/lib/src/models/apb_bfm/apb_monitor.dart +++ b/lib/src/models/apb_bfm/apb_monitor.dart @@ -32,7 +32,9 @@ class ApbMonitor extends Monitor { intf.clk.posedge.listen((event) { for (var i = 0; i < intf.numSelects; i++) { - if (intf.sel[i].value.toBool() && intf.enable.value.toBool()) { + if (intf.sel[i].value.toBool() && + intf.enable.value.toBool() && + intf.ready.value.toBool()) { if (intf.write.value.toBool()) { add(ApbWritePacket( addr: intf.addr.value, diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index af18a062..bfaa41cb 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -11,8 +11,6 @@ import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/interfaces/interfaces.dart'; -import 'package:rohd_hcl/src/models/apb_bfm/apb_tracker.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A packet on an [ApbInterface]. diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 28eb72f9..16bd0ce8 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -36,6 +36,8 @@ class ApbBfmTest extends Test { final int interTxnDelay; + final bool withRandomRspDelays; + String get outFolder => 'tmp_test/apbbfm/$name/'; ApbBfmTest( @@ -43,14 +45,21 @@ class ApbBfmTest extends Test { this.numTransfers = 10, this.withStrobes = false, this.interTxnDelay = 0, - }) { + this.withRandomRspDelays = false, + }) : super(randomSeed: 123) { intf = ApbInterface(); intf.clk <= SimpleClockGenerator(10).clk; requester = ApbRequesterAgent(intf: intf, parent: this); - ApbCompleterAgent(intf: intf, parent: this, storage: storage); + ApbCompleterAgent( + intf: intf, + parent: this, + storage: storage, + responseDelay: + withRandomRspDelays ? (request) => Test.random.nextInt(5) : null, + ); final monitor = ApbMonitor(intf: intf, parent: this); @@ -87,13 +96,11 @@ class ApbBfmTest extends Test { await _resetFlow(); - final rand = Random(123); - final randomStrobes = List.generate( - numTransfers, (index) => LogicValue.ofInt(rand.nextInt(16), 4)); + numTransfers, (index) => LogicValue.ofInt(Test.random.nextInt(16), 4)); - final randomData = List.generate( - numTransfers, (index) => LogicValue.ofInt(rand.nextInt(1 << 32), 32)); + final randomData = List.generate(numTransfers, + (index) => LogicValue.ofInt(Test.random.nextInt(1 << 32), 32)); LogicValue strobedData(LogicValue originalData, LogicValue strobe) => [ for (var i = 0; i < 4; i++) @@ -146,7 +153,6 @@ class ApbBfmTest extends Test { } //TODO: check the checker -//TODO: check response delays void main() { tearDown(() async { @@ -188,4 +194,22 @@ void main() { test('writes and reads with big delays', () async { await runTest(ApbBfmTest('delay5', interTxnDelay: 5)); }); + + test('random response delays', () async { + await runTest(ApbBfmTest( + 'randrsp', + numTransfers: 20, + withRandomRspDelays: true, + )); + }); + + test('random everything', () async { + await runTest(ApbBfmTest( + 'randeverything', + numTransfers: 20, + withRandomRspDelays: true, + withStrobes: true, + interTxnDelay: 3, + )); + }); } From 378a39483d209a8be39464812167537e41f920ae Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 14:33:58 -0700 Subject: [PATCH 22/41] got errors working --- lib/src/interfaces/apb.dart | 2 +- lib/src/models/apb_bfm/abp_completer.dart | 74 +++++++++++++++---- .../apb_bfm/apb_compliance_checker.dart | 3 +- lib/src/models/apb_bfm/apb_monitor.dart | 14 ++-- lib/src/models/apb_bfm/apb_packet.dart | 39 ++++++---- .../models/apb_bfm/apb_requester_driver.dart | 6 +- lib/src/models/sparse_memory_storage.dart | 10 ++- test/apb_bfm_test.dart | 41 ++++++++-- 8 files changed, 138 insertions(+), 51 deletions(-) diff --git a/lib/src/interfaces/apb.dart b/lib/src/interfaces/apb.dart index f7bf4086..45285432 100644 --- a/lib/src/interfaces/apb.dart +++ b/lib/src/interfaces/apb.dart @@ -230,7 +230,7 @@ class ApbInterface extends Interface { setPorts([ Port('PREADY'), Port('PRDATA', dataWidth), - Port('PSLVERR'), + if (includeSlvErr) Port('PSLVERR'), Port('PWAKEUP'), if (userDataWidth != 0) Port('PRUSER', userDataWidth), if (userRespWidth != 0) Port('PBUSER', userRespWidth), diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 1a2d834a..d9754ab7 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -18,8 +18,6 @@ class ApbCompleterAgent extends Agent { /// The interface to drive. final ApbInterface intf; - //TODO: slverr - /// The index that this is listening to on the [intf]. final int selectIndex; @@ -34,6 +32,19 @@ class ApbCompleterAgent extends Agent { /// If none is provided, then the delay will always be `0`. final int Function(ApbPacket request)? responseDelay; + /// A function that determines whether a response for a request should contain + /// an error (`slvErr`). + /// + /// If none is provided, it will always respond with no error. + final bool Function(ApbPacket request)? respondWithError; + + /// If true, then returned data on an error will be `x`. + final bool invalidReadDataOnError; + + /// If true, then writes that respond with an error will not store into the + /// [storage]. + final bool dropWriteDataOnError; + /// Creates a new model [ApbCompleterAgent]. ApbCompleterAgent( {required this.intf, @@ -41,6 +52,9 @@ class ApbCompleterAgent extends Agent { required Component parent, this.selectIndex = 0, this.responseDelay, + this.respondWithError, + this.invalidReadDataOnError = true, + this.dropWriteDataOnError = true, String name = 'apbCompleter'}) : super(name, parent); @@ -52,7 +66,7 @@ class ApbCompleterAgent extends Agent { storage.reset(); }); - intf.ready.inject(0); + _respond(ready: false); // wait for reset to complete await intf.resetN.nextPosedge; @@ -103,26 +117,54 @@ class ApbCompleterAgent extends Agent { } if (packet is ApbWritePacket) { + final writeError = respondWithError != null && respondWithError!(packet); + // store the data - storage.writeData( - packet.addr, - _strobeData( - storage.readData(packet.addr), - packet.data, - packet.strobe, - ), + if (!(writeError && dropWriteDataOnError)) { + storage.writeData( + packet.addr, + _strobeData( + storage.readData(packet.addr), + packet.data, + packet.strobe, + ), + ); + } + + _respond( + ready: true, + error: writeError, ); - intf.ready.inject(1); } else if (packet is ApbReadPacket) { // capture the data - Simulator.injectAction(() { - intf.rData.put(storage.readData(packet.addr)); - intf.ready.put(1); - }); + _respond( + ready: true, + data: storage.readData(packet.addr), + error: respondWithError != null && respondWithError!(packet), + ); } // wait a cycle then end the transfer await intf.enable.nextNegedge; - intf.ready.inject(0); + _respond(ready: false); + } + + /// Sets up response signals for the completer (including using inject). + void _respond({required bool ready, bool? error, LogicValue? data}) { + Simulator.injectAction(() { + intf.ready.put(ready); + + if (error == null) { + intf.slvErr?.put(LogicValue.x); + } else { + intf.slvErr?.put(error); + } + + if (data == null || ((error ?? false) && invalidReadDataOnError)) { + intf.rData.put(LogicValue.x); + } else { + intf.rData.put(data); + } + }); } } diff --git a/lib/src/models/apb_bfm/apb_compliance_checker.dart b/lib/src/models/apb_bfm/apb_compliance_checker.dart index 578512b2..6e092b89 100644 --- a/lib/src/models/apb_bfm/apb_compliance_checker.dart +++ b/lib/src/models/apb_bfm/apb_compliance_checker.dart @@ -2,4 +2,5 @@ // Rules: // - strobe must not be "active" during read transfer (all low during read) // - the FSM is followed -// - addr, write, and wdata are valid when psel is asserted \ No newline at end of file +// - addr, write, and wdata are valid when psel is asserted +// - slverr is not X during a transfer \ No newline at end of file diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart index cff81ffc..f0caf524 100644 --- a/lib/src/models/apb_bfm/apb_monitor.dart +++ b/lib/src/models/apb_bfm/apb_monitor.dart @@ -36,12 +36,14 @@ class ApbMonitor extends Monitor { intf.enable.value.toBool() && intf.ready.value.toBool()) { if (intf.write.value.toBool()) { - add(ApbWritePacket( - addr: intf.addr.value, - data: intf.wData.value, - strobe: intf.strb.value, - selectIndex: i, - )); + add( + ApbWritePacket( + addr: intf.addr.value, + data: intf.wData.value, + strobe: intf.strb.value, + selectIndex: i, + )..complete(slvErr: intf.slvErr?.value), + ); } else { add( ApbReadPacket( diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index bfaa41cb..2d82e53d 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -24,6 +24,24 @@ abstract class ApbPacket extends SequenceItem implements Trackable { /// Creates a new packet. ApbPacket({required this.addr, this.selectIndex = 0}); + /// A [Future] that completes once the the read has been completed. + Future get completed => _completer.future; + final Completer _completer = Completer(); + + /// Error indication returned by the read. + LogicValue? get returnedSlvErr => _returnedSlvErr; + LogicValue? _returnedSlvErr; + + /// Called by a completer when a transfer is completed. + void complete({LogicValue? slvErr}) { + if (_returnedSlvErr != null) { + throw RohdHclException('Packet is already completed.'); + } + + _returnedSlvErr = slvErr; + _completer.complete(); + } + @override String? trackerString(TrackerField field) { switch (field) { @@ -78,27 +96,20 @@ class ApbReadPacket extends ApbPacket { LogicValue? get returnedData => _returnedData; LogicValue? _returnedData; - /// Error indication returned by the read. - LogicValue? get returnedSlvErr => _returnedSlvErr; - LogicValue? _returnedSlvErr; - - /// A [Future] that completes once the the read has been completed. - Future get completed => _completer.future; - final Completer _completer = Completer(); + /// Creates a read packet. + ApbReadPacket({required super.addr, super.selectIndex}); /// Called by a completer when a transfer is completed. - void complete({required LogicValue data, LogicValue? slvErr}) { - if (_returnedData != null || _returnedSlvErr != null) { + @override + void complete({LogicValue? slvErr, LogicValue? data}) { + if (_returnedData != null) { throw RohdHclException('Packet is already completed.'); } _returnedData = data; - _returnedSlvErr = slvErr; - _completer.complete(); - } - /// Creates a read packet. - ApbReadPacket({required super.addr, super.selectIndex}); + super.complete(slvErr: slvErr); + } @override String? trackerString(TrackerField field) { diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 4d729f41..07c1ea61 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -98,7 +98,11 @@ class ApbRequesterDriver extends PendingClockedDriver { await intf.ready.nextPosedge; } - if (packet is ApbReadPacket) { + if (packet is ApbWritePacket) { + packet.complete( + slvErr: intf.slvErr?.value, + ); + } else if (packet is ApbReadPacket) { packet.complete( data: intf.rData.value, slvErr: intf.slvErr?.value, diff --git a/lib/src/models/sparse_memory_storage.dart b/lib/src/models/sparse_memory_storage.dart index 1397d719..6aced455 100644 --- a/lib/src/models/sparse_memory_storage.dart +++ b/lib/src/models/sparse_memory_storage.dart @@ -62,10 +62,12 @@ abstract class MemoryStorage { MemoryStorage({ required this.addrWidth, required this.dataWidth, - this.onInvalidWrite = _defaultOnInvalidWrite, - this.onInvalidRead = _defaultOnInvalidRead, - this.alignAddress = _defaultAlignAddress, - }); + void Function()? onInvalidWrite = _defaultOnInvalidWrite, + LogicValue Function(LogicValue addr, int dataWidth)? onInvalidRead, + LogicValue Function(LogicValue addr)? alignAddress, + }) : onInvalidWrite = onInvalidWrite ?? _defaultOnInvalidWrite, + onInvalidRead = onInvalidRead ?? _defaultOnInvalidRead, + alignAddress = alignAddress ?? _defaultAlignAddress; /// Reads a verilog-compliant hex file and preloads memory with it. /// diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 16bd0ce8..bf8e6804 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -10,7 +10,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; @@ -38,6 +37,8 @@ class ApbBfmTest extends Test { final bool withRandomRspDelays; + final bool withErrors; + String get outFolder => 'tmp_test/apbbfm/$name/'; ApbBfmTest( @@ -46,8 +47,9 @@ class ApbBfmTest extends Test { this.withStrobes = false, this.interTxnDelay = 0, this.withRandomRspDelays = false, + this.withErrors = false, }) : super(randomSeed: 123) { - intf = ApbInterface(); + intf = ApbInterface(includeSlvErr: true); intf.clk <= SimpleClockGenerator(10).clk; @@ -59,6 +61,7 @@ class ApbBfmTest extends Test { storage: storage, responseDelay: withRandomRspDelays ? (request) => Test.random.nextInt(5) : null, + respondWithError: withErrors ? (request) => true : null, ); final monitor = ApbMonitor(intf: intf, parent: this); @@ -111,10 +114,19 @@ class ApbBfmTest extends Test { // normal writes for (var i = 0; i < numTransfers; i++) { - requester.sequencer.add(ApbWritePacket( + final wrPkt = ApbWritePacket( addr: LogicValue.ofInt(i, 32), data: randomData[i], - strobe: withStrobes ? randomStrobes[i] : null)); + strobe: withStrobes ? randomStrobes[i] : null); + + requester.sequencer.add(wrPkt); + + unawaited(wrPkt.completed.then((value) { + expect(wrPkt.returnedSlvErr!.toBool(), withErrors); + + numTransfersCompleted++; + })); + await waitCycles(intf.clk, interTxnDelay); } @@ -126,10 +138,15 @@ class ApbBfmTest extends Test { unawaited(rdPkt.completed.then((value) { expect( rdPkt.returnedData, - withStrobes - ? strobedData(randomData[i], randomStrobes[i]) - : randomData[i], + withErrors + ? LogicValue.filled(32, LogicValue.x) + : withStrobes + ? strobedData(randomData[i], randomStrobes[i]) + : randomData[i], ); + + expect(rdPkt.returnedSlvErr!.toBool(), withErrors); + numTransfersCompleted++; })); @@ -148,7 +165,11 @@ class ApbBfmTest extends Test { @override void check() { - expect(numTransfersCompleted, numTransfers); + expect(numTransfersCompleted, numTransfers * 2); + + if (withErrors) { + expect(storage.isEmpty, true); + } } } @@ -212,4 +233,8 @@ void main() { interTxnDelay: 3, )); }); + + test('with errors', () async { + await runTest(ApbBfmTest('werr', withErrors: true)); + }); } From ba9599679980f510f82f5a307c56a05162f8e6d6 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 15:19:48 -0700 Subject: [PATCH 23/41] some compliance checks --- .../apb_bfm/apb_compliance_checker.dart | 157 +++++++++++++++++- test/apb_bfm_test.dart | 4 +- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/lib/src/models/apb_bfm/apb_compliance_checker.dart b/lib/src/models/apb_bfm/apb_compliance_checker.dart index 6e092b89..04a62b44 100644 --- a/lib/src/models/apb_bfm/apb_compliance_checker.dart +++ b/lib/src/models/apb_bfm/apb_compliance_checker.dart @@ -1,6 +1,153 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// apb_completer.dart +// An agent for completing APB requests. +// +// 2023 June 14 +// Author: Max Korbel -// Rules: -// - strobe must not be "active" during read transfer (all low during read) -// - the FSM is followed -// - addr, write, and wdata are valid when psel is asserted -// - slverr is not X during a transfer \ No newline at end of file +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A checker for some of the rules defined in the APB interface specification. +/// +/// This does not necessarily cover all rules defined in the spec. +class ApbComplianceChecker extends Component { + /// The interface being checked. + final ApbInterface intf; + + /// Creates a new compliance checker for [intf]. + ApbComplianceChecker( + this.intf, { + required Component parent, + String name = 'apbComplianceChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + // wait for reset to complete + await intf.resetN.nextPosedge; + + var accessLastCycle = false; + + LogicValue? lastWrite; + LogicValue? lastAddr; + List? lastSel; + LogicValue? lastWriteData; + LogicValue? lastStrb; + LogicValue? lastProt; + LogicValue? lastAuser; + LogicValue? lastWuser; + + intf.clk.posedge.listen((event) { + final currSels = intf.sel.map((e) => e.value).toList(); + + if (currSels.map((e) => e.isValid).reduce((a, b) => a | b)) { + // if any select is high + + // valid checks + if (!intf.write.value.isValid) { + logger.severe('Write must be valid during select.'); + } + if (!intf.addr.value.isValid) { + logger.severe('Addr must be valid during select.'); + } + if (!intf.wData.value.isValid) { + logger.severe('WData must be valid during select.'); + } + if (!intf.strb.value.isValid) { + logger.severe('Strobe must be valid during select.'); + } + if (!intf.enable.value.isValid) { + logger.severe('Enable must be valid during select.'); + } + + // stability checks + if (intf.enable.value.isValid && intf.enable.value.toBool()) { + if (lastWrite != null && lastWrite != intf.write.value) { + logger.severe('Write must be stable until ready.'); + } + if (lastAddr != null && lastAddr != intf.addr.value) { + logger.severe('Addr must be stable until ready.'); + } + if (lastSel != null) { + for (var i = 0; i < intf.numSelects; i++) { + if (intf.sel[i].value != lastSel![i]) { + logger.severe('Sel must be stable until ready.'); + } + } + } + if (lastWriteData != null && lastWriteData != intf.wData.value) { + logger.severe('Write data must be stable until ready.'); + } + if (lastStrb != null && lastStrb != intf.strb.value) { + logger.severe('Strobe must be stable until ready.'); + } + if (lastProt != null && lastProt != intf.prot.value) { + logger.severe('Prot must be stable until ready.'); + } + if (lastAuser != null && lastAuser != intf.aUser?.value) { + logger.severe('AUser must be stable until ready.'); + } + if (lastWuser != null && lastWuser != intf.wUser?.value) { + logger.severe('WUser must be stable until ready.'); + } + + // collect "last" items for next check + lastWrite = intf.write.value; + lastAddr = intf.addr.value; + lastSel = currSels; + lastWriteData = intf.wData.value; + lastStrb = intf.strb.value; + lastProt = intf.prot.value; + lastAuser = intf.aUser?.value; + lastWuser = intf.wUser?.value; + } + } + + if (intf.ready.value.toBool()) { + lastWrite = null; + lastAddr = null; + lastSel = null; + lastWriteData = null; + lastStrb = null; + lastProt = null; + lastAuser = null; + lastWuser = null; + } + + if (intf.write.value.isValid && + !intf.write.value.toBool() && + intf.enable.value.isValid && + intf.enable.value.toBool() && + intf.strb.value.isValid && + intf.strb.value.toInt() > 0) { + // strobe must not be "active" during read transfer (all low during read) + logger.severe('Strobe must not be active during read transfer.'); + } + + if (intf.enable.value.isValid && + intf.enable.value.toBool() && + intf.ready.value.isValid && + intf.ready.value.toBool()) { + if (accessLastCycle) { + logger.severe('Cannot have back-to-back accesses.'); + } + + if (intf.includeSlvErr && !intf.slvErr!.value.isValid) { + logger.severe('SlvErr must be valid during transfer.'); + } + + accessLastCycle = true; + } else { + accessLastCycle = false; + } + }); + } +} diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index bf8e6804..02dc57de 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -74,6 +74,8 @@ class ApbBfmTest extends Test { outputFolder: outFolder, ); + ApbComplianceChecker(intf, parent: this); + Simulator.registerEndOfSimulationAction(() async { await tracker.terminate(); @@ -173,8 +175,6 @@ class ApbBfmTest extends Test { } } -//TODO: check the checker - void main() { tearDown(() async { await Simulator.reset(); From d451e81b8f6278e4ecabaf5989e45599193b5c5a Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 15:41:34 -0700 Subject: [PATCH 24/41] cleanup and docs --- doc/apb_bfm.md | 20 +++++++++++++++++++ lib/src/interfaces/apb.dart | 10 +++++++++- lib/src/models/apb_bfm/abp_completer.dart | 4 ++-- .../apb_bfm/apb_compliance_checker.dart | 2 +- .../models/apb_bfm/apb_requester_driver.dart | 5 ----- lib/src/models/apb_bfm/apb_tracker.dart | 15 ++++++++++++-- lib/src/models/memory_model.dart | 2 -- 7 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 doc/apb_bfm.md diff --git a/doc/apb_bfm.md b/doc/apb_bfm.md new file mode 100644 index 00000000..8060ad27 --- /dev/null +++ b/doc/apb_bfm.md @@ -0,0 +1,20 @@ +# APB BFM + +The APB BFM is a collection of [ROHD-VF](https://github.com/intel/rohd-vf) components and objects that are helpful for validating hardware that contains an APB interface. It includes all the basic APB interface features for sending and responding to reads and writes, including with strobes and errors. + +The main two components are the `ApbRequesterAgent` and the `ApbCompleterAgent`, which behave like a "requester" and "completer" as described in the APB spec, respectively. The `ApbRequesterAgent` has a standard `Sequencer` that accepts `ApbPacket`s to be driven out to the completer. The `ApbCompleterAgent` has default behavior and accepts a `MemoryStorage` instance as a memory model. See the API docs for more details on how to use each of these components, which both have substantial configurability to control behavior. + +A `ApbMonitor` is also included, which implements the standard `Monitor` and provides a stream of `ApbPacket`s monitored on positive edges of the clock. The `ApbTracker` can be used to log all items detected by the monitor by implementing the standard `Tracker` API (log file or JSON both supported). + +Finally, a `ApbComplianceChecker` monitors an `ApbInterface` for a subset of the rules described in the APB specification. Errors are flagged using the `severe` log messages, as is standard for errors in ROHD-VF. + +The unit tests in `apb_test.dart`, which have a completer and requester communicating with each other, are a good example for setting up the APB BFM. + +## Unsupported features + +The following features are not supported by or have no utilities within the BFM: + +- **Wake-up signalling**: wake-up features are not considered. +- **Protection**: protection features are not considered. +- **User requests and responses**: these signals are un-driven and not monitored by the BFM. +- **Retry on error**: it is up to the user of the BFM to write any error handling logic. diff --git a/lib/src/interfaces/apb.dart b/lib/src/interfaces/apb.dart index 45285432..2aa23858 100644 --- a/lib/src/interfaces/apb.dart +++ b/lib/src/interfaces/apb.dart @@ -159,7 +159,9 @@ class ApbInterface extends Interface { /// Wake-up. /// /// Indicates any activity associated with an APB interface. - Logic get wakeup => port('PWAKEUP'); + /// + /// Only generated if [includeWakeup] is `true`. + Logic? get wakeup => includeWakeup ? port('PWAKEUP') : null; /// User request attribute. /// @@ -187,6 +189,11 @@ class ApbInterface extends Interface { /// than `1`. final int numSelects; + /// If `true`, indicates that this interface supports wake-up. + /// + /// The interface will only include [wakeup] if this is `true`. + final bool includeWakeup; + /// Construct a new instance of an APB interface. ApbInterface({ this.addrWidth = 32, @@ -195,6 +202,7 @@ class ApbInterface extends Interface { this.userDataWidth = 0, this.userRespWidth = 0, this.includeSlvErr = false, + this.includeWakeup = false, this.numSelects = 1, }) { _validateParameters(); diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index d9754ab7..f8b5b210 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -23,8 +23,8 @@ class ApbCompleterAgent extends Agent { /// A place where the completer should save and retrieve data. /// - /// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal is - /// dropped. + /// The [ApbCompleterAgent] will reset [storage] whenever the `resetN` signal + /// is dropped. final MemoryStorage storage; /// A function which delays the response for the given `request`. diff --git a/lib/src/models/apb_bfm/apb_compliance_checker.dart b/lib/src/models/apb_bfm/apb_compliance_checker.dart index 04a62b44..20d6b965 100644 --- a/lib/src/models/apb_bfm/apb_compliance_checker.dart +++ b/lib/src/models/apb_bfm/apb_compliance_checker.dart @@ -128,7 +128,7 @@ class ApbComplianceChecker extends Component { intf.enable.value.toBool() && intf.strb.value.isValid && intf.strb.value.toInt() > 0) { - // strobe must not be "active" during read transfer (all low during read) + // strobe must not be "active" during read xfer (all low during read) logger.severe('Strobe must not be active during read transfer.'); } diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 07c1ea61..8d716412 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -21,11 +21,6 @@ class ApbRequesterDriver extends PendingClockedDriver { /// The interface to drive. final ApbInterface intf; - //TODO: wakeup - //TODO: protection - //TODO: user - //TODO: retry on error? no, not in spec - /// Creates a new [ApbRequesterDriver]. ApbRequesterDriver({ required Component parent, diff --git a/lib/src/models/apb_bfm/apb_tracker.dart b/lib/src/models/apb_bfm/apb_tracker.dart index 00abb6a0..9ef84769 100644 --- a/lib/src/models/apb_bfm/apb_tracker.dart +++ b/lib/src/models/apb_bfm/apb_tracker.dart @@ -12,14 +12,25 @@ import 'package:rohd_vf/rohd_vf.dart'; /// A tracker for the [ApbInterface]. class ApbTracker extends Tracker { - //TODO: make these widths dynamic? - + /// Tracker field for simulation time. static const timeField = TrackerField('time', columnWidth: 12); + + /// Tracker field for select. static const selectField = TrackerField('select', columnWidth: 4); + + /// Tracker field for type (R/W). static const typeField = TrackerField('type', columnWidth: 1); + + /// Tracker field for address. static const addrField = TrackerField('addr', columnWidth: 12); + + /// Tracker field for data. static const dataField = TrackerField('data', columnWidth: 12); + + /// Tracker field for strobe. static const strobeField = TrackerField('strobe', columnWidth: 4); + + /// Tracker field for errors. static const slverrField = TrackerField('slverr', columnWidth: 1); /// Creates a new tracker for [ApbInterface]. diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index cb07be05..9a888ddd 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -10,8 +10,6 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -//TODO: move the X, addr adjust, etc. over to storage - /// A model of a [Memory] which uses a software-based [SparseMemoryStorage] to /// store data. /// From 269ce4d1d5dd7217a7d5371fc210ed0703aa347b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 15:59:30 -0700 Subject: [PATCH 25/41] update library --- lib/src/models/models.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index cb47d0e4..8009ac3d 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -2,4 +2,5 @@ // SPDX-License-Identifier: BSD-3-Clause export 'apb_bfm/apb_bfm.dart'; +export 'memory_model.dart'; export 'sparse_memory_storage.dart'; From 2fee2a79b8faeaae7b28d8f10ebe2089e2fb5837 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 14 Jun 2023 16:33:53 -0700 Subject: [PATCH 26/41] fix lint --- test/memory_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/memory_test.dart b/test/memory_test.dart index 65123bbf..c388793a 100644 --- a/test/memory_test.dart +++ b/test/memory_test.dart @@ -14,7 +14,6 @@ import 'dart:async'; import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/models/memory_model.dart'; import 'package:test/test.dart'; void main() { From 80a0be4173d429bd9a2c18fe01d783424f2c24d0 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 15 Jun 2023 09:14:27 -0700 Subject: [PATCH 27/41] add utils export --- lib/rohd_hcl.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index 4d42445d..f37facff 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -14,3 +14,4 @@ export 'src/one_hot.dart'; export 'src/ripple_carry_adder.dart'; export 'src/rotate.dart'; export 'src/sort.dart'; +export 'src/utils.dart'; From 180b5c8773f7fa66c8891b1171e75b4d2f9a1073 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Thu, 15 Jun 2023 09:33:49 -0700 Subject: [PATCH 28/41] fix lint --- lib/src/fifo.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/fifo.dart b/lib/src/fifo.dart index 56d62615..24942d7d 100644 --- a/lib/src/fifo.dart +++ b/lib/src/fifo.dart @@ -10,7 +10,6 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -import 'package:rohd_hcl/src/utils.dart'; import 'package:rohd_vf/rohd_vf.dart'; /// A simple FIFO (First In, First Out). From 6e28c6335564ff18da1c10a31ef16e1c798b522e Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Fri, 16 Jun 2023 09:35:31 -0700 Subject: [PATCH 29/41] fix fifo to use previousValue --- lib/src/fifo.dart | 17 +++++++++-------- pubspec.yaml | 5 ++++- test/fifo_test.dart | 3 +++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/src/fifo.dart b/lib/src/fifo.dart index 24942d7d..81106147 100644 --- a/lib/src/fifo.dart +++ b/lib/src/fifo.dart @@ -327,24 +327,25 @@ class FifoTracker extends Tracker { columnWidth: fifo.dataWidth ~/ 4 + log2Ceil(fifo.dataWidth) + 1), TrackerField('Occupancy', columnWidth: fifo.depth ~/ 10 + 1), ]) { - LogicValue? prevReadValue; - Simulator.preTick.listen((event) { - prevReadValue = fifo.readData.value; - }); + // register previousValue listeners for important signals + fifo.readData.previousValue; + fifo._writeData.previousValue; + fifo._readEnable.previousValue; + fifo._writeEnable.previousValue; fifo._clk.posedge.listen((event) { - if (fifo._writeEnable.value.toBool()) { + if (fifo._writeEnable.previousValue!.toBool()) { record(_FifoEvent( _FifoCmd.wr, - fifo._writeData.value, + fifo._writeData.previousValue!, ++_occupancy, )); } - if (fifo._readEnable.value.toBool()) { + if (fifo._readEnable.previousValue!.toBool()) { record(_FifoEvent( _FifoCmd.rd, - prevReadValue!, + fifo.readData.previousValue!, --_occupancy, )); } diff --git a/pubspec.yaml b/pubspec.yaml index a951e659..e86d2c95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,4 +23,7 @@ dependency_overrides: git: url: https://github.com/mkorbel1/rohd-vf.git ref: pending_driver - \ No newline at end of file + rohd: + git: + url: https://github.com/mkorbel1/rohd.git + ref: previousvalue \ No newline at end of file diff --git a/test/fifo_test.dart b/test/fifo_test.dart index b15e2dcf..08cc714b 100644 --- a/test/fifo_test.dart +++ b/test/fifo_test.dart @@ -548,6 +548,9 @@ void main() { Directory('tmp_test').createSync(); + // await fifoTest.fifo.build(); + // WaveDumper(fifoTest.fifo); + final tracker = FifoTracker(fifoTest.fifo, outputFolder: 'tmp_test', dumpTable: false); From 3507065b0bd2158fe417b8705c21101b1036a60b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Fri, 16 Jun 2023 10:41:36 -0700 Subject: [PATCH 30/41] adjust fifo to use updated rohd api --- lib/src/fifo.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/fifo.dart b/lib/src/fifo.dart index 81106147..f4b57d21 100644 --- a/lib/src/fifo.dart +++ b/lib/src/fifo.dart @@ -327,12 +327,6 @@ class FifoTracker extends Tracker { columnWidth: fifo.dataWidth ~/ 4 + log2Ceil(fifo.dataWidth) + 1), TrackerField('Occupancy', columnWidth: fifo.depth ~/ 10 + 1), ]) { - // register previousValue listeners for important signals - fifo.readData.previousValue; - fifo._writeData.previousValue; - fifo._readEnable.previousValue; - fifo._writeEnable.previousValue; - fifo._clk.posedge.listen((event) { if (fifo._writeEnable.previousValue!.toBool()) { record(_FifoEvent( From be5de51d4ed1092db83ad6a92efbf99b9356deee Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Fri, 16 Jun 2023 18:02:16 -0700 Subject: [PATCH 31/41] fix monitors to use previousValue and fix tracker to be more configurable --- lib/src/models/apb_bfm/abp_completer.dart | 12 +++++---- lib/src/models/apb_bfm/apb_monitor.dart | 25 ++++++++++------- lib/src/models/apb_bfm/apb_packet.dart | 6 ++--- lib/src/models/apb_bfm/apb_tracker.dart | 33 ++++++++++++++--------- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index f8b5b210..fae588cb 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -123,11 +123,13 @@ class ApbCompleterAgent extends Agent { if (!(writeError && dropWriteDataOnError)) { storage.writeData( packet.addr, - _strobeData( - storage.readData(packet.addr), - packet.data, - packet.strobe, - ), + packet.strobe.and().toBool() // don't `readData` if all 1's + ? packet.data + : _strobeData( + storage.readData(packet.addr), + packet.data, + packet.strobe, + ), ); } diff --git a/lib/src/models/apb_bfm/apb_monitor.dart b/lib/src/models/apb_bfm/apb_monitor.dart index f0caf524..6f757bea 100644 --- a/lib/src/models/apb_bfm/apb_monitor.dart +++ b/lib/src/models/apb_bfm/apb_monitor.dart @@ -32,24 +32,29 @@ class ApbMonitor extends Monitor { intf.clk.posedge.listen((event) { for (var i = 0; i < intf.numSelects; i++) { - if (intf.sel[i].value.toBool() && - intf.enable.value.toBool() && - intf.ready.value.toBool()) { - if (intf.write.value.toBool()) { + if (intf.sel[i].previousValue!.toBool() && + intf.enable.previousValue!.toBool() && + intf.ready.previousValue!.toBool()) { + if (intf.write.previousValue!.toBool()) { add( ApbWritePacket( - addr: intf.addr.value, - data: intf.wData.value, - strobe: intf.strb.value, + addr: intf.addr.previousValue!, + data: intf.wData.previousValue!, + strobe: intf.strb.previousValue, selectIndex: i, - )..complete(slvErr: intf.slvErr?.value), + )..complete( + slvErr: intf.slvErr?.previousValue, + ), ); } else { add( ApbReadPacket( - addr: intf.addr.value, + addr: intf.addr.previousValue!, selectIndex: i, - )..complete(data: intf.rData.value, slvErr: intf.slvErr?.value), + )..complete( + data: intf.rData.previousValue, + slvErr: intf.slvErr?.previousValue, + ), ); } } diff --git a/lib/src/models/apb_bfm/apb_packet.dart b/lib/src/models/apb_bfm/apb_packet.dart index 2d82e53d..2daeb8f3 100644 --- a/lib/src/models/apb_bfm/apb_packet.dart +++ b/lib/src/models/apb_bfm/apb_packet.dart @@ -44,7 +44,7 @@ abstract class ApbPacket extends SequenceItem implements Trackable { @override String? trackerString(TrackerField field) { - switch (field) { + switch (field.title) { case ApbTracker.timeField: return Simulator.time.toString(); case ApbTracker.addrField: @@ -77,7 +77,7 @@ class ApbWritePacket extends ApbPacket { @override String? trackerString(TrackerField field) { - switch (field) { + switch (field.title) { case ApbTracker.typeField: return 'W'; case ApbTracker.dataField: @@ -113,7 +113,7 @@ class ApbReadPacket extends ApbPacket { @override String? trackerString(TrackerField field) { - switch (field) { + switch (field.title) { case ApbTracker.typeField: return 'R'; case ApbTracker.dataField: diff --git a/lib/src/models/apb_bfm/apb_tracker.dart b/lib/src/models/apb_bfm/apb_tracker.dart index 9ef84769..0f7245c2 100644 --- a/lib/src/models/apb_bfm/apb_tracker.dart +++ b/lib/src/models/apb_bfm/apb_tracker.dart @@ -13,39 +13,46 @@ import 'package:rohd_vf/rohd_vf.dart'; /// A tracker for the [ApbInterface]. class ApbTracker extends Tracker { /// Tracker field for simulation time. - static const timeField = TrackerField('time', columnWidth: 12); + static const timeField = 'time'; /// Tracker field for select. - static const selectField = TrackerField('select', columnWidth: 4); + static const selectField = 'select'; /// Tracker field for type (R/W). - static const typeField = TrackerField('type', columnWidth: 1); + static const typeField = 'type'; /// Tracker field for address. - static const addrField = TrackerField('addr', columnWidth: 12); + static const addrField = 'addr'; /// Tracker field for data. - static const dataField = TrackerField('data', columnWidth: 12); + static const dataField = 'data'; /// Tracker field for strobe. - static const strobeField = TrackerField('strobe', columnWidth: 4); + static const strobeField = 'strobe'; /// Tracker field for errors. - static const slverrField = TrackerField('slverr', columnWidth: 1); + static const slverrField = 'slverr'; /// Creates a new tracker for [ApbInterface]. + /// + /// If the [selectColumnWidth] is set to 0, the field will be omitted. ApbTracker({ required ApbInterface intf, String name = 'apbTracker', super.dumpJson, super.dumpTable, super.outputFolder, + int timeColumnWidth = 12, + int selectColumnWidth = 4, }) : super(name, [ - timeField, - typeField, - addrField, - dataField, - strobeField, - if (intf.includeSlvErr) slverrField, + TrackerField(timeField, columnWidth: timeColumnWidth), + if (selectColumnWidth > 0) + TrackerField(selectField, columnWidth: selectColumnWidth), + const TrackerField(typeField, columnWidth: 1), + const TrackerField(addrField, columnWidth: 12), + const TrackerField(dataField, columnWidth: 12), + const TrackerField(slverrField, columnWidth: 4), + if (intf.includeSlvErr) + const TrackerField(slverrField, columnWidth: 1), ]); } From bafd939c3fd556ae88c7823e59a28bd44bd9890c Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 20 Jun 2023 14:26:21 -0700 Subject: [PATCH 32/41] fix adder and update dependencies --- lib/src/adder.dart | 3 ++- lib/src/models/apb_bfm/abp_completer.dart | 3 +-- lib/src/ripple_carry_adder.dart | 14 +++++--------- pubspec.yaml | 10 +++++----- test/apb_bfm_test.dart | 8 ++++---- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/src/adder.dart b/lib/src/adder.dart index 83eb104b..132d5702 100644 --- a/lib/src/adder.dart +++ b/lib/src/adder.dart @@ -23,7 +23,7 @@ abstract class Adder extends Module { late final Logic b; /// The addition results [sum]. - Logic get sum; + Logic get sum => output('sum'); /// Takes in input [a] and input [b] and return the [sum] of the addition /// result. The width of input [a] and [b] must be the same. @@ -33,6 +33,7 @@ abstract class Adder extends Module { } this.a = addInput('a', a, width: a.width); this.b = addInput('b', b, width: b.width); + addOutput('sum', width: a.width + 1); } } diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index fae588cb..afbf20c3 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -108,8 +108,7 @@ class ApbCompleterAgent extends Agent { if (responseDelay != null) { final delayCycles = responseDelay!(packet); if (delayCycles > 0) { - await waitCycles( - intf.clk, + await intf.clk.waitCycles( delayCycles, edge: Edge.neg, ); diff --git a/lib/src/ripple_carry_adder.dart b/lib/src/ripple_carry_adder.dart index 65062837..0c257512 100644 --- a/lib/src/ripple_carry_adder.dart +++ b/lib/src/ripple_carry_adder.dart @@ -17,24 +17,20 @@ import 'package:rohd_hcl/rohd_hcl.dart'; /// from the least significant bit (LSB) to most significant bit (MSB), the /// adder sequentially adds corresponding bits of two binary numbers. class RippleCarryAdder extends Adder { - /// The List of results returned from the [FullAdder]. - final _sum = []; - - /// The final result of the NBitAdder in a list of Logic. - @override - Logic get sum => _sum.rswizzle(); - /// Constructs an n-bit adder based on inputs List of inputs. RippleCarryAdder(super.a, super.b, {super.name = 'ripple_carry_adder'}) { Logic carry = Const(0); + final sumList = []; for (var i = 0; i < a.width; i++) { final fullAdder = FullAdder(a: a[i], b: b[i], carryIn: carry); carry = fullAdder.carryOut; - _sum.add(fullAdder.sum); + sumList.add(fullAdder.sum); } - _sum.add(carry); + sumList.add(carry); + + sum <= sumList.rswizzle(); } } diff --git a/pubspec.yaml b/pubspec.yaml index e86d2c95..87efb640 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,11 +19,11 @@ dev_dependencies: test: ^1.21.0 dependency_overrides: - rohd_vf: - git: - url: https://github.com/mkorbel1/rohd-vf.git - ref: pending_driver rohd: git: url: https://github.com/mkorbel1/rohd.git - ref: previousvalue \ No newline at end of file + ref: main + rohd_vf: + git: + url: https://github.com/mkorbel1/rohd-vf.git + ref: main diff --git a/test/apb_bfm_test.dart b/test/apb_bfm_test.dart index 02dc57de..84a1290b 100644 --- a/test/apb_bfm_test.dart +++ b/test/apb_bfm_test.dart @@ -129,7 +129,7 @@ class ApbBfmTest extends Test { numTransfersCompleted++; })); - await waitCycles(intf.clk, interTxnDelay); + await intf.clk.waitCycles(interTxnDelay); } // normal reads that check data @@ -152,16 +152,16 @@ class ApbBfmTest extends Test { numTransfersCompleted++; })); - await waitCycles(intf.clk, interTxnDelay); + await intf.clk.waitCycles(interTxnDelay); } obj.drop(); } Future _resetFlow() async { - await waitCycles(intf.clk, 2); + await intf.clk.waitCycles(2); intf.resetN.inject(0); - await waitCycles(intf.clk, 3); + await intf.clk.waitCycles(3); intf.resetN.inject(1); } From 9a22ded651923919ffab0aaac37c29aab890be44 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 20 Jun 2023 15:00:39 -0700 Subject: [PATCH 33/41] fix typo on tracker field for strobe --- lib/src/models/apb_bfm/apb_tracker.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/apb_bfm/apb_tracker.dart b/lib/src/models/apb_bfm/apb_tracker.dart index 0f7245c2..69146344 100644 --- a/lib/src/models/apb_bfm/apb_tracker.dart +++ b/lib/src/models/apb_bfm/apb_tracker.dart @@ -51,7 +51,7 @@ class ApbTracker extends Tracker { const TrackerField(typeField, columnWidth: 1), const TrackerField(addrField, columnWidth: 12), const TrackerField(dataField, columnWidth: 12), - const TrackerField(slverrField, columnWidth: 4), + const TrackerField(strobeField, columnWidth: 4), if (intf.includeSlvErr) const TrackerField(slverrField, columnWidth: 1), ]); From b92a02c476302d3bfbc9e63def99bfcdf73e29cb Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 20 Jun 2023 16:37:44 -0700 Subject: [PATCH 34/41] put on posedges --- lib/src/models/apb_bfm/abp_completer.dart | 7 ++----- lib/src/models/apb_bfm/apb_requester_driver.dart | 15 +++++++++++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index afbf20c3..63d2b7bb 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -108,10 +108,7 @@ class ApbCompleterAgent extends Agent { if (responseDelay != null) { final delayCycles = responseDelay!(packet); if (delayCycles > 0) { - await intf.clk.waitCycles( - delayCycles, - edge: Edge.neg, - ); + await intf.clk.waitCycles(delayCycles); } } @@ -145,7 +142,7 @@ class ApbCompleterAgent extends Agent { ); } - // wait a cycle then end the transfer + // drop the ready when enable drops await intf.enable.nextNegedge; _respond(ready: false); } diff --git a/lib/src/models/apb_bfm/apb_requester_driver.dart b/lib/src/models/apb_bfm/apb_requester_driver.dart index 8d716412..5ac8b828 100644 --- a/lib/src/models/apb_bfm/apb_requester_driver.dart +++ b/lib/src/models/apb_bfm/apb_requester_driver.dart @@ -39,7 +39,14 @@ class ApbRequesterDriver extends PendingClockedDriver { Future run(Phase phase) async { unawaited(super.run(phase)); - _deselectAll(); + Simulator.injectAction(() { + _deselectAll(); + intf.enable.put(0); + intf.write.put(0); + intf.addr.put(0); + intf.wData.put(0); + intf.strb.put(0); + }); // wait for reset to complete before driving anything await intf.resetN.nextPosedge; @@ -48,7 +55,7 @@ class ApbRequesterDriver extends PendingClockedDriver { if (pendingSeqItems.isNotEmpty) { await _drivePacket(pendingSeqItems.removeFirst()); } else { - await intf.clk.nextNegedge; + await intf.clk.nextPosedge; Simulator.injectAction(() { _deselectAll(); intf.enable.put(0); @@ -61,7 +68,7 @@ class ApbRequesterDriver extends PendingClockedDriver { Future _drivePacket(ApbPacket packet) async { // first, SETUP - await intf.clk.nextNegedge; + await intf.clk.nextPosedge; // if we're not selecting this interface, then we need to select it if (!intf.sel[packet.selectIndex].value.toBool()) { @@ -83,7 +90,7 @@ class ApbRequesterDriver extends PendingClockedDriver { } }); - await intf.clk.nextNegedge; + await intf.clk.nextPosedge; // now, ACCESS intf.enable.inject(1); From 10645d288fe9a4c37301e77e8493a37856548b49 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Wed, 21 Jun 2023 09:53:31 -0700 Subject: [PATCH 35/41] fix dep overrides for now --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 87efb640..9b0ba35b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,9 +21,9 @@ dev_dependencies: dependency_overrides: rohd: git: - url: https://github.com/mkorbel1/rohd.git + url: https://github.com/intel/rohd.git ref: main rohd_vf: git: - url: https://github.com/mkorbel1/rohd-vf.git + url: https://github.com/intel/rohd-vf.git ref: main From 6bb78065f07dd202f7f21a9611144f94e031c7dd Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 7 Aug 2023 10:59:26 -0700 Subject: [PATCH 36/41] adjust sampling in memory model to use previousValue --- lib/src/models/memory_model.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index 9a888ddd..ee244664 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -58,34 +58,35 @@ class MemoryModel extends Memory { // on posedge of clock, sample write ports and save to memory clk.posedge.listen((event) { - if (reset.value == LogicValue.one) { + if (reset.previousValue == LogicValue.one) { storage.reset(); return; } for (final wrPort in wrPorts) { - if (!wrPort.en.value.isValid && !storage.isEmpty) { + if (!(wrPort.en.previousValue?.isValid ?? false) && !storage.isEmpty) { // storage doesnt have access to `en`, so check ourselves storage.invalidWrite(); return; } - if (wrPort.en.value == LogicValue.one) { - final addrValue = wrPort.addr.value; + if (wrPort.en.previousValue == LogicValue.one) { + final addrValue = wrPort.addr.previousValue!; if (wrPort is MaskedDataPortInterface) { storage.writeData( addrValue, [ for (var index = 0; index < dataWidth ~/ 8; index++) - wrPort.mask.value[index].toBool() - ? wrPort.data.value.getRange(index * 8, (index + 1) * 8) + wrPort.mask.previousValue![index].toBool() + ? wrPort.data.previousValue! + .getRange(index * 8, (index + 1) * 8) : storage .readData(addrValue) .getRange(index * 8, (index + 1) * 8) ].rswizzle(), ); } else { - storage.writeData(addrValue, wrPort.data.value); + storage.writeData(addrValue, wrPort.data.previousValue!); } } } From 3fc454be98ac650e4f5916004a78761b85c71732 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 7 Aug 2023 11:02:51 -0700 Subject: [PATCH 37/41] be more lenient if first edge --- lib/src/models/memory_model.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/models/memory_model.dart b/lib/src/models/memory_model.dart index ee244664..e3b9e9a4 100644 --- a/lib/src/models/memory_model.dart +++ b/lib/src/models/memory_model.dart @@ -63,7 +63,8 @@ class MemoryModel extends Memory { return; } for (final wrPort in wrPorts) { - if (!(wrPort.en.previousValue?.isValid ?? false) && !storage.isEmpty) { + if (!(wrPort.en.previousValue?.isValid ?? wrPort.en.value.isValid) && + !storage.isEmpty) { // storage doesnt have access to `en`, so check ourselves storage.invalidWrite(); return; From bc120240284565d7669fba4cb19e04ea5e53a120 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Mon, 18 Sep 2023 21:04:02 -0700 Subject: [PATCH 38/41] add todo for now --- lib/src/models/apb_bfm/abp_completer.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 63d2b7bb..49efa6c3 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -45,6 +45,8 @@ class ApbCompleterAgent extends Agent { /// [storage]. final bool dropWriteDataOnError; + //TODO: allow a default storage instead of requiring one + /// Creates a new model [ApbCompleterAgent]. ApbCompleterAgent( {required this.intf, @@ -77,7 +79,7 @@ class ApbCompleterAgent extends Agent { } /// Calculates a strobed version of data. - LogicValue _strobeData( + static LogicValue _strobeData( LogicValue originalData, LogicValue newData, LogicValue strobe) => [ for (var i = 0; i < strobe.width; i++) From b39d06449ef5b03e6b1390e55582413f8d006e98 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 26 Sep 2023 11:57:49 -0700 Subject: [PATCH 39/41] remove dupe file --- doc/apb_bfm.md | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 doc/apb_bfm.md diff --git a/doc/apb_bfm.md b/doc/apb_bfm.md deleted file mode 100644 index 8060ad27..00000000 --- a/doc/apb_bfm.md +++ /dev/null @@ -1,20 +0,0 @@ -# APB BFM - -The APB BFM is a collection of [ROHD-VF](https://github.com/intel/rohd-vf) components and objects that are helpful for validating hardware that contains an APB interface. It includes all the basic APB interface features for sending and responding to reads and writes, including with strobes and errors. - -The main two components are the `ApbRequesterAgent` and the `ApbCompleterAgent`, which behave like a "requester" and "completer" as described in the APB spec, respectively. The `ApbRequesterAgent` has a standard `Sequencer` that accepts `ApbPacket`s to be driven out to the completer. The `ApbCompleterAgent` has default behavior and accepts a `MemoryStorage` instance as a memory model. See the API docs for more details on how to use each of these components, which both have substantial configurability to control behavior. - -A `ApbMonitor` is also included, which implements the standard `Monitor` and provides a stream of `ApbPacket`s monitored on positive edges of the clock. The `ApbTracker` can be used to log all items detected by the monitor by implementing the standard `Tracker` API (log file or JSON both supported). - -Finally, a `ApbComplianceChecker` monitors an `ApbInterface` for a subset of the rules described in the APB specification. Errors are flagged using the `severe` log messages, as is standard for errors in ROHD-VF. - -The unit tests in `apb_test.dart`, which have a completer and requester communicating with each other, are a good example for setting up the APB BFM. - -## Unsupported features - -The following features are not supported by or have no utilities within the BFM: - -- **Wake-up signalling**: wake-up features are not considered. -- **Protection**: protection features are not considered. -- **User requests and responses**: these signals are un-driven and not monitored by the BFM. -- **Retry on error**: it is up to the user of the BFM to write any error handling logic. From 9ee26d827eeb433f10d09b22979a0493c3213c6b Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 26 Sep 2023 11:58:19 -0700 Subject: [PATCH 40/41] remove wavedump code --- test/fifo_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/fifo_test.dart b/test/fifo_test.dart index bcd5ec54..901eda7f 100644 --- a/test/fifo_test.dart +++ b/test/fifo_test.dart @@ -570,9 +570,6 @@ void main() { Directory('tmp_test').createSync(); - // await fifoTest.fifo.build(); - // WaveDumper(fifoTest.fifo); - final tracker = FifoTracker(fifoTest.fifo, outputFolder: 'tmp_test', dumpTable: false); From 43ffbbb343e6a4d8003c02ef587d66439f88bcc8 Mon Sep 17 00:00:00 2001 From: Max Korbel Date: Tue, 26 Sep 2023 12:02:03 -0700 Subject: [PATCH 41/41] allow default storage in completer agent --- lib/src/models/apb_bfm/abp_completer.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/models/apb_bfm/abp_completer.dart b/lib/src/models/apb_bfm/abp_completer.dart index 49efa6c3..55052566 100644 --- a/lib/src/models/apb_bfm/abp_completer.dart +++ b/lib/src/models/apb_bfm/abp_completer.dart @@ -45,20 +45,25 @@ class ApbCompleterAgent extends Agent { /// [storage]. final bool dropWriteDataOnError; - //TODO: allow a default storage instead of requiring one - /// Creates a new model [ApbCompleterAgent]. + /// + /// If no [storage] is provided, it will use a default [SparseMemoryStorage]. ApbCompleterAgent( {required this.intf, - required this.storage, required Component parent, + MemoryStorage? storage, this.selectIndex = 0, this.responseDelay, this.respondWithError, this.invalidReadDataOnError = true, this.dropWriteDataOnError = true, String name = 'apbCompleter'}) - : super(name, parent); + : storage = storage ?? + SparseMemoryStorage( + addrWidth: intf.addrWidth, + dataWidth: intf.dataWidth, + ), + super(name, parent); @override Future run(Phase phase) async {