diff --git a/.gitignore b/.gitignore index 7e07b90e..04504631 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ tmp* *.vcd .vscode/* confapp/.vscode/* +*tracker.json +*tracker.log # Exceptions !.vscode/extensions.json diff --git a/doc/README.md b/doc/README.md index bc653e7a..91858e2e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -84,6 +84,7 @@ Some in-development items will have opened issues, as well. Feel free to create - HBM - Models - [APB](./components/apb_bfm.md) + - [Ready/Valid](./components/ready_valid_bfm.md) ---------------- diff --git a/doc/components/apb_bfm.md b/doc/components/apb_bfm.md index 8060ad27..429b2ffa 100644 --- a/doc/components/apb_bfm.md +++ b/doc/components/apb_bfm.md @@ -4,11 +4,11 @@ The APB BFM is a collection of [ROHD-VF](https://github.com/intel/rohd-vf) compo 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). +An `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. +The unit tests in `apb_bfm_test.dart`, which have a completer and requester communicating with each other, are a good example for setting up the APB BFM. ## Unsupported features diff --git a/doc/components/ready_valid_bfm.md b/doc/components/ready_valid_bfm.md new file mode 100644 index 00000000..74e3ebc9 --- /dev/null +++ b/doc/components/ready_valid_bfm.md @@ -0,0 +1,13 @@ +# Ready/Valid BFM + +The Ready/Valid BFM is a collection of [ROHD-VF](https://github.com/intel/rohd-vf) components and objects that are helpful for validating hardware that contains interfaces that use a ready/valid protocol. To summarize: + +- When a transmitter has something to send, it raises `valid`. +- When a receiver is able to accept something, it raises `ready`. +- When both `valid` and `ready` are high, the transaction is accepted by both sides. + +The main two components are the `ReadyValidTransmitterAgent` and `ReadyValidReceiverAgent`, which transmit and receive `data`, respectively. Any bundle of information can be mapped onto the `data` bus. Both agents provide a `blockRate` argument which controls a random weighted chance of preventing a transaction from occuring (either delaying a `valid` or dropping a `ready`). + +Additionally, the `ReadyValidMonitor` can be placed on any ready/valid protocol to observe transactions that are accepted. The resulting `ReadyValidPacket`s can also be logged via the `ReadyValidTracker`. + +The unit tests in `ready_valid_bfm_test.dart`, which have a transmitter and receiver agent talking to each other, can serve as a good example of how to use these components. diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 8009ac3d..617bf5ab 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,6 +1,7 @@ -// Copyright (C) 2023 Intel Corporation +// Copyright (C) 2023-2024 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'apb_bfm/apb_bfm.dart'; export 'memory_model.dart'; +export 'ready_valid_bfm/ready_valid_bfm.dart'; export 'sparse_memory_storage.dart'; diff --git a/lib/src/models/ready_valid_bfm/ready_valid_agent.dart b/lib/src/models/ready_valid_bfm/ready_valid_agent.dart new file mode 100644 index 00000000..66f76b7c --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_agent.dart @@ -0,0 +1,40 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// ready_valid_agent.dart +// A generic agent for ready/valid protocol. +// +// 2024 January 5 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A generic agent for ready/valid protocol. +abstract class ReadyValidAgent extends Agent { + /// The clock. + final Logic clk; + + /// Active-high reset. + final Logic reset; + + /// Ready signal. + final Logic ready; + + /// Valid signal. + final Logic valid; + + /// Data being transmitted. + final Logic data; + + /// Creates a new agent. + ReadyValidAgent({ + required this.clk, + required this.reset, + required this.ready, + required this.valid, + required this.data, + required Component? parent, + String name = 'readyValidComponent', + }) : super(name, parent); +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_bfm.dart b/lib/src/models/ready_valid_bfm/ready_valid_bfm.dart new file mode 100644 index 00000000..428b7e4a --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_bfm.dart @@ -0,0 +1,10 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'ready_valid_agent.dart'; +export 'ready_valid_monitor.dart'; +export 'ready_valid_packet.dart'; +export 'ready_valid_receiver_agent.dart'; +export 'ready_valid_tracker.dart'; +export 'ready_valid_transmitter_agent.dart'; +export 'ready_valid_transmitter_driver.dart'; diff --git a/lib/src/models/ready_valid_bfm/ready_valid_monitor.dart b/lib/src/models/ready_valid_bfm/ready_valid_monitor.dart new file mode 100644 index 00000000..a4c74f97 --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_monitor.dart @@ -0,0 +1,60 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// ready_valid_monitor.dart +// A monitor for ready/valid protocol. +// +// 2024 January 5 +// 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 [Monitor] for ready/valid protocol. +class ReadyValidMonitor extends Monitor { + /// The clock. + final Logic clk; + + /// Active-high reset. + final Logic reset; + + /// Ready signal. + final Logic ready; + + /// Valid signal. + final Logic valid; + + /// Data being transmitted. + final Logic data; + + /// Creates a new [ReadyValidMonitor]. + ReadyValidMonitor({ + required this.clk, + required this.reset, + required this.ready, + required this.valid, + required this.data, + required Component? parent, + String name = 'readyValidMonitor', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + await reset.nextNegedge; + + clk.posedge.listen((event) { + if (!ready.previousValue!.isValid || !valid.previousValue!.isValid) { + logger.severe('Both ready and valid must be valid for protocol,' + ' but found ready=${ready.value} and valid=${valid.value}'); + } else if (ready.previousValue!.toBool() && + valid.previousValue!.toBool()) { + add(ReadyValidPacket(data.previousValue!)); + } + }); + } +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_packet.dart b/lib/src/models/ready_valid_bfm/ready_valid_packet.dart new file mode 100644 index 00000000..9989e7bb --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_packet.dart @@ -0,0 +1,33 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// ready_valid_packet.dart +// A monitor for ready/valid protocol. +// +// 2024 January 5 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A packet to be transmitted over a ready/valid interface. +class ReadyValidPacket extends SequenceItem implements Trackable { + /// The data associated with this packet. + final LogicValue data; + + /// Constructs a new packet with associated [data]. + ReadyValidPacket(this.data); + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case ReadyValidTracker.timeField: + return Simulator.time.toString(); + case ReadyValidTracker.dataField: + return data.toString(); + } + + return null; + } +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_receiver_agent.dart b/lib/src/models/ready_valid_bfm/ready_valid_receiver_agent.dart new file mode 100644 index 00000000..6e8becae --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_receiver_agent.dart @@ -0,0 +1,57 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// ready_valid_receiver_agent.dart +// An agent for receiving over a ready/valid protocol. +// +// 2024 January 5 +// Author: Max Korbel + +import 'dart:async'; +import 'dart:math'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An [Agent] for receiving over a ready/valid protocol. +class ReadyValidReceiverAgent extends ReadyValidAgent { + /// Probability (from 0 to 1) of blocking a ready from being driven. + /// + /// 0 -> never block, accept transactions as soon as possible. + final double blockRate; + + /// Creates an [Agent] for receiving over a ready/valid protocol. + ReadyValidReceiverAgent({ + required super.clk, + required super.reset, + required super.ready, + required super.valid, + required super.data, + required super.parent, + this.blockRate = 0, + super.name = 'readyValidReceiverAgent', + }); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final random = Test.random ?? Random(); + + await _drive(LogicValue.zero); + + await reset.nextNegedge; + + while (!Simulator.simulationHasEnded) { + final doBlock = random.nextDouble() < blockRate; + + await _drive(doBlock ? LogicValue.zero : LogicValue.one); + } + } + + Future _drive(LogicValue newReady) async { + ready.inject(newReady); + await clk.nextPosedge; + } +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_tracker.dart b/lib/src/models/ready_valid_bfm/ready_valid_tracker.dart new file mode 100644 index 00000000..8991918d --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_tracker.dart @@ -0,0 +1,24 @@ +import 'package:rohd_hcl/src/models/models.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A tracker for a ready/valid protocol. +class ReadyValidTracker extends Tracker { + /// Tracker field for simulation time. + static const timeField = 'time'; + + /// Tracker field for data. + static const dataField = 'data'; + + /// Creates a new tracker for a ready/valid protocol. + ReadyValidTracker({ + String name = 'readyValidTracker', + super.dumpJson, + super.dumpTable, + super.outputFolder, + int timeColumnWidth = 12, + int dataColumnWidth = 14, + }) : super(name, [ + TrackerField(timeField, columnWidth: timeColumnWidth), + TrackerField(dataField, columnWidth: dataColumnWidth), + ]); +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_transmitter_agent.dart b/lib/src/models/ready_valid_bfm/ready_valid_transmitter_agent.dart new file mode 100644 index 00000000..445fd3cb --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_transmitter_agent.dart @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// ready_valid_transmitter_agent.dart +// An agent for transmitting over a ready/valid protocol. +// +// 2024 January 5 +// Author: Max Korbel + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An [Agent] for transmitting over a ready/valid protocol. +class ReadyValidTransmitterAgent extends ReadyValidAgent { + /// The [Sequencer] to send [ReadyValidPacket]s into. + late final Sequencer sequencer; + + /// Creates an [Agent] for transmitting over a ready/valid protocol. + /// + /// The [blockRate] is the probability (from 0 to 1) of blocking a valid from + /// being driven. + ReadyValidTransmitterAgent({ + required super.clk, + required super.reset, + required super.ready, + required super.valid, + required super.data, + required super.parent, + double blockRate = 0, + super.name = 'readyValidTransmitterAgent', + }) { + sequencer = Sequencer('sequencer', this); + + ReadyValidTransmitterDriver( + clk: clk, + reset: reset, + ready: ready, + valid: valid, + data: data, + sequencer: sequencer, + blockRate: blockRate, + parent: this, + ); + } +} diff --git a/lib/src/models/ready_valid_bfm/ready_valid_transmitter_driver.dart b/lib/src/models/ready_valid_bfm/ready_valid_transmitter_driver.dart new file mode 100644 index 00000000..270d73de --- /dev/null +++ b/lib/src/models/ready_valid_bfm/ready_valid_transmitter_driver.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_hcl/src/models/ready_valid_bfm/ready_valid_packet.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An [Agent] for transmitting over a ready/valid protocol. +class ReadyValidTransmitterDriver + extends PendingClockedDriver { + /// Active-high reset. + final Logic reset; + + /// Ready signal. + final Logic ready; + + /// Valid signal. + final Logic valid; + + /// Data being transmitted. + final Logic data; + + /// Probability (from 0 to 1) of blocking a valid from being driven. + /// + /// 0 -> never block, send transactions as soon as possible. + final double blockRate; + + /// Creates an [Agent] for transmitting over a ready/valid protocol. + ReadyValidTransmitterDriver({ + required super.clk, + required this.reset, + required this.ready, + required this.valid, + required this.data, + required super.sequencer, + required Component? parent, + this.blockRate = 0, + String name = 'readyValidTransmitterDriver', + super.dropDelayCycles = 30, + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final random = Test.random ?? Random(); + + await _drive(null); + + await reset.nextNegedge; + + while (!Simulator.simulationHasEnded) { + final doBlock = random.nextDouble() < blockRate; + + if (pendingSeqItems.isNotEmpty && !doBlock) { + await _drive(pendingSeqItems.removeFirst()); + } else { + await _drive(null); + } + } + } + + Future _drive(ReadyValidPacket? pkt) async { + if (pkt == null) { + valid.inject(0); + data.inject(LogicValue.x); + + await clk.nextPosedge; + } else { + valid.inject(1); + + assert(pkt.data.width == data.width, 'Data widths should match.'); + data.inject(pkt.data); + + // wait for it to be accepted + await clk.nextPosedge; + while (!ready.previousValue!.toBool()) { + await clk.nextPosedge; + } + } + } +} diff --git a/test/ready_valid_bfm_test.dart b/test/ready_valid_bfm_test.dart new file mode 100644 index 00000000..f43acec8 --- /dev/null +++ b/test/ready_valid_bfm_test.dart @@ -0,0 +1,184 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/models/models.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +class DummyReadyValidModule extends Module { + DummyReadyValidModule( + Logic clk, + Logic reset, + Logic ready, + Logic valid, + Logic data, + ) { + clk = addInput('clk', clk); + reset = addInput('reset', reset); + ready = addInput('ready', ready); + valid = addInput('valid', valid); + data = addInput('data', data, width: 32); + } +} + +class ReadyValidBfmTest extends Test { + final int numTransfers; + + final int interTxnDelay; + + final bool withRandomBlocks; + + final clk = SimpleClockGenerator(10).clk; + final Logic reset = Logic(); + final Logic ready = Logic(); + final Logic valid = Logic(); + final Logic data = Logic(width: 32); + + late final ReadyValidTransmitterAgent transmitter; + late final ReadyValidReceiverAgent receiver; + late final ReadyValidMonitor monitor; + + String get outFolder => 'tmp_test/readyvalidbfm/$name/'; + + ReadyValidBfmTest( + super.name, { + this.numTransfers = 10, + this.interTxnDelay = 0, + this.withRandomBlocks = false, + super.randomSeed = 1234, + super.printLevel = Level.OFF, + }) { + transmitter = ReadyValidTransmitterAgent( + clk: clk, + reset: reset, + ready: ready, + valid: valid, + data: data, + blockRate: withRandomBlocks ? 0.5 : 0, + parent: this, + ); + + receiver = ReadyValidReceiverAgent( + clk: clk, + reset: reset, + ready: ready, + valid: valid, + data: data, + blockRate: withRandomBlocks ? 0.5 : 0, + parent: this, + ); + + monitor = ReadyValidMonitor( + clk: clk, + reset: reset, + ready: ready, + valid: valid, + data: data, + parent: this, + ); + + Directory(outFolder).createSync(recursive: true); + + final tracker = ReadyValidTracker(outputFolder: outFolder); + monitor.stream.listen(tracker.record); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + + final jsonStr = + File('$outFolder/readyValidTracker.tracker.json').readAsStringSync(); + final jsonContents = json.decode(jsonStr); + // ignore: avoid_dynamic_calls + expect(jsonContents['records'].length, numTransfers); + + Directory(outFolder).deleteSync(recursive: true); + }); + } + + int numTransfersCompleted = 0; + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('apbBfmTestObj'); + + await _resetFlow(); + + logger.info('Reset flow completed'); + + final randomData = List.generate(numTransfers, + (index) => LogicValue.ofInt(Test.random!.nextInt(1 << 32), 32)); + + // check correct data coming out of the monitor + monitor.stream.listen((event) { + logger.info('Monitor received $numTransfersCompleted $event'); + expect(event.data, randomData[numTransfersCompleted++]); + }); + + for (var i = 0; i < numTransfers; i++) { + final pkt = ReadyValidPacket(randomData[i]); + + logger.info('Adding packet $i'); + transmitter.sequencer.add(pkt); + + await clk.waitCycles(interTxnDelay); + } + + logger.info('Dropping objection!'); + + obj.drop(); + } + + Future _resetFlow() async { + await clk.waitCycles(2); + reset.inject(1); + await clk.waitCycles(3); + reset.inject(0); + } + + @override + void check() { + expect(numTransfersCompleted, numTransfers); + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + Future runTest(ReadyValidBfmTest readyValidBfmTest, + {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(3000); + + if (dumpWaves) { + final mod = DummyReadyValidModule( + readyValidBfmTest.clk, + readyValidBfmTest.reset, + readyValidBfmTest.ready, + readyValidBfmTest.valid, + readyValidBfmTest.data, + ); + await mod.build(); + WaveDumper(mod); + } + + await readyValidBfmTest.start(); + } + + test('simple', () async { + await runTest(ReadyValidBfmTest('simple')); + }); + + test('kitchen sink', () async { + await runTest(ReadyValidBfmTest( + 'kitchen_sink', + withRandomBlocks: true, + interTxnDelay: 3, + )); + }); +}