diff --git a/doc/README.md b/doc/README.md index 06534248..b3a342e3 100644 --- a/doc/README.md +++ b/doc/README.md @@ -73,6 +73,8 @@ Some in-development items will have opened issues, as well. Feel free to create - CRC - [Parity](./components/parity.md) - Interleaving +- Clocking + - [Clock gating](./components/clock_gating.md) - Data flow - Ready/Valid - Connect/Disconnect diff --git a/doc/components/clock_gate.png b/doc/components/clock_gate.png new file mode 100644 index 00000000..16fe71f4 Binary files /dev/null and b/doc/components/clock_gate.png differ diff --git a/doc/components/clock_gating.md b/doc/components/clock_gating.md new file mode 100644 index 00000000..c24e0758 --- /dev/null +++ b/doc/components/clock_gating.md @@ -0,0 +1,84 @@ +# Clock Gating + +ROHD-HCL includes a generic clock gating component for enabling and disabling clocks to save power. The implementation supports multiple scenarios and use cases: + +- Easily control whether clock gating `isPresent` or not without modifying the implementation. +- Delay (or don't) controlled signals that are sampled in the gated clock domain, depending on your timing needs. +- Optionally use an override to force all clock gates to be enabled. +- Bring your own clock gating implementation and propagate the instantiation and any additionally required ports through an entire hierarchy without modifying any lower levels of the design. +- Automatically handle some tricky situations (e.g. keeping clocks enabled during reset for synchronous reset). + +![Diagram of the clock gating component](clock_gate.png) + +A very simple counter design is shown below with clock gating included via the component. + +```dart +class CounterWithSimpleClockGate extends Module { + Logic get count => output('count'); + + CounterWithSimpleClockGate({ + required Logic clk, + required Logic incr, + required Logic reset, + required ClockGateControlInterface cgIntf, + }) : super(name: 'clk_gated_counter') { + clk = addInput('clk', clk); + incr = addInput('incr', incr); + reset = addInput('reset', reset); + + // We clone the incoming interface, receiving all config information with it + cgIntf = ClockGateControlInterface.clone(cgIntf) + ..pairConnectIO(this, cgIntf, PairRole.consumer); + + // In this case, we want to enable the clock any time we're incrementing + final clkEnable = incr; + + // Build the actual clock gate component. + final clkGate = ClockGate( + clk, + enable: clkEnable, + reset: reset, + controlIntf: cgIntf, + ); + + final count = addOutput('count', width: 8); + count <= + flop( + // access the gated clock from the component + clkGate.gatedClk, + // by default, `controlled` signals are delayed by 1 cycle + count + clkGate.controlled(incr).zeroExtend(count.width), + reset: reset, + ); + } +} +``` + +Some important pieces to note here are: + +- The clock gate component is instantiated like any other component +- We pass it a `ClockGateControlInterface` which brings with it any potential custom control. When we punch ports for this design, we use the `clone` constructor, which carries said configuration information. +- We enable the clock any time `incr` is asserted to increment the counter. +- Use the gated clock on the downstream flop for the counter. +- Use a "controlled" version of `incr`, which by default is delayed by one cycle. + +The `ClockGateControlInterface` comes with an optional `enableOverride` which can force the clocks to always be enabled. It also contains a boolean `isPresent` which can control whether clock gating should be generated at all. Since configuration information is automatically carried down through the hierarchy, this means you *can turn on or off clock gating generation through an entire hierarchy without modifying your design*. + +Suppose now we wanted to add our own custom clock gating module implementation. This implementation may require some additional signals as well. When we pass a control interface we can provide some additional arguments to achieve this. For example: + +```dart +ClockGateControlInterface( + additionalPorts: [ + Port('anotherOverride'), + ], + gatedClockGenerator: (intf, clk, enable) => CustomClockGatingModule( + clk: clk, + en: enable, + anotherOverride: intf.port('anotherOverride'), + ).gatedClk, +); +``` + +Passing in an interface configured like this would mean that any consumers would automatically get the additional ports and new clock gating implementation. Our counter example could get this new method for clock gating and a new port without changing the design of the counter at all. + +An executable version of this example is available in `example/clock_gating_example.dart`. diff --git a/example/clock_gating_example.dart b/example/clock_gating_example.dart new file mode 100644 index 00000000..44ebeaca --- /dev/null +++ b/example/clock_gating_example.dart @@ -0,0 +1,176 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// clock_gating_example.dart +// Example of how to use clock gating. +// +// 2024 September 24 +// Author: Max Korbel + +// ignore_for_file: avoid_print + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/rohd_hcl.dart'; +import 'package:rohd_vf/rohd_vf.dart'; + +/// A very simple counter that has clock gating internally. +class CounterWithSimpleClockGate extends Module { + Logic get count => output('count'); + + CounterWithSimpleClockGate({ + required Logic clk, + required Logic incr, + required Logic reset, + required ClockGateControlInterface cgIntf, + }) : super(name: 'clk_gated_counter') { + clk = addInput('clk', clk); + incr = addInput('incr', incr); + reset = addInput('reset', reset); + + // We clone the incoming interface, receiving all config information with it + cgIntf = ClockGateControlInterface.clone(cgIntf) + ..pairConnectIO(this, cgIntf, PairRole.consumer); + + // In this case, we want to enable the clock any time we're incrementing + final clkEnable = incr; + + // Build the actual clock gate component. + final clkGate = ClockGate( + clk, + enable: clkEnable, + reset: reset, + controlIntf: cgIntf, + delayControlledSignals: true, + ); + + final count = addOutput('count', width: 8); + count <= + flop( + // access the gated clock from the component + clkGate.gatedClk, + + // depending on configuration default, `controlled` signals are + // delayed by 1 cycle (in this case we enable it) + count + clkGate.controlled(incr).zeroExtend(count.width), + + reset: reset, + ); + } +} + +/// A reference to an external SystemVerilog clock-gating macro. +class CustomClockGateMacro extends Module with CustomSystemVerilog { + Logic get gatedClk => output('gatedClk'); + + CustomClockGateMacro({ + required Logic clk, + required Logic en, + required Logic override, + required Logic anotherOverride, + }) : super(name: 'custom_clock_gate_macro') { + // make sure ports match the SystemVerilog + clk = addInput('clk', clk); + en = addInput('en', en); + override = addInput('override', override); + anotherOverride = addInput('another_override', anotherOverride); + addOutput('gatedClk'); + + // simulation-only behavior + gatedClk <= clk & flop(~clk, en | override | anotherOverride); + } + + // define how to instantiate this custom SystemVerilog + @override + String instantiationVerilog(String instanceType, String instanceName, + Map inputs, Map outputs) => + '`CUSTOM_CLOCK_GATE(' + '${outputs['gatedClk']}, ' + '${inputs['clk']}, ' + '${inputs['en']}, ' + '${inputs['override']}, ' + '${inputs['another_override']}' + ')'; +} + +Future main({bool noPrint = false}) async { + // Build a custom version of the clock gating control interface which uses our + // custom macro. + final customClockGateControlIntf = ClockGateControlInterface( + hasEnableOverride: true, + additionalPorts: [ + // we add an additional override port, for example, which is passed + // automatically down the hierarchy + Port('anotherOverride'), + ], + gatedClockGenerator: (intf, clk, enable) => CustomClockGateMacro( + clk: clk, + en: enable, + override: intf.enableOverride!, + anotherOverride: intf.port('anotherOverride'), + ).gatedClk, + ); + + // Generate a simple clock. This will run along by itself as + // the Simulator goes. + final clk = SimpleClockGenerator(10).clk; + + // ... and some additional signals + final reset = Logic(); + final incr = Logic(); + + final counter = CounterWithSimpleClockGate( + clk: clk, + reset: reset, + incr: incr, + cgIntf: customClockGateControlIntf, + ); + + // build the module and attach a waveform viewer for debug + await counter.build(); + + // Let's see what this module looks like as SystemVerilog, so we can pass it + // to other tools. + final systemVerilogCode = counter.generateSynth(); + if (!noPrint) { + print(systemVerilogCode); + } + + // Now let's try simulating! + + // Attach a waveform dumper so we can see what happens. + if (!noPrint) { + WaveDumper(counter); + } + + // Start off with a disabled counter and asserting reset at the start. + incr.inject(0); + reset.inject(1); + + // leave overrides turned off + customClockGateControlIntf.enableOverride!.inject(0); + customClockGateControlIntf.port('anotherOverride').inject(0); + + Simulator.setMaxSimTime(1000); + unawaited(Simulator.run()); + + // wait a bit before dropping reset + await clk.waitCycles(3); + reset.inject(0); + + // wait a bit before raising incr + await clk.waitCycles(5); + incr.inject(1); + + // leave it high for a bit, then drop it + await clk.waitCycles(5); + incr.inject(0); + + // wait a little longer, then end the test + await clk.waitCycles(5); + await Simulator.endSimulation(); + + // Now we can review the waves to see how the gated clock does not toggle + // while gated! +} diff --git a/example/example.dart b/example/example.dart index ad4eabe0..51405e1a 100644 --- a/example/example.dart +++ b/example/example.dart @@ -12,7 +12,7 @@ import 'package:rohd/rohd.dart'; import 'package:rohd_hcl/rohd_hcl.dart'; -Future main() async { +Future main({bool noPrint = false}) async { // Build a module that rotates a 16-bit signal by an 8-bit signal, which // we guarantee will never see more than 10 as the rotate amount. final original = Logic(width: 16); @@ -23,11 +23,15 @@ Future main() async { // Do a quick little simulation with some inputs original.put(0x4321); rotateAmount.put(4); - print('Shifting ${original.value} by ${rotateAmount.value} ' - 'yields ${rotated.value}'); + if (!noPrint) { + print('Shifting ${original.value} by ${rotateAmount.value} ' + 'yields ${rotated.value}'); + } // Generate verilog for it and print it out await mod.build(); - print('Generating verilog...'); - print(mod.generateSynth()); + final sv = mod.generateSynth(); + if (!noPrint) { + print(sv); + } } diff --git a/lib/rohd_hcl.dart b/lib/rohd_hcl.dart index 8cf3540a..5b95a5fb 100644 --- a/lib/rohd_hcl.dart +++ b/lib/rohd_hcl.dart @@ -4,6 +4,7 @@ export 'src/arbiters/arbiters.dart'; export 'src/arithmetic/arithmetic.dart'; export 'src/binary_gray.dart'; +export 'src/clock_gating.dart'; export 'src/component_config/component_config.dart'; export 'src/count.dart'; export 'src/edge_detector.dart'; diff --git a/lib/src/clock_gating.dart b/lib/src/clock_gating.dart new file mode 100644 index 00000000..f056ca72 --- /dev/null +++ b/lib/src/clock_gating.dart @@ -0,0 +1,270 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// clock_gating.dart +// Clock gating. +// +// 2024 September 18 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +// ignore: implementation_imports +import 'package:rohd/src/utilities/uniquifier.dart'; + +/// An [Interface] for controlling [ClockGate]s. +class ClockGateControlInterface extends PairInterface { + /// Whether an [enableOverride] is present on this interface. + final bool hasEnableOverride; + + /// If asserted, then clocks will be enabled regardless of the `enable` + /// signal. + /// + /// Presence is controlled by [hasEnableOverride]. + Logic? get enableOverride => tryPort('en_override'); + + /// Indicates whether clock gating logic [isPresent] or not. If it is not, + /// then no clock gating will occur and no clock gating logic will be + /// generated. + final bool isPresent; + + /// A default implementation for clock gating, effectively just an AND of the + /// clock and the enable signal, with an optional [enableOverride]. + static Logic defaultGenerateGatedClock( + ClockGateControlInterface intf, + Logic clk, + Logic enable, + ) => + clk & + flop( + ~clk, + [ + enable, + if (intf.hasEnableOverride) intf.enableOverride!, + ].swizzle().or()); + + /// A function that generates the gated clock signal based on the provided + /// `intf`, `clk`, and `enable` signals. + final Logic Function( + ClockGateControlInterface intf, + Logic clk, + Logic enable, + ) gatedClockGenerator; + + /// Constructs a [ClockGateControlInterface] with the provided arguments. + /// + /// If [isPresent] is false, then no clock gating will occur and no clock + /// gating logic will be generated. + /// + /// If [hasEnableOverride] is true, then an additional [enableOverride] port + /// will be generated. + /// + /// [additionalPorts] can optionally be added to this interface, which can be + /// useful in conjunction with a custom [gatedClockGenerator]. As the + /// interface is punched through hierarchies, any modules using this interface + /// will automatically include the [additionalPorts] and use the custom + /// [gatedClockGenerator] for clock gating logic. + ClockGateControlInterface({ + this.isPresent = true, + this.hasEnableOverride = false, + List? additionalPorts, + this.gatedClockGenerator = defaultGenerateGatedClock, + }) : super(portsFromProvider: [ + if (hasEnableOverride) Port('en_override'), + ...?additionalPorts, + ]); + + /// Creates a clone of [otherInterface] with the same configuration, including + /// any `additionalPorts` and `gatedClockGenerator` function. This should be + /// used to replicate interface configuration through hierarchies to carry + /// configuration information. + /// + /// If [isPresent] is provided, then it will override the [isPresent] value + /// from [otherInterface]. + /// + /// If a [gatedClockGenerator] is provided, then it will override the + /// [gatedClockGenerator] function from [otherInterface]. + ClockGateControlInterface.clone( + ClockGateControlInterface super.otherInterface, { + bool? isPresent, + Logic Function( + ClockGateControlInterface intf, + Logic clk, + Logic enable, + )? gatedClockGenerator, + }) : hasEnableOverride = otherInterface.hasEnableOverride, + isPresent = isPresent ?? otherInterface.isPresent, + gatedClockGenerator = + gatedClockGenerator ?? otherInterface.gatedClockGenerator, + super.clone(); +} + +/// A generic and configurable clock gating block. +class ClockGate extends Module { + /// An internal cache for controlled signals to avoid duplicating them. + final Map _controlledCache = {}; + + /// Returns a (potentially) delayed (by one cycle) version of [original] if + /// [delayControlledSignals] is true and the clock gating [isPresent]. This is + /// the signal that should be used as inputs to logic depending on the + /// [gatedClk]. + /// + /// If a [resetValue] is provided, then the signal will be reset to that value + /// when the clock gating is reset. + Logic controlled(Logic original, {dynamic resetValue}) { + if (!isPresent || !delayControlledSignals) { + return original; + } + + if (_controlledCache.containsKey(original)) { + return _controlledCache[original]!; + } else { + final o = super.addOutput( + _uniquifier.getUniqueName(initialName: '${original.name}_delayed')); + + _controlledCache[original] = o; + + o <= + flop( + _freeClk, + reset: _reset, + resetValue: resetValue, + super.addInput( + _uniquifier.getUniqueName(initialName: original.name), + original, + width: original.width, + ), + ); + + return o; + } + } + + /// A uniquifier for ports to ensure that they are unique as they punch via + /// [controlled]. + final _uniquifier = Uniquifier(); + + // override the addInput and addOutput functions for uniquification purposes + + @override + Logic addInput(String name, Logic x, {int width = 1}) { + _uniquifier.getUniqueName(initialName: name, reserved: true); + return super.addInput(name, x, width: width); + } + + @override + Logic addOutput(String name, {int width = 1}) { + _uniquifier.getUniqueName(initialName: name, reserved: true); + return super.addOutput(name, width: width); + } + + /// The gated clock output. + late final Logic gatedClk; + + /// Reset for all internal logic. + late final Logic? _reset; + + /// The enable signal provided as an input. + late final Logic _enable; + + /// The free clock signal provided as an input. + late final Logic _freeClk; + + /// The control interface for the clock gating, if provided. + late final ClockGateControlInterface? _controlIntf; + + /// Indicates whether the clock gating is present. If it is not present, then + /// the [gatedClk] is directly connected to the free clock and the + /// [controlled] signals are not delayed. + bool get isPresent => + _controlIntf?.isPresent ?? + // if no interface is provided, then _controlInterface is initialized with + // `isPresent` as true, so if this is null then there is no clock gating + false; + + /// Indicates whether the controlled signals are delayed by 1 cycle. If this + /// is false, or clock gating is not [isPresent], then the [controlled] + /// signals are not delayed. + final bool delayControlledSignals; + + /// Constructs a clock gating block where [enable] controls whether the + /// [gatedClk] is connected directly to the [freeClk] or is gated off. + /// + /// If [controlIntf] is provided, then the clock gating can be controlled + /// externally (for example whether the clock gating [isPresent] or using an + /// override signal to force clocks enabled). If [controlIntf] is not + /// provided, then the clock gating is always present. + /// + /// If [delayControlledSignals] is true, then any signals that are + /// [controlled] by the clock gating will be delayed by 1 cycle. This can be + /// helpful for timing purposes to avoid ungating the clock on the same cycle + /// as the signal is used. Using the [controlled] signals helps turn on or off + /// the delay across all applicable signals via a single switch: + /// [delayControlledSignals]. + /// + /// The [gatedClk] is automatically enabled during [reset] (if provided) so + /// that synchronous resets work properly, and the [enable] is extended for an + /// appropriate duration (if [delayControlledSignals]) to ensure proper + /// capture of data. + ClockGate( + Logic freeClk, { + required Logic enable, + Logic? reset, + ClockGateControlInterface? controlIntf, + this.delayControlledSignals = false, + super.name = 'clock_gate', + }) { + // if this clock gating is not intended to be present, then just do nothing + if (!(controlIntf?.isPresent ?? true)) { + _controlIntf = null; + gatedClk = freeClk; + return; + } + + _freeClk = addInput('freeClk', freeClk); + _enable = addInput('enable', enable); + + if (reset != null) { + _reset = addInput('reset', reset); + } else { + _reset = null; + } + + if (controlIntf == null) { + // if we are not provided an interface, make our own to use with default + // configuration + _controlIntf = ClockGateControlInterface(); + } else { + _controlIntf = ClockGateControlInterface.clone(controlIntf) + ..pairConnectIO(this, controlIntf, PairRole.consumer); + } + + gatedClk = addOutput('gatedClk'); + + _buildLogic(); + } + + /// Build the internal logic for handling enabling the gated clock. + void _buildLogic() { + var internalEnable = _enable; + + if (_reset != null) { + // we want to enable the clock during reset so that synchronous resets + // work properly + internalEnable |= _reset!; + } + + if (delayControlledSignals) { + // extra if there's a delay on the inputs relative to the enable + internalEnable |= flop( + _freeClk, + _enable, + reset: _reset, + resetValue: 1, // during reset, keep the clock enabled + ); + } + + gatedClk <= + _controlIntf! + .gatedClockGenerator(_controlIntf!, _freeClk, internalEnable); + } +} diff --git a/test/clock_gating_test.dart b/test/clock_gating_test.dart new file mode 100644 index 00000000..cae925c8 --- /dev/null +++ b/test/clock_gating_test.dart @@ -0,0 +1,275 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// clock_gating_test.dart +// Tests for clock gating. +// +// 2024 September 18 +// Author: Max Korbel + +import 'dart:async'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd_hcl/src/clock_gating.dart'; +import 'package:rohd_vf/rohd_vf.dart'; +import 'package:test/test.dart'; + +class CustomClockGateMacro extends Module with CustomSystemVerilog { + Logic get gatedClk => output('gatedClk'); + + CustomClockGateMacro({ + required Logic clk, + required Logic en, + required Logic override, + required Logic anotherOverride, + }) : super(name: 'custom_clock_gate_macro') { + clk = addInput('clk', clk); + en = addInput('en', en); + override = addInput('override', override); + anotherOverride = addInput('another_override', anotherOverride); + + addOutput('gatedClk'); + + // simulation-only behavior + gatedClk <= clk & flop(~clk, en | override | anotherOverride); + } + + @override + String instantiationVerilog(String instanceType, String instanceName, + Map inputs, Map outputs) => + '`CUSTOM_CLOCK_GATE(' + '${outputs['gatedClk']}, ' + '${inputs['clk']}, ' + '${inputs['en']}, ' + '${inputs['override']}, ' + '${inputs['another_override']}' + ')'; +} + +class CustomClockGateControlInterface extends ClockGateControlInterface { + Logic get anotherOverride => port('anotherOverride'); + + CustomClockGateControlInterface({super.isPresent}) + : super( + hasEnableOverride: true, + additionalPorts: [ + Port('anotherOverride'), + ], + gatedClockGenerator: (intf, clk, enable) => CustomClockGateMacro( + clk: clk, + en: enable, + override: intf.enableOverride!, + anotherOverride: intf.port('anotherOverride'), + ).gatedClk); +} + +class CounterWithSimpleClockGate extends Module { + Logic get count => output('count'); + + /// A probe for clock gating. + late final ClockGate _clkGate; + + CounterWithSimpleClockGate(Logic clk, Logic incr, Logic reset, + {bool withDelay = true, ClockGateControlInterface? cgIntf}) + : super(name: 'clk_gated_counter') { + if (cgIntf != null) { + cgIntf = ClockGateControlInterface.clone(cgIntf) + ..pairConnectIO(this, cgIntf, PairRole.consumer); + } + + clk = addInput('clk', clk); + incr = addInput('incr', incr); + reset = addInput('reset', reset); + + final clkEnable = incr; + _clkGate = ClockGate( + clk, + enable: clkEnable, + reset: reset, + controlIntf: cgIntf, + delayControlledSignals: withDelay, + ); + + final count = addOutput('count', width: 8); + count <= + flop( + _clkGate.gatedClk, + count + _clkGate.controlled(incr).zeroExtend(count.width), + reset: reset, + ); + } +} + +void main() { + tearDown(() async { + await Simulator.reset(); + }); + + test('custom clock gating port', () async { + final cgIntf = CustomClockGateControlInterface(); + + cgIntf.enableOverride!.inject(0); + cgIntf.anotherOverride.inject(1); + + final clk = SimpleClockGenerator(10).clk; + final incr = Logic()..inject(0); + final reset = Logic(); + + final counter = CounterWithSimpleClockGate( + clk, + incr, + reset, + cgIntf: cgIntf, + ); + + await counter.build(); + + final sv = counter.generateSynth(); + expect(sv, contains('anotherOverride')); + expect(sv, contains('CUSTOM_CLOCK_GATE')); + + // ignore: invalid_use_of_protected_member + expect(counter.tryInput('anotherOverride'), isNotNull); + + Simulator.setMaxSimTime(500); + unawaited(Simulator.run()); + + reset.inject(1); + await clk.waitCycles(3); + reset.inject(0); + await clk.waitCycles(3); + + incr.inject(1); + await clk.waitCycles(5); + incr.inject(0); + await clk.waitCycles(5); + + expect(counter.count.value.toInt(), 5); + + cgIntf.anotherOverride.inject(1); + + await counter._clkGate.gatedClk.nextPosedge; + final t1 = Simulator.time; + await counter._clkGate.gatedClk.nextPosedge; + expect(Simulator.time, t1 + 10); + + cgIntf.anotherOverride.inject(0); + + unawaited(counter._clkGate.gatedClk.nextPosedge.then((_) { + fail('Expected a gated clock, no more toggles'); + })); + + await clk.waitCycles(5); + + await Simulator.endSimulation(); + }); + + group('basic clock gating', () { + final clockGatingTypes = { + 'none': () => null, + 'normal': ClockGateControlInterface.new, + 'normal not present': () => ClockGateControlInterface(isPresent: false), + 'override': () => ClockGateControlInterface(hasEnableOverride: true), + 'custom': CustomClockGateControlInterface.new, + }; + + for (final withDelay in [true, false]) { + for (final cgType in clockGatingTypes.entries) { + final hasOverride = cgType.value()?.hasEnableOverride ?? false; + for (final enOverride in [ + if (hasOverride) true, + false, + ]) { + test( + [ + cgType.key, + if (withDelay) 'with delay', + if (hasOverride) 'override: $enOverride', + ].join(', '), () async { + final cgIntf = cgType.value(); + + final overrideSignal = cgIntf is CustomClockGateControlInterface + ? (cgIntf.anotherOverride..inject(0)) + : cgIntf?.enableOverride; + cgIntf?.enableOverride?.inject(0); + + if (enOverride) { + overrideSignal?.inject(1); + } else { + overrideSignal?.inject(0); + } + + final clk = SimpleClockGenerator(10).clk; + final incr = Logic()..inject(0); + final reset = Logic(); + + final counter = CounterWithSimpleClockGate( + clk, + incr, + reset, + cgIntf: cgIntf, + withDelay: withDelay, + ); + + await counter.build(); + + // WaveDumper(counter); + + var clkToggleCount = 0; + counter._clkGate.gatedClk.posedge.listen((_) { + clkToggleCount++; + }); + + Simulator.setMaxSimTime(500); + unawaited(Simulator.run()); + + reset.inject(1); + await clk.waitCycles(3); + reset.inject(0); + await clk.waitCycles(3); + + incr.inject(1); + await clk.waitCycles(5); + incr.inject(0); + await clk.waitCycles(5); + + expect(counter.count.value.toInt(), 5); + + if (counter._clkGate.isPresent && !enOverride) { + if (counter._clkGate.delayControlledSignals) { + expect(clkToggleCount, lessThanOrEqualTo(7 + 4)); + } else { + expect(clkToggleCount, lessThanOrEqualTo(6 + 4)); + } + } else { + expect(clkToggleCount, greaterThanOrEqualTo(14)); + } + + if (hasOverride) { + if (cgIntf is CustomClockGateControlInterface) { + cgIntf.anotherOverride.inject(0); + } + + cgIntf!.enableOverride!.inject(1); + + await counter._clkGate.gatedClk.nextPosedge; + final t1 = Simulator.time; + await counter._clkGate.gatedClk.nextPosedge; + expect(Simulator.time, t1 + 10); + + cgIntf.enableOverride!.inject(0); + + unawaited(counter._clkGate.gatedClk.nextPosedge.then((_) { + fail('Expected a gated clock, no more toggles'); + })); + + await clk.waitCycles(5); + } + + await Simulator.endSimulation(); + }); + } + } + } + }); +} diff --git a/test/example_test.dart b/test/example_test.dart new file mode 100644 index 00000000..53b6ef20 --- /dev/null +++ b/test/example_test.dart @@ -0,0 +1,21 @@ +// Copyright (C) 2024 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// example_test.dart +// Tests that the examples run. +// +// 2024 September 24 +// Author: Max Korbel + +import 'package:test/test.dart'; + +import '../example/clock_gating_example.dart' as clock_gating_example; +import '../example/example.dart' as example; + +void main() { + test('examples run', () async { + for (final exMain in [example.main, clock_gating_example.main]) { + await exMain(noPrint: true); + } + }); +}