diff --git a/doc/README.md b/doc/README.md index 8dcf21fd7..3d63a3f8d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -109,14 +109,14 @@ Some in-development items will have opened issues, as well. Feel free to create - PCIe - UCIe - JTAG - - SPI + - [SPI](./components/standard_interfaces.md#spi) - UART - DDR - HBM - Models - [APB](./components/apb_bfm.md) - [Ready/Valid](./components/ready_valid_bfm.md) - - SPI + - [SPI](./components/spi_bfm.md) - CXL ---------------- diff --git a/doc/components/spi_bfm.md b/doc/components/spi_bfm.md new file mode 100644 index 000000000..672d8673c --- /dev/null +++ b/doc/components/spi_bfm.md @@ -0,0 +1,17 @@ +# SPI BFM + +The SPI 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 SPI interface. It includes all the basic SPI interface features for sending and responding to data between a main-sub connection. + +The main two components are the `SpiMainAgent` and the `SpiSubAgent`, which behave like a "Main" and "Sub" as commonly seen in SPI implementations. Both have a standard `Sequencer` that accepts `SpiPacket`s to be driven out to each other. Both also have a corresponding `Driver` as `SpiMainDriver` and `SpiSubDriver`, respectively, that handle the driving of data onto the `SpiInterface`. + +An `SpiMonitor` is also included, which implements the standard `Monitor` and provides a stream of `SpiPacket`s monitored on positive edges of the clock. The `SpiTracker` 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 `SpiChecker` monitors an `SpiInterface` for a subset of the rules commonly used in SPI implementations. Errors are flagged using the `severe` log messages, as is standard for errors in ROHD-VF. + +The unit tests in `spi_bfm_test.dart`, which have a main and sub communicating with each other, are a good example for setting up the SPI BFM. The unit test in `spi_gaskets_test` also have good example of the SPI BFM interacting with their corresponding hardware components. + +## Unsupported features + +The following features are currently not supported by or have no utilities within the BFM: + +- **CPOL/CPHA**: different clock polarity and clock phase are not considered, the BFM is implemented with a SPI Mode = 0 (CPOL/CPHA = 0). diff --git a/doc/components/spi_gaskets.md b/doc/components/spi_gaskets.md new file mode 100644 index 000000000..c2b5848f3 --- /dev/null +++ b/doc/components/spi_gaskets.md @@ -0,0 +1,37 @@ +# SPI Gaskets + +ROHD-HCL implements a `SpiMain` and `SpiSub` set of components that enable communication via a Serial Peripheral Interface. + +## SpiMain + +Interacts as the provider on the `SpiInterface`. + +The inputs to the `SpiMain` component are: + +* `clk` => clock for synchronous logic and driving `SpiInterface.sclk` +* `reset` => asynchronous reset for component and to reset `busIn` values +* `start` => to initiate a data transfer +* `busIn` => to load data to transmit + +The outputs to the `SpiSub` component are: + +* `busOut` => to output data received +* `done` => signals completion of a data transfer + +When data is available on `busIn`, pulsing `reset` will load the data into the internal shift register. Pulsing `start` will make `SpiInterface.csb` active and begin driving a clock signal on `SpiInterface.sclk`. On every clock pulse data will shift out onto `SpiInterface.mosi` and shift in from `SpiInterface.miso`. Data shifted in will be avaible on `busOut`. The `done` signal will indicate when transmissions are complete. + +## SpiSub + +Interacts as the consumer on the `SpiInterface`. + +The inputs to the `SpiSub` component are: + +* `reset` => optional input, asynchronous reset for component and to reset `busIn` values +* `busIn` => optional input, to load data to transmit + +The outputs to the `SpiSub` component are: + +* `busOut` => to output data received +* `done` => signals completion of a data transfer + +When data is available on `busIn`, pulsing `reset` will load the data into the internal shift register. When `SpiInterface.csb` is active and clock signal is present on `SpiInterface.sclk`, data will shift out onto `SpiInterface.miso` and shift in from `SpiInterface.mosi`. Data shifted in will be avaible on `busOut`. The `done` signal will indicate when transmissions are complete. diff --git a/doc/components/standard_interfaces.md b/doc/components/standard_interfaces.md index 8bb3ddb49..8768c8d11 100644 --- a/doc/components/standard_interfaces.md +++ b/doc/components/standard_interfaces.md @@ -5,3 +5,7 @@ ROHD-HCL provides a set of standard interfaces using ROHD `Interface`s. This ma ## APB The [ABP Interface](https://developer.arm.com/documentation/ihi0024/latest/) is a standard AMBA interface. ROHD HCL has a configurable version of the APB interface called [`ApbInterface`](https://intel.github.io/rohd-hcl/rohd_hcl/ApbInterface-class.html). + +## SPI + +The Serial Peripheral Interface (SPI) is a common serial communicaton interface. ROHD HCL has a configurable version of the SPI interface called [`SpiInterface`](https://intel.github.io/rohd-hcl/rohd_hcl/SpiInterface-class.html). diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index b12b6ead1..f6fb5734b 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2023-2025 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'src/arbiters/arbiters.dart'; @@ -14,6 +14,7 @@ export 'src/exceptions.dart'; export 'src/extrema.dart'; export 'src/fifo.dart'; export 'src/find.dart'; +export 'src/gaskets/spi/spi_gaskets.dart'; export 'src/interfaces/interfaces.dart'; export 'src/memory/memories.dart'; export 'src/models/models.dart'; diff --git a/lib/src/gaskets/spi/spi_gaskets.dart b/lib/src/gaskets/spi/spi_gaskets.dart new file mode 100644 index 000000000..8660514dc --- /dev/null +++ b/lib/src/gaskets/spi/spi_gaskets.dart @@ -0,0 +1,5 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause + +export 'spi_main.dart'; +export 'spi_sub.dart'; diff --git a/lib/src/gaskets/spi/spi_main.dart b/lib/src/gaskets/spi/spi_main.dart new file mode 100644 index 000000000..4bf2a4d23 --- /dev/null +++ b/lib/src/gaskets/spi/spi_main.dart @@ -0,0 +1,101 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_main.dart +// Implementation of SPI Main component. +// +// 2024 October 1 +// Author: Roberto Torres + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// Main component for Serial Peripheral Interface (SPI). +class SpiMain extends Module { + /// Output bus from Main. + Logic get busOut => output('busOut'); + + /// Done signal from Main. + Logic get done => output('done'); + + /// Creates a SPI Main component that interfaces with [SpiInterface]. + /// + /// The SPI Main component will drive a clock signal on [SpiInterface.sclk], + /// chip select on [SpiInterface.csb], shift data out on [SpiInterface.mosi], + /// and shift data in from [SpiInterface.miso]. Data to shift out is provided + /// on [busIn]. Data shifted in from [SpiInterface.miso] will be available on + /// [busOut]. After data is available on [busIn], pulsing [reset] will load + /// the data, and pulsing [start] will begin transmitting data until all bits + /// from [busIn] are shifted out. After transmissions is complete [done] + /// signal will go high. + SpiMain(SpiInterface intf, + {required Logic clk, + required Logic reset, + required Logic start, + required Logic busIn, + super.name = 'spiMain'}) { + busIn = addInput('busIn', busIn, width: busIn.width); + + clk = addInput('clk', clk); + + reset = addInput('reset', reset); + + start = addInput('start', start); + + addOutput('busOut', width: busIn.width); + + addOutput('done'); + + intf = SpiInterface.clone(intf) + ..pairConnectIO(this, intf, PairRole.provider); + + final isRunning = Logic(name: 'isRunning'); + + // Counter to track of the number of bits shifted out. + final count = Counter.simple( + clk: ~clk, + enable: start | isRunning, + reset: reset, + asyncReset: true, + minValue: 1, + maxValue: busIn.width); + + // Done signal will be high when the counter is at the max value. + done <= count.equalsMax; + + // isRunning will be high when start is pulsed high or counter is not done. + isRunning <= + flop( + clk, + start | ~done, + reset: reset, + asyncReset: true, + ); + + // Shift register in from MISO. + // NOTE: Reset values are set to busIn values. + final shiftReg = ShiftRegister( + intf.miso, + clk: intf.sclk, + depth: intf.dataLength, + reset: reset, + asyncReset: true, + resetValue: busIn.elements, + ); + + // busOut bits are connected to the corresponding shift register data stage. + // NOTE: dataStage0 corresponds to the last bit shifted in. + busOut <= shiftReg.stages.rswizzle(); + + // SCLK runs off clk when isRunning is true or start is pulsed high. + intf.sclk <= ~clk & (isRunning | start); + + // CS is active low. It will go low when isRunning or start is pulsed high. + intf.csb <= ~(isRunning | start); + + // MOSI is connected shift register dataOut. + intf.mosi <= + flop(~intf.sclk, shiftReg.dataOut, + reset: reset, asyncReset: true, resetValue: busIn[-1]); + } +} diff --git a/lib/src/gaskets/spi/spi_sub.dart b/lib/src/gaskets/spi/spi_sub.dart new file mode 100644 index 000000000..d590e6dba --- /dev/null +++ b/lib/src/gaskets/spi/spi_sub.dart @@ -0,0 +1,93 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_sub.dart +// Implementation of SPI Sub component. +// +// 2024 October 4 +// Author: Roberto Torres + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; + +/// Sub component for Serial Peripheral Interface (SPI). +class SpiSub extends Module { + /// Output bus from Sub. + Logic get busOut => output('busOut'); + + /// Done signal from Sub. + Logic get done => output('done'); + + /// Creates a SPI Sub component that interfaces with [SpiInterface]. + /// + /// The SPI Sub component will enable via chip select from [SpiInterface.csb]. + /// Clock signal will be received on [SpiInterface.sclk], data will shift in + /// from [SpiInterface.mosi], and shift data out from [SpiInterface.miso]. + /// Data shifted in from [SpiInterface.mosi] will be available on [busOut]. + /// + /// If [busIn] and [reset] are provided, data to shift out will be loaded from + /// [busIn]. After data is available on [busIn], pulsing [reset] will load the + /// data asynchronously, and a bit of data will be transmitted per pulse of + /// [SpiInterface.sclk]. After all data is shifted out, an optional [done] + /// signal will indicate completion. + SpiSub( + {required SpiInterface intf, + Logic? busIn, + Logic? reset, + super.name = 'spiSub'}) { + // SPI Interface + intf = SpiInterface.clone(intf) + ..pairConnectIO(this, intf, PairRole.consumer); + + // Bus Input to sub, if provided. + if (busIn != null) { + busIn = addInput('busIn', busIn, width: intf.dataLength); + } + + // Reset signal for sub, if provided. + if (reset != null) { + reset = addInput('reset', reset); + } + + // Bus Output from Sub + addOutput('busOut', width: intf.dataLength); + + addOutput('done'); + + // Counter to track of the number of bits shifted out. + final count = Counter.simple( + clk: intf.sclk, + enable: ~intf.csb, + reset: reset ?? Const(0, width: 1), + asyncReset: true, + minValue: 1, + maxValue: intf.dataLength); + + // Done signal will be high when the counter is at the max value. + done <= count.equalsMax; + + // Shift Register in from MOSI. + // NOTE: Reset values are set to busIn values. + final shiftReg = ShiftRegister( + intf.mosi, + enable: ~intf.csb, + clk: intf.sclk, + depth: intf.dataLength, + reset: reset, + asyncReset: true, + resetValue: busIn?.elements, + ); + + // busOut bits are connected to the corresponding shift register data stage. + // NOTE: dataStage0 corresponds to the last bit shifted in. + busOut <= shiftReg.stages.rswizzle(); + + // MISO is connected to shift register dataOut. + intf.miso <= + flop(~intf.sclk, shiftReg.dataOut, + en: ~intf.csb, + reset: reset, + asyncReset: true, + resetValue: busIn?[-1]); + } +} diff --git a/lib/src/interfaces/interfaces.dart b/lib/src/interfaces/interfaces.dart index 45914619e..39acc9bc7 100644 --- a/lib/src/interfaces/interfaces.dart +++ b/lib/src/interfaces/interfaces.dart @@ -2,3 +2,4 @@ // SPDX-License-Identifier: BSD-3-Clause export 'apb.dart'; +export 'spi.dart'; diff --git a/lib/src/interfaces/spi.dart b/lib/src/interfaces/spi.dart new file mode 100644 index 000000000..bc29c04ee --- /dev/null +++ b/lib/src/interfaces/spi.dart @@ -0,0 +1,39 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi.dart +// Definitions for Serial Peripheral Interface (SPI). +// +// 2024 September 23 +// Author: Roberto Torres + +import 'package:rohd/rohd.dart'; + +/// A standard Serial Peripheral Interface. +class SpiInterface extends PairInterface { + /// The data length for serial transmissions on this interface. + final int dataLength; + + /// Serial clock (SCLK). Clock signal from main to sub(s). + Logic get sclk => port('SCLK'); + + /// Main Out Sub In (MOSI). Serial data from main to sub(s). + Logic get mosi => port('MOSI'); + + /// Main In Sub Out (MISO). Serial data from sub(s) to main. + Logic get miso => port('MISO'); + + /// Chip select (active low). Chip select signal from main to sub. + Logic get csb => port('CSB'); + + /// Creates a new [SpiInterface]. + SpiInterface({this.dataLength = 1}) + : super( + portsFromConsumer: [Port('MISO')], + portsFromProvider: [Port('MOSI'), Port('CSB'), Port('SCLK')]); + + /// Clones this [SpiInterface]. + SpiInterface.clone(SpiInterface super.otherInterface) + : dataLength = otherInterface.dataLength, + super.clone(); +} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 617bf5ab5..c94f744a8 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -5,3 +5,4 @@ export 'apb_bfm/apb_bfm.dart'; export 'memory_model.dart'; export 'ready_valid_bfm/ready_valid_bfm.dart'; export 'sparse_memory_storage.dart'; +export 'spi_bfm/spi_bfm.dart'; diff --git a/lib/src/models/spi_bfm/spi_bfm.dart b/lib/src/models/spi_bfm/spi_bfm.dart new file mode 100644 index 000000000..8c4724f3a --- /dev/null +++ b/lib/src/models/spi_bfm/spi_bfm.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_bfm.dart +// BFM for SPI interface +// +// 2024 September 23 +// Author: Roberto Torres + +export 'spi_checker.dart'; +export 'spi_main_agent.dart'; +export 'spi_main_driver.dart'; +export 'spi_monitor.dart'; +export 'spi_packet.dart'; +export 'spi_sub_agent.dart'; +export 'spi_sub_driver.dart'; +export 'spi_tracker.dart'; diff --git a/lib/src/models/spi_bfm/spi_checker.dart b/lib/src/models/spi_bfm/spi_checker.dart new file mode 100644 index 000000000..eeefd4512 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_checker.dart @@ -0,0 +1,50 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_checker.dart +// Implementation of SPI Checker component. +// +// 2025 January 22 +// Author: Roberto Torres + +import 'dart:async'; +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// Checker component for Serial Peripheral Interface (SPI). +class SpiChecker extends Component { + /// Interface to check. + final SpiInterface intf; + + /// Creates a SPI Checker component that interfaces with [SpiInterface]. + SpiChecker( + this.intf, { + required Component parent, + String name = 'spiChecker', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + LogicValue? mosiVal; + LogicValue? misoVal; + + // Save the value of mosi and miso on posedge + intf.sclk.posedge.listen((event) { + mosiVal = intf.mosi.value; + misoVal = intf.miso.value; + }); + + // checking prev value at negedge + intf.sclk.negedge.listen((event) { + if (misoVal != null && misoVal != intf.miso.previousValue) { + logger.severe('Data on MISO is changing on posedge of sclk'); + } + if (mosiVal != null && mosiVal != intf.mosi.previousValue) { + logger.severe('Data on MOSI is changing on posedge of sclk'); + } + }); + } +} diff --git a/lib/src/models/spi_bfm/spi_main_agent.dart b/lib/src/models/spi_bfm/spi_main_agent.dart new file mode 100644 index 000000000..b2ab63f1b --- /dev/null +++ b/lib/src/models/spi_bfm/spi_main_agent.dart @@ -0,0 +1,55 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_main_agent.dart +// An agent for the main side of the SPI interface. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// An agent for the main side of the [SpiInterface]. +class SpiMainAgent extends Agent { + /// The interface to drive. + final SpiInterface intf; + + /// The sequencer + late final Sequencer sequencer; + + /// The driver that sends packets. + late final SpiMainDriver driver; + + /// The monitor that watches the interface. + late final SpiMonitor monitor; + + /// The number of cycles before dropping an objection. + final int dropDelayCycles; + + /// Creates a new [SpiMainAgent]. + SpiMainAgent({ + required this.intf, + required Component parent, + required Logic clk, + String name = 'spiMain', + this.dropDelayCycles = 30, + }) : super(name, parent) { + sequencer = Sequencer('sequencer', this); + + driver = SpiMainDriver( + parent: this, + intf: intf, + clk: clk, + sequencer: sequencer, + dropDelayCycles: dropDelayCycles, + ); + + monitor = SpiMonitor( + parent: this, + direction: SpiDirection.sub, + intf: intf, + ); + } +} diff --git a/lib/src/models/spi_bfm/spi_main_driver.dart b/lib/src/models/spi_bfm/spi_main_driver.dart new file mode 100644 index 000000000..92004a994 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_main_driver.dart @@ -0,0 +1,76 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_main_driver.dart +// A driver for SPI Main. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the main side of the [SpiInterface]. +/// +/// Driven packets will update the returned data into the same packet. +class SpiMainDriver extends PendingClockedDriver { + /// The interface to drive. + final SpiInterface intf; + + /// Creates a new [SpiMainDriver]. + SpiMainDriver({ + required Component parent, + required this.intf, + required super.clk, + required super.sequencer, + super.dropDelayCycles = 30, + String name = 'spiMainDriver', + }) : super(name, parent) { + intf.sclk <= ~clk & _clkenable; + _clkenable.inject(0); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + Simulator.injectAction(() { + intf.csb.put(1); + intf.mosi.put(0); + }); + + while (!Simulator.simulationHasEnded) { + if (pendingSeqItems.isNotEmpty) { + await _drivePacket(pendingSeqItems.removeFirst()); + } else { + await clk.nextNegedge; + Simulator.injectAction(() { + intf.csb.put(1); + _clkenable.inject(0); + intf.mosi.put(0); + }); + } + } + } + + /// Clock enable signal. + final _clkenable = Logic(name: 'clkenable'); + + /// Drives a packet onto the interface. + Future _drivePacket(SpiPacket packet) async { + intf.csb.inject(0); + + // Loop through the bits of the packet + for (var i = 1; i <= packet.data.width; i++) { + intf.mosi.inject(packet.data[-i]); + await clk.nextNegedge; + _clkenable.inject(1); + + // Wait for the next clock cycle + await clk.nextPosedge; + } + } +} diff --git a/lib/src/models/spi_bfm/spi_monitor.dart b/lib/src/models/spi_bfm/spi_monitor.dart new file mode 100644 index 000000000..61aab62d1 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_monitor.dart @@ -0,0 +1,60 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_monitor.dart +// A monitor that watches the SPI interface. +// +// 2024 September 23 +// Author: Roberto Torres + +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 [SpiInterface]s. +class SpiMonitor extends Monitor { + /// The interface to watch. + final SpiInterface intf; + + /// The direction to monitor. If null, monitors both directions. + final SpiDirection? direction; + + /// Creates a new [SpiMonitor] for [intf]. + SpiMonitor( + {required this.intf, + required Component parent, + this.direction, + String name = 'spiMonitor'}) + : super(name, parent); + + /// Run function. + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final dataListRead = []; + final dataListWrite = []; + + intf.sclk.posedge.listen((event) { + if (direction == null || direction == SpiDirection.main) { + dataListWrite.add(intf.mosi.previousValue!); + } + if (direction == null || direction == SpiDirection.sub) { + dataListRead.add(intf.miso.previousValue!); + } + + if (dataListWrite.length == intf.dataLength) { + add(SpiPacket( + data: dataListWrite.swizzle(), direction: SpiDirection.main)); + dataListWrite.clear(); + } + if (dataListRead.length == intf.dataLength) { + add(SpiPacket( + data: dataListRead.swizzle(), direction: SpiDirection.sub)); + dataListRead.clear(); + } + }); + } +} diff --git a/lib/src/models/spi_bfm/spi_packet.dart b/lib/src/models/spi_bfm/spi_packet.dart new file mode 100644 index 000000000..18d5a7b29 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_packet.dart @@ -0,0 +1,58 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_packet.dart +// Packet the SPI interface. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// Direction of the packet. +enum SpiDirection { + /// Main direction. + main, + + /// Sub direction. + sub +} + +/// A packet for the [SpiInterface]. +class SpiPacket extends SequenceItem implements Trackable { + /// The data in the packet. + final LogicValue data; + + /// Direction of the packet. + final SpiDirection? direction; + + /// Creates a new packet. + SpiPacket({required this.data, this.direction}); + + /// A [Future] that completes once 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() { + _completer.complete(); + } + + @override + String? trackerString(TrackerField field) { + switch (field.title) { + case SpiTracker.timeField: + return Simulator.time.toString(); + case SpiTracker.typeField: + return direction?.name; + case SpiTracker.dataField: + return data.toString(); + } + + return null; + } +} diff --git a/lib/src/models/spi_bfm/spi_sub_agent.dart b/lib/src/models/spi_bfm/spi_sub_agent.dart new file mode 100644 index 000000000..a1c347181 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_sub_agent.dart @@ -0,0 +1,47 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_sub_agent.dart +// An agent for the sub side of the SPI interface. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A model for the sub side of the SPI interface. +class SpiSubAgent extends Agent { + /// The interface to drive. + final SpiInterface intf; + + /// The sequencer + late final Sequencer sequencer; + + /// The driver that sends packets. + late final SpiSubDriver driver; + + /// The monitor that watches the interface. + late final SpiMonitor monitor; + + /// Creates a new [SpiSubAgent]. + SpiSubAgent({ + required this.intf, + required Component parent, + String name = 'spiSub', + }) : super(name, parent) { + sequencer = Sequencer('sequencer', this); + + driver = SpiSubDriver( + parent: this, + intf: intf, + sequencer: sequencer, + ); + + monitor = SpiMonitor( + parent: this, + direction: SpiDirection.main, + intf: intf, + ); + } +} diff --git a/lib/src/models/spi_bfm/spi_sub_driver.dart b/lib/src/models/spi_bfm/spi_sub_driver.dart new file mode 100644 index 000000000..2e03e6f3b --- /dev/null +++ b/lib/src/models/spi_bfm/spi_sub_driver.dart @@ -0,0 +1,78 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_sub_driver.dart +// A driver for SPI Sub. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'dart:async'; + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A driver for the sub side of the [SpiInterface]. +class SpiSubDriver extends PendingDriver { + /// The interface to drive. + final SpiInterface intf; + + /// Creates a new [SpiSubDriver]. + SpiSubDriver({ + required Component parent, + required this.intf, + required super.sequencer, + String name = 'spiSubDriver', + }) : super(name, parent); + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + intf.miso.inject(0); + + intf.csb.negedge.listen((_) { + _packetHandler(loadOnly: true); + }); + + intf.sclk.negedge.listen((_) { + _packetHandler(loadOnly: false); + }); + } + + /// The pending packet. + SpiPacket? _packet; + + /// The index of the data. + int? _dataIndex; + + // Function handles the packet. + void _packetHandler({required bool loadOnly}) { + if (pendingSeqItems.isNotEmpty) { + _packet = pendingSeqItems.removeFirst(); + if (loadOnly) { + _dataIndex = _packet!.data.width - 1; + } else { + _dataIndex = _packet!.data.width; + } + } + if (_packet != null) { + if (loadOnly) { + intf.miso.inject(_packet!.data[_dataIndex!]); + } else { + _dataIndex = _dataIndex! - 1; + if (_dataIndex! > -1) { + intf.miso.inject(_packet!.data[_dataIndex!]); + } + } + + if (_dataIndex! <= -1) { + _packet = null; + _dataIndex = null; + _packetHandler(loadOnly: loadOnly); + } + } else { + intf.miso.inject(0); + } + } +} diff --git a/lib/src/models/spi_bfm/spi_tracker.dart b/lib/src/models/spi_bfm/spi_tracker.dart new file mode 100644 index 000000000..1453f3d78 --- /dev/null +++ b/lib/src/models/spi_bfm/spi_tracker.dart @@ -0,0 +1,42 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_tracker.dart +// A monitor that watches the SPI interface. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A tracker for [SpiInterface]. +class SpiTracker extends Tracker { + /// The interface to watch. + final SpiInterface intf; + + /// Tracker field for simulation time. + static const timeField = 'time'; + + /// Tracker field for type from: Main or Sub. + static const typeField = 'from'; + + /// Tracker field for data. + static const dataField = 'data'; + + /// Creates a new tracker for [SpiInterface]. + SpiTracker({ + required this.intf, + String name = 'spiTracker', + super.dumpJson, + super.dumpTable, + super.outputFolder, + int timeColumnWidth = 8, + int dataColumnWidth = 8, + int typeColumnWidth = 8, + }) : super(name, [ + TrackerField(timeField, columnWidth: timeColumnWidth), + TrackerField(typeField, columnWidth: typeColumnWidth), + TrackerField(dataField, columnWidth: dataColumnWidth), + ]); +} diff --git a/lib/src/shift_register.dart b/lib/src/shift_register.dart index ac4acc268..63b1f9520 100644 --- a/lib/src/shift_register.dart +++ b/lib/src/shift_register.dart @@ -38,11 +38,12 @@ class ShiftRegister extends Module { final String dataName; /// Creates a new shift register with specified [depth] which is only active - /// when [enable]d. If [reset] is provided, it will reset synchronously with - /// [clk] or aynchronously if [asyncReset] is true. The [reset] will reset all - /// stages to a default of `0` or to the provided [resetValue]. - /// If [resetValue] is a [List] the stages will reset to the corresponding - /// value in the list. + /// when [enable]d. + /// + /// If [reset] is provided, it will reset synchronously with [clk] or + /// aynchronously if [asyncReset] is true. The [reset] will reset all stages + /// to a default of `0` or to the provided [resetValue]. If [resetValue] is + /// a [List] the stages will reset to the corresponding value in the list. ShiftRegister( Logic dataIn, { required Logic clk, @@ -77,6 +78,8 @@ class ShiftRegister extends Module { 'ResetValue list length must equal shift register depth.'); } + resetValue = List.of(resetValue); + for (var i = 0; i < resetValue.length; i++) { final element = resetValue[i]; if (element is Logic) { @@ -108,9 +111,10 @@ class ShiftRegister extends Module { } Sequential.multi( - [clk, if (asyncReset && reset != null) reset], + [clk], reset: reset, resetValues: resetValues, + asyncReset: asyncReset, conds, ); diff --git a/lib/src/summation/counter.dart b/lib/src/summation/counter.dart index 785f892a5..b7480e649 100644 --- a/lib/src/summation/counter.dart +++ b/lib/src/summation/counter.dart @@ -27,6 +27,9 @@ class Counter extends SummationBase { @protected late final Logic reset; + /// Whether the [reset] is asynchronous. + final bool asyncReset; + /// The restart signal. @protected late final Logic? restart; @@ -58,6 +61,7 @@ class Counter extends SummationBase { required Logic reset, Logic? restart, dynamic resetValue = 0, + this.asyncReset = false, super.maxValue, super.minValue = 0, super.width, @@ -66,7 +70,6 @@ class Counter extends SummationBase { }) : super(initialValue: resetValue) { this.clk = addInput('clk', clk); this.reset = addInput('reset', reset); - if (restart != null) { this.restart = addInput('restart', restart); } else { @@ -95,8 +98,10 @@ class Counter extends SummationBase { buildFlops(); // need to flop these since value is flopped - overflowed <= flop(clk, summer.overflowed, reset: reset); - underflowed <= flop(clk, summer.underflowed, reset: reset); + overflowed <= + flop(clk, summer.overflowed, reset: reset, asyncReset: asyncReset); + underflowed <= + flop(clk, summer.underflowed, reset: reset, asyncReset: asyncReset); equalsMax <= count.eq(maxValueLogic); equalsMin <= count.eq(minValueLogic); @@ -111,6 +116,7 @@ class Counter extends SummationBase { summer.sum, reset: reset, resetValue: initialValueLogic, + asyncReset: asyncReset, ); } @@ -128,6 +134,7 @@ class Counter extends SummationBase { Logic? restart, bool saturates = false, bool increments = true, + bool asyncReset = false, int resetValue = 0, String name = 'counter', }) : this([ @@ -141,6 +148,7 @@ class Counter extends SummationBase { clk: clk, reset: reset, resetValue: resetValue, + asyncReset: asyncReset, restart: restart, maxValue: maxValue, minValue: minValue, @@ -165,6 +173,7 @@ class Counter extends SummationBase { Logic? enable, int? width, bool saturates = false, + bool asyncReset = false, String name = 'counter', }) => Counter( @@ -176,6 +185,7 @@ class Counter extends SummationBase { clk: clk, reset: reset, resetValue: resetValue, + asyncReset: asyncReset, maxValue: maxValue, minValue: minValue, width: width, diff --git a/test/spi/spi_bfm_test.dart b/test/spi/spi_bfm_test.dart new file mode 100644 index 000000000..b78f71d51 --- /dev/null +++ b/test/spi/spi_bfm_test.dart @@ -0,0 +1,113 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_bfm_test.dart +// Tests for the SPI BFM. +// +// 2024 September 23 +// Author: Roberto Torres + +import 'dart:async'; +import 'dart:io'; + +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 SpiMod extends Module { + SpiMod(SpiInterface intf, {super.name = 'SpiModIntf'}) { + intf = SpiInterface.clone(intf) + ..connectIO(this, intf, + inputTags: [PairDirection.fromProvider, PairDirection.fromConsumer]); + } +} + +class SpiBfmTest extends Test { + late final SpiInterface intf; + late final SpiMainAgent main; + late final SpiSubAgent sub; + late final SpiMonitor monitor; + + String get outFolder => 'tmp_test/spiBfm/$name/'; + + SpiBfmTest(super.name) : super() { + intf = SpiInterface(dataLength: 8); + + final clk = SimpleClockGenerator(10).clk; + + main = SpiMainAgent(intf: intf, parent: this, clk: clk); + + sub = SpiSubAgent(intf: intf, parent: this); + + monitor = SpiMonitor(intf: intf, parent: this); + + Directory(outFolder).createSync(recursive: true); + + final tracker = + SpiTracker(intf: intf, dumpTable: false, outputFolder: outFolder); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + }); + + monitor.stream.listen(tracker.record); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('spiBfmTestObj'); + + main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0xCB, 8))); //0b1100 1011 = 203 + + main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0x00, 8))); + + unawaited(monitor.stream + .where((event) => + event.direction == SpiDirection.main && event.data.toInt() == 0xCB) + .first + .then((_) { + sub.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0x1B, 8))); //0b0001 1011 = 27 + })); + + var packetReceived = false; + + await monitor.stream + .where((event) => + event.direction == SpiDirection.sub && event.data.toInt() == 0x1B) + .first + .then((_) { + packetReceived = true; + }); + + expect(packetReceived, true); + + obj.drop(); + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + Future runTest(SpiBfmTest spiBfmTest, {bool dumpWaves = true}) async { + Simulator.setMaxSimTime(3000); + + if (dumpWaves) { + final mod = SpiMod(spiBfmTest.intf); + await mod.build(); + WaveDumper(mod, outputPath: '${spiBfmTest.outFolder}/waves.vcd'); + } + + await spiBfmTest.start(); + } + + test('simple transfers', () async { + await runTest(SpiBfmTest('simple')); + }); +} diff --git a/test/spi/spi_gaskets_test.dart b/test/spi/spi_gaskets_test.dart new file mode 100644 index 000000000..f5b8d1149 --- /dev/null +++ b/test/spi/spi_gaskets_test.dart @@ -0,0 +1,657 @@ +// Copyright (C) 2024-2025 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_gaskets_test.dart +// Tests for SPI gaskets, main and sub. +// +// 2024 October 10 +// Author: Roberto Torres + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +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 SpiMainTest extends Test { + late final SpiInterface intf; + late final SpiSubAgent sub; + late final SpiMonitor monitor; + late final SpiMain main; + late final Logic reset; + late final Logic starts; + late final Logic clk; + late final Logic busInMain; + + String get outFolder => 'tmp_test/spiMain/$name/'; + + final Future Function(SpiMainTest test) stimulus; + + SpiMainTest(this.stimulus, super.name) : super() { + intf = SpiInterface(dataLength: 8); + + sub = SpiSubAgent(intf: intf, parent: this); + + monitor = SpiMonitor(intf: intf, parent: this); + + Directory(outFolder).createSync(recursive: true); + + final tracker = + SpiTracker(intf: intf, dumpTable: false, outputFolder: outFolder); + + SpiChecker(intf, parent: this); + + clk = SimpleClockGenerator(10).clk; + + // initialize the bus with 00 + busInMain = Logic(width: 8)..inject(0x00); + reset = Logic(); + starts = Logic(); + + main = + SpiMain(intf, busIn: busInMain, clk: clk, reset: reset, start: starts); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + const numTransfers = 4; + final jsonStr = + File('$outFolder/spiTracker.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); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('SpiMainTestObj'); + + // reset flow + await clk.waitCycles(1); + reset.inject(true); + starts.inject(false); + await clk.waitCycles(1); + reset.inject(false); + await clk.waitCycles(1); + await stimulus(this); + + obj.drop(); + } +} + +class SpiSubTest extends Test { + late final SpiInterface intf; + late final SpiMainAgent main; + late final SpiMonitor monitor; + late final SpiSub sub; + late final Logic reset; + late final Logic clk; + late final Logic busIn; + + String get outFolder => 'tmp_test/spiSub/$name/'; + + final Future Function(SpiSubTest test) stimulus; + + SpiSubTest(this.stimulus, super.name) : super() { + intf = SpiInterface(dataLength: 8); + + clk = SimpleClockGenerator(10).clk; + + main = SpiMainAgent(intf: intf, parent: this, clk: clk); + + monitor = SpiMonitor(intf: intf, parent: this); + + busIn = Logic(width: 8); + + reset = Logic(); + + sub = SpiSub(intf: intf, busIn: busIn, reset: reset); + + Directory(outFolder).createSync(recursive: true); + + final tracker = + SpiTracker(intf: intf, dumpTable: false, outputFolder: outFolder); + + SpiChecker(intf, parent: this); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + Directory(outFolder).deleteSync(recursive: true); + }); + + monitor.stream.listen(tracker.record); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('SpiSubTestObj'); + + // reset flow + busIn.inject(0x00); + reset.inject(false); + await clk.waitCycles(1); + reset.inject(true); + await clk.waitCycles(1); + reset.inject(false); + await stimulus(this); + + obj.drop(); + } +} + +class SpiTop extends Module { + SpiTop(SpiInterface intf, SpiSubTest? test, {super.name = 'spiTop'}) { + addOutput('dummy') <= intf.sclk; + if (test != null) { + addOutput('clk') <= test.clk; + } + } +} + +class SpiPairTest extends Test { + late final SpiInterface intf; + late final SpiMonitor monitor; + late final SpiMain main; + late final SpiSub sub; + late final Logic clk; + late final Logic resetMain; + late final Logic resetSub; + late final Logic busInMain; + late final Logic busInSub; + late final Logic starts; + + String get outFolder => 'tmp_test/spiPair/$name/'; + + final Future Function(SpiPairTest test) stimulus; + + SpiPairTest(this.stimulus, super.name) : super() { + intf = SpiInterface(dataLength: 8); + + monitor = SpiMonitor(intf: intf, parent: this); + + Directory(outFolder).createSync(recursive: true); + + final tracker = + SpiTracker(intf: intf, dumpTable: false, outputFolder: outFolder); + + SpiChecker(intf, parent: this); + + clk = SimpleClockGenerator(10).clk; + + // initialize main + resetMain = Logic(); + busInMain = Logic(width: 8); + starts = Logic(); + + main = SpiMain(intf, + busIn: busInMain, clk: clk, reset: resetMain, start: starts); + + //init sub + resetSub = Logic(); + busInSub = Logic(width: 8); + sub = SpiSub(intf: intf, busIn: busInSub, reset: resetSub); + + Simulator.registerEndOfSimulationAction(() async { + await tracker.terminate(); + Directory(outFolder).deleteSync(recursive: true); + }); + + monitor.stream.listen(tracker.record); + } + + @override + Future run(Phase phase) async { + unawaited(super.run(phase)); + + final obj = phase.raiseObjection('SpiPairTestObj'); + + // Initialize all inputs to initial state. + // Just for waveform clarity. + await clk.waitCycles(1); + + busInMain.inject(00); + busInSub.inject(00); + + starts.inject(false); + resetMain.inject(false); + resetSub.inject(false); + + await clk.waitCycles(1); + resetMain.inject(true); + resetSub.inject(true); + + await clk.waitCycles(1); + resetMain.inject(false); + resetSub.inject(false); + + await clk.waitCycles(1); + await stimulus(this); + + obj.drop(); + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + group('main gasket tests', () { + Future runMainTest(SpiMainTest spiMainTest, + {bool dumpWaves = true}) async { + Simulator.setMaxSimTime(3000); + + if (dumpWaves) { + await spiMainTest.main.build(); + WaveDumper(spiMainTest.main, + outputPath: '${spiMainTest.outFolder}/waves.vcd'); + } + await spiMainTest.start(); + } + + test('simple transfers no gap', () async { + await runMainTest(SpiMainTest((test) async { + Future sendMainData(SpiMainTest test, int data) async { + test.busInMain.inject(LogicValue.ofInt(data, test.intf.dataLength)); + test.reset.inject(true); + await test.clk.nextNegedge; + test.reset.inject(false); + test.starts.inject(true); + await test.clk.waitCycles(1); + test.starts.inject(false); + await test.clk.waitCycles(6); + await test.clk.nextPosedge; + } + + Future sendSubPacket(SpiMainTest test, LogicValue data) async { + test.sub.sequencer.add(SpiPacket(data: data)); + await sendMainData(test, 0x00); + } + + var clkCount = 0; + test.clk.negedge.listen((event) { + clkCount++; + }); + final txPeriod = test.intf.dataLength; + + await sendSubPacket(test, LogicValue.ofInt(0x72, 8)); + expect(test.main.busOut.value.toInt(), 0x72); + expect(clkCount, txPeriod); + + await sendSubPacket(test, LogicValue.ofInt(0xCD, 8)); + expect(test.main.busOut.value.toInt(), 0xCD); + expect(clkCount, 2 * txPeriod); + + await sendSubPacket(test, LogicValue.ofInt(0x56, 8)); + expect(test.main.busOut.value.toInt(), 0x56); + expect(clkCount, 3 * txPeriod); + + await sendSubPacket(test, LogicValue.ofInt(0xE2, 8)); + expect(test.main.busOut.value.toInt(), 0xE2); + expect(clkCount, 4 * txPeriod); + + await test.clk.waitCycles(4); + }, 'testMainA')); + }); + + test('simple transfers with gaps', () async { + await runMainTest(SpiMainTest((test) async { + Future sendMainData(SpiMainTest test, int data) async { + test.busInMain.inject(LogicValue.ofInt(data, test.intf.dataLength)); + test.reset.inject(true); + await test.clk.nextPosedge; + test.reset.inject(false); + test.starts.inject(true); + await test.clk.waitCycles(1); + test.starts.inject(false); + await test.clk.waitCycles(7); + } + + Future sendSubPacket(SpiMainTest test, LogicValue data) async { + test.sub.sequencer.add(SpiPacket(data: data)); + await sendMainData(test, 0x00); + } + + var clkCount = 0; + test.clk.negedge.listen((event) { + clkCount++; + }); + + final clkPeriod = test.intf.dataLength + 1; + await sendSubPacket(test, LogicValue.ofInt(0x72, 8)); + + expect(test.main.busOut.value.toInt(), 0x72); + expect(test.main.done.value.toBool(), true); + expect(clkCount, clkPeriod); + + await test.clk.waitCycles(3); + expect(clkCount, clkPeriod + 3); + + await sendSubPacket(test, LogicValue.ofInt(0xCD, 8)); // 1100 1101 + expect(test.main.busOut.value.toInt(), 0xCD); + expect(test.main.done.value.toBool(), true); + expect(clkCount, (2 * clkPeriod) + 3); + + await test.clk.waitCycles(4); + + await sendSubPacket(test, LogicValue.ofInt(0x56, 8)); + expect(test.main.busOut.value.toInt(), 0x56); + expect(test.main.done.value.toBool(), true); + expect(clkCount, (3 * clkPeriod) + 7); + + await test.clk.waitCycles(4); + + await sendSubPacket(test, LogicValue.ofInt(0xAB, 8)); + expect(test.main.busOut.value.toInt(), 0xAB); + expect(test.main.done.value.toBool(), true); + expect(clkCount, (4 * clkPeriod) + 11); + + await test.clk.waitCycles(4); + }, 'testMainB')); + }); + }); + + group('sub gasket tests', () { + Future runSubTest(SpiSubTest spiSubTest, + {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(3000); + final mod = SpiTop(spiSubTest.intf, spiSubTest); + if (dumpWaves) { + await mod.build(); + WaveDumper(mod, outputPath: '${spiSubTest.outFolder}/waves.vcd'); + } + + await spiSubTest.start(); + } + + test('sub tx busOut correctly no gaps', () async { + await runSubTest(SpiSubTest((test) async { + test.main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0xCD, 8))); // 1100 1101 + test.main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0x83, 8))); // 1000 0011 + test.main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0xE2, 8))); // 1110 0010 + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0x00, 8))); + + await test.clk.waitCycles(8); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xCD); + + await test.clk.waitCycles(7); + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x83); + + await test.clk.waitCycles(7); + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xE2); + + await test.clk.waitCycles(7); + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x00); + await test.clk.waitCycles(4); + }, 'testSubA')); + }); + + test('sub tx busOut correctly with gaps', () async { + await runSubTest(SpiSubTest((test) async { + test.main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0xCD, 8))); // 1100 1101 + + await test.clk.waitCycles(8); + expect(test.sub.done.value.toBool(), false); + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xCD); + expect(test.sub.done.value.toBool(), true); + //gap + await test.clk.waitCycles(7); + + test.main.sequencer + .add(SpiPacket(data: LogicValue.ofInt(0x72, 8))); // 0111 0010 + await test.clk.waitCycles(8); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x72); + + // gap + await test.clk.waitCycles(2); + + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0xAC, 8))); + await test.clk.waitCycles(8); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xAC); + + // gap + await test.clk.waitCycles(3); + + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0xE2, 8))); + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0x00, 8))); + await test.clk.waitCycles(8); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xE2); + + // waiting for the read packet + await test.clk.waitCycles(7); + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x00); + }, 'testSubA')); + }); + + test('sub tx with no gaps, busIn and reset injects', () async { + await runSubTest(SpiSubTest((test) async { + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0xCD, 8))); + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0x72, 8))); + + await test.clk.waitCycles(8); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0xCD); + + await test.clk.waitCycles(7); + + // read busOut here + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x72); + + // inject bus in on neg edge of same cycle + await test.clk.nextNegedge; + test.busIn.inject(0x19); + test.main.sequencer.add(SpiPacket(data: LogicValue.ofInt(0x00, 8))); + + // trigger reset + await test.clk.nextPosedge; + test.reset.inject(true); + await test.clk.waitCycles(1); + test.reset.inject(false); + + await test.clk.waitCycles(7); + + await test.clk.nextPosedge; + expect(test.sub.busOut.value.toInt(), 0x00); + + await test.clk.waitCycles(4); + }, 'testSubA')); + }); + }); + + group('pair of gaskets tests', () { + Future runPairTest(SpiPairTest spiPairTest, + {bool dumpWaves = false}) async { + Simulator.setMaxSimTime(3000); + final mod = SpiTop(spiPairTest.intf, null); + if (dumpWaves) { + await mod.build(); + WaveDumper(mod, outputPath: '${spiPairTest.outFolder}/waves.vcd'); + } + await spiPairTest.start(); + } + + Future sendMainData(SpiPairTest test, int data) async { + test.busInMain.inject(LogicValue.ofInt(data, test.intf.dataLength)); + await test.clk.nextNegedge; + test.resetMain.inject(true); + await test.clk.nextPosedge; + test.resetMain.inject(false); + test.starts.inject(true); + await test.clk.waitCycles(1); + test.starts.inject(false); + expect(test.sub.done.value.toBool(), false); + await test.clk.waitCycles(7); + } + + Future sendBothData(SpiPairTest test, + {required int mainData, required int subData}) async { + test.busInSub.inject(LogicValue.ofInt(subData, test.intf.dataLength)); + test.busInMain.inject(LogicValue.ofInt(mainData, test.intf.dataLength)); + await test.clk.nextNegedge; + test.resetSub.inject(true); + test.resetMain.inject(true); + await test.clk.nextPosedge; + test.resetSub.inject(false); + test.resetMain.inject(false); + test.starts.inject(true); + await test.clk.waitCycles(1); + test.starts.inject(false); + await test.clk.waitCycles(7); + } + + void checkMainBusOut(SpiPairTest test, int data) { + if (test.main.busOut.value.toInt() != data) { + test.logger.severe('main busOut: ${test.main.busOut.value}'); + } + } + + void checkSubBusOut(SpiPairTest test, int data) { + if (test.sub.busOut.value.toInt() != data) { + test.logger.severe('sub busOut: ${test.sub.busOut.value}'); + } + } + + test('main busIn injects, both busOut checks, no gaps', () async { + await runPairTest(SpiPairTest((test) async { + // Send main data. + await sendMainData(test, 0x73); // 0111 0011 + // Check both busOuts on posEdge of 8th sclk/ negEdge of 8th CLK + checkSubBusOut(test, 0x73); + checkMainBusOut(test, 0x00); + + // Send new main data. + await sendMainData(test, 0xCD); // 1100 1101 + + // check both busOuts, main should equal previous main busIn data + checkSubBusOut(test, 0xCD); + checkMainBusOut(test, 0x73); + + // Send new, check both busOuts + await sendMainData(test, 0xE2); // 1110 0010 + checkSubBusOut(test, 0xE2); + checkMainBusOut(test, 0xCD); + + // Send new, check both busOuts + await sendMainData(test, 0xB3); // 1011 0011 + checkSubBusOut(test, 0xB3); + checkMainBusOut(test, 0xE2); + + // Send new, check both busOuts + await sendMainData(test, 0x00); // 1011 0011 + checkSubBusOut(test, 0x00); + checkMainBusOut(test, 0xB3); + }, 'testPairA')); + }); + + test('main busIn injects, both busOut checks, with gaps', () async { + await runPairTest(SpiPairTest((test) async { + // Send main data. + await sendMainData(test, 0x73); // 0111 0011 + // Check both busOuts on posEdge of 8th sclk/ negEdge of 8th CLK + checkSubBusOut(test, 0x73); + checkMainBusOut(test, 0x00); + + // 1 cycle gap + await test.clk.waitCycles(1); + + // Send new main data. + await sendMainData(test, 0xCD); // 1100 1101 + + // check both busOuts, main should equal previous main busIn data + checkSubBusOut(test, 0xCD); + checkMainBusOut(test, 0x73); + await test.clk.waitCycles(1); + // Send new, check both busOuts + await sendMainData(test, 0xE2); // 1110 0010 + checkSubBusOut(test, 0xE2); + checkMainBusOut(test, 0xCD); + await test.clk.waitCycles(3); + // Send new, check both busOuts + await sendMainData(test, 0xB3); // 1011 0011 + checkSubBusOut(test, 0xB3); + checkMainBusOut(test, 0xE2); + + // with gaps + await test.clk.waitCycles(1); + + await sendMainData(test, 0x15); // 0001 0101 + checkSubBusOut(test, 0x15); + checkMainBusOut(test, 0xB3); + + await test.clk.waitCycles(4); + + await sendMainData(test, 0x2D); // 0010 1101 + checkSubBusOut(test, 0x2D); + checkMainBusOut(test, 0x15); + + await test.clk.waitCycles(6); + await sendMainData(test, 0x00); + checkSubBusOut(test, 0x00); + checkMainBusOut(test, 0x2D); + await test.clk.waitCycles(4); + }, 'testPairA')); + }); + + test('main and sub busIn, both busOut checks', () async { + await runPairTest(SpiPairTest((test) async { + // Send regular main data. + // var mainData = Random().nextInt(256); + // var subData = Random().nextInt(256); + await sendBothData(test, mainData: 0x73, subData: 0x00); // 0111 0011 + checkSubBusOut(test, 0x73); + checkMainBusOut(test, 0x00); + + await test.clk.waitCycles(1); + + // Send sub data with main 00 and check busOuts + await sendBothData(test, mainData: 0x00, subData: 0x6A); // 0110 1010 + checkMainBusOut(test, 0x6A); + checkSubBusOut(test, 0x00); + + await test.clk.waitCycles(2); + + await sendBothData(test, mainData: 0x50, subData: 0x82); // 1000 0010 + checkMainBusOut(test, 0x82); + checkSubBusOut(test, 0x50); + + // await test.clk.waitCycles(4); + + await sendBothData(test, mainData: 0x33, subData: 0x7D); // 1000 0010 + checkMainBusOut(test, 0x7D); + checkSubBusOut(test, 0x33); + + await test.clk.waitCycles(4); + }, 'testPairA')); + }); + }); +} diff --git a/test/spi/spi_test.dart b/test/spi/spi_test.dart new file mode 100644 index 000000000..5e8d589f8 --- /dev/null +++ b/test/spi/spi_test.dart @@ -0,0 +1,48 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// spi_test.dart +// Tests for SPI interface +// +// 2024 September 23 +// Author: Roberto Torres + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:test/test.dart'; + +class SpiMainIntf extends Module { + SpiMainIntf(SpiInterface intf, {super.name = 'SpiMainIntf'}) { + intf = SpiInterface.clone(intf) + ..pairConnectIO(this, intf, PairRole.provider); + } +} + +class SpiSubIntf extends Module { + SpiSubIntf(SpiInterface intf, {super.name = 'SpiSubIntf'}) { + intf = SpiInterface.clone(intf) + ..pairConnectIO(this, intf, PairRole.consumer); + } +} + +class SpiTopIntf extends Module { + SpiTopIntf({super.name = 'SpiTopIntf'}) { + final intf = SpiInterface(); + SpiMainIntf(intf); + SpiSubIntf(intf); + addOutput('dummy') <= intf.sclk; + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + test('spi_test', () async { + final mod = SpiTopIntf(); + await mod.build(); + final genSV = mod.generateSynth(); + expect(genSV, contains('input logic MOSI')); + }); +} diff --git a/test/summation/counter_test.dart b/test/summation/counter_test.dart index 6fcd565a9..c756888cc 100644 --- a/test/summation/counter_test.dart +++ b/test/summation/counter_test.dart @@ -272,6 +272,88 @@ void main() { await Simulator.endSimulation(); }); + test('async reset, clock tied off', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final restart = Logic(); + + final counter = Counter( + [ + SumInterface(fixedAmount: 4), + SumInterface(fixedAmount: 2, increments: false), + ], + clk: Const(0), + reset: reset, + restart: restart, + asyncReset: true, + resetValue: 4, + maxValue: 10, + minValue: 1, + ); + + await counter.build(); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + WaveDumper(counter); + // initializing/resetting with no clock + await clk.waitCycles(2); + reset.inject(0); + restart.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextPosedge; + expect(counter.count.value.toInt(), 4); + + await clk.waitCycles(2); + await Simulator.endSimulation(); + }); + + test('async reset with clock', () async { + final clk = SimpleClockGenerator(10).clk; + final reset = Logic(); + final restart = Logic(); + + final counter = Counter( + [ + SumInterface(fixedAmount: 4), + SumInterface(fixedAmount: 2, increments: false), + ], + clk: clk, + reset: reset, + restart: restart, + asyncReset: true, + resetValue: 2, + maxValue: 20, + minValue: 1, + ); + + await counter.build(); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // initial reset flow + reset.inject(0); + restart.inject(0); + await clk.nextNegedge; + reset.inject(1); + await clk.nextPosedge; + reset.inject(0); + + // check counter counts + await clk.waitCycles(4); + expect(counter.count.value.toInt(), 10); + + // reset + reset.inject(1); + await clk.nextChanged; + expect(counter.count.previousValue!.toInt(), 2); + await clk.waitCycles(2); + + await Simulator.endSimulation(); + }); + group('random counter', () { const numRandCounters = 20; const restartProbability = 0.05;