Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support negative edge-triggered Sequentials #483

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/finite_state_machine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class FiniteStateMachine<StateIdentifier> {
List<State<StateIdentifier>> states,
) : this.multi([clk], reset, resetState, states);

// TODO: allow FSM to support async reset
/// Creates an finite state machine for the specified list of [_states], with
/// an initial state of [resetState] (when synchronous [reset] is high) and
/// transitions on positive edges of any of [_clks].
Expand Down
139 changes: 86 additions & 53 deletions lib/src/modules/conditional.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2021-2023 Intel Corporation
// Copyright (C) 2021-2024 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
//
// conditional.dart
Expand Down Expand Up @@ -417,20 +417,55 @@ class Combinational extends _Always {
@Deprecated('Use Sequential instead')
typedef FF = Sequential;

/// A tracking construct for triggers of [Sequential]s.
class _SequentialTrigger {
/// The signal for this trigger.
final Logic signal;

/// Whether this triggers on a positive edge (`true`) or a negative edge
/// (`false`).
final bool isPosedge;

/// The value of the trigger before the tick.
LogicValue? preTickValue;

/// Creates a tracking object for triggers.
_SequentialTrigger(this.signal, {required this.isPosedge})
: _edgeCheck = isPosedge ? LogicValue.isPosedge : LogicValue.isNegedge;

/// The method for checking whether an edge has occurred.
final bool Function(LogicValue previousValue, LogicValue newValue) _edgeCheck;

/// The previous value of the signal.
LogicValue get _previousValue =>
// if the pre-tick value is null, then it should have the same value as
// it currently does
preTickValue ?? signal.value;

/// Whether this trigger has been triggered.
bool get isTriggered => isValid && _edgeCheck(_previousValue, signal.value);

/// Whether this trigger is valid.
bool get isValid => signal.value.isValid && _previousValue.isValid;

/// The SystemVerilog keyword for this trigger.
String get verilogTriggerKeyword => isPosedge ? 'posedge' : 'negedge';
}

/// Represents a block of sequential logic.
///
/// This is similar to an `always_ff` block in SystemVerilog. Positive edge
/// triggered by either one trigger or multiple with [Sequential.multi].
class Sequential extends _Always {
/// The input clocks used in this block.
final List<Logic> _clks = [];
/// The input edge triggers used in this block.
final List<_SequentialTrigger> _triggers = [];

/// When `false`, an [SignalRedrivenException] will be thrown during
/// simulation if the same signal is driven multiple times within this
/// [Sequential].
final bool allowMultipleAssignments;

/// Constructs a [Sequential] single-triggered by [clk].
/// Constructs a [Sequential] single-triggered by the positive edge of [clk].
///
/// If `reset` is provided, then all signals driven by this block will be
/// conditionally reset when the signal is high.
Expand All @@ -455,50 +490,60 @@ class Sequential extends _Always {
allowMultipleAssignments: allowMultipleAssignments,
);

/// Constructs a [Sequential] multi-triggered by any of [clks].
/// Constructs a [Sequential] multi-triggered by any of [posedgeTriggers] and
/// [negedgeTriggers] (on positive and negative edges, respectively).
///
/// If `reset` is provided, then all signals driven by this block will be
/// conditionally reset when the signal is high.
/// The default reset value is to `0`, but if `resetValues` is provided then
/// the corresponding value associated with the driven signal will be set to
/// that value instead upon reset. If a signal is in `resetValues` but not
/// driven by any other [Conditional] in this block, it will be driven to the
/// specified reset value.
/// conditionally reset when the signal is high. The default reset value is to
/// `0`, but if `resetValues` is provided then the corresponding value
/// associated with the driven signal will be set to that value instead upon
/// reset. If a signal is in `resetValues` but not driven by any other
/// [Conditional] in this block, it will be driven to the specified reset
/// value.
Sequential.multi(
List<Logic> clks,
List<Logic> posedgeTriggers,
super._conditionals, {
super.reset,
super.resetValues,
super.name = 'sequential',
this.allowMultipleAssignments = true,
List<Logic> negedgeTriggers = const [],
}) {
if (clks.isEmpty) {
_registerInputTriggers(posedgeTriggers, isPosedge: true);
_registerInputTriggers(negedgeTriggers, isPosedge: false);

if (_triggers.isEmpty) {
throw IllegalConfigurationException('Must provide at least one clock.');
}

for (var i = 0; i < clks.length; i++) {
final clk = clks[i];
if (clk.width > 1) {
throw Exception('Each clk must be 1 bit, but saw $clk.');
_setup();
}

/// Registers either positive or negative edge trigger inputs for
/// [providedTriggers] based on [isPosedge].
void _registerInputTriggers(List<Logic> providedTriggers,
{required bool isPosedge}) {
for (var i = 0; i < providedTriggers.length; i++) {
final trigger = providedTriggers[i];
if (trigger.width != 1) {
throw Exception('Each clk or trigger must be 1 bit, but saw $trigger.');
}
_clks.add(addInput(
_portUniquifier.getUniqueName(
initialName: Sanitizer.sanitizeSV(
Naming.unpreferredName('clk${i}_${clk.name}'))),
clk));
_preTickClkValues.add(null);

_triggers.add(_SequentialTrigger(
addInput(
_portUniquifier.getUniqueName(
initialName: Sanitizer.sanitizeSV(
Naming.unpreferredName('clk${i}_${trigger.name}'))),
trigger),
isPosedge: isPosedge));
}
_setup();
}

/// A map from input [Logic]s to the values that should be used for
/// computations on the edge.
final Map<Logic, LogicValue> _inputToPreTickInputValuesMap =
HashMap<Logic, LogicValue>();

/// The value of the clock before the tick.
final List<LogicValue?> _preTickClkValues = [];

/// Keeps track of whether the clock has glitched and an [_execute] is
/// necessary.
bool _pendingExecute = false;
Expand Down Expand Up @@ -560,11 +605,10 @@ class Sequential extends _Always {
}

// listen to every clock glitch to see if we need to execute
for (var i = 0; i < _clks.length; i++) {
final clk = _clks[i];
clk.glitch.listen((event) async {
for (final trigger in _triggers) {
trigger.signal.glitch.listen((event) async {
// we want the first previousValue from the first glitch of this tick
_preTickClkValues[i] ??= event.previousValue;
trigger.preTickValue ??= event.previousValue;
if (!_pendingExecute) {
unawaited(Simulator.clkStable.first.then((value) {
// once the clocks are stable, execute the contents of the FF
Expand Down Expand Up @@ -593,27 +637,14 @@ class Sequential extends _Always {
}

void _execute() {
var anyClkInvalid = false;
var anyClkPosedge = false;

for (var i = 0; i < _clks.length; i++) {
// if the pre-tick value is null, then it should have the same value as
// it currently does
if (!_clks[i].value.isValid || !(_preTickClkValues[i]?.isValid ?? true)) {
anyClkInvalid = true;
break;
} else if (_preTickClkValues[i] != null &&
LogicValue.isPosedge(_preTickClkValues[i]!, _clks[i].value)) {
anyClkPosedge = true;
break;
}
}
final anyTriggered = _triggers.any((t) => t.isTriggered);
final anyTriggerInvalid = _triggers.any((t) => !t.isValid);

if (anyClkInvalid) {
if (anyTriggerInvalid) {
for (final receiverOutput in _assignedReceiverToOutputMap.values) {
receiverOutput.put(LogicValue.x);
}
} else if (anyClkPosedge) {
} else if (anyTriggered) {
if (allowMultipleAssignments) {
for (final element in conditionals) {
element.execute(null, null);
Expand All @@ -630,16 +661,18 @@ class Sequential extends _Always {
}

// clear out all the pre-tick value of clocks
for (var i = 0; i < _clks.length; i++) {
_preTickClkValues[i] = null;
for (final trigger in _triggers) {
trigger.preTickValue = null;
}
}

@override
String alwaysVerilogStatement(Map<String, String> inputs) {
final triggers =
_clks.map((clk) => 'posedge ${inputs[clk.name]}').join(' or ');
return 'always_ff @($triggers)';
final svTriggers = _triggers
.map((trigger) =>
'${trigger.verilogTriggerKeyword} ${inputs[trigger.signal.name]}')
.join(' or ');
return 'always_ff @($svTriggers)';
}

@override
Expand Down
1 change: 1 addition & 0 deletions lib/src/modules/pipeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class Pipeline {
resetValues: resetValues,
reset: reset);

// TODO: allow Pipeline to support async reset
/// Constructs a [Pipeline] with multiple triggers on any of [_clks].
Pipeline.multi(this._clks,
{List<List<Conditional> Function(PipelineStageInfo p)> stages = const [],
Expand Down
103 changes: 100 additions & 3 deletions test/sequential_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (C) 2021-2023 Intel Corporation
// Copyright (C) 2021-2024 Intel Corporation
//
// sequential_test.dart
// Unit test for Sequential
Expand Down Expand Up @@ -125,6 +125,35 @@ class SeqResetValTypes extends Module {
}
}

class NegedgeTriggeredSeq extends Module {
NegedgeTriggeredSeq(Logic a) {
a = addInput('a', a);
final b = addOutput('b');
final clk = SimpleClockGenerator(10).clk;

Sequential.multi(
[],
negedgeTriggers: [~clk],
[b < a],
);
}
}

class BothTriggeredSeq extends Module {
BothTriggeredSeq(Logic reset) {
reset = addInput('reset', reset);
final b = addOutput('b', width: 8);
final clk = SimpleClockGenerator(10).clk;

Sequential.multi(
[clk],
reset: reset,
negedgeTriggers: [clk],
[b < b + 1],
);
}
}

void main() {
tearDown(() async {
await Simulator.reset();
Expand Down Expand Up @@ -152,8 +181,7 @@ void main() {
Vector({}, {'out': 5}),
];
await SimCompare.checkFunctionalVector(dut, vectors);
final simResult = SimCompare.iverilogVector(dut, vectors);
expect(simResult, equals(true));
SimCompare.checkIverilogVector(dut, vectors);
});

group('shorthand with sequential', () {
Expand Down Expand Up @@ -208,4 +236,73 @@ void main() {
await SimCompare.checkFunctionalVector(dut, vectors);
SimCompare.checkIverilogVector(dut, vectors);
});

test('negedge triggered flop', () async {
final mod = NegedgeTriggeredSeq(Logic());
await mod.build();

final sv = mod.generateSynth();
expect(sv, contains('always_ff @(negedge'));

final vectors = [
Vector({'a': 0}, {}),
Vector({'a': 1}, {'b': 0}),
Vector({'a': 0}, {'b': 1}),
];

await SimCompare.checkFunctionalVector(mod, vectors);
SimCompare.checkIverilogVector(mod, vectors);
});

test('multiple triggers, both edges', () async {
final mod = BothTriggeredSeq(Logic());
await mod.build();

final vectors = [
Vector({'reset': 1}, {}),
Vector({'reset': 1}, {'b': 0}),
Vector({'reset': 0}, {'b': 0}),
Vector({}, {'b': 2}),
Vector({}, {'b': 4}),
Vector({}, {'b': 6}),
];

await SimCompare.checkFunctionalVector(mod, vectors);
SimCompare.checkIverilogVector(mod, vectors);
});

test('negedge trigger actually occurs on negedge', () async {
final clk = Logic()..put(0);

final a = Logic()..put(1);
final b = Logic();

Sequential.multi([], negedgeTriggers: [clk], [b < a]);

Simulator.registerAction(20, () => clk.put(1));

Simulator.registerAction(30, () => expect(b.value, LogicValue.z));

Simulator.registerAction(40, () => clk.put(0));

Simulator.registerAction(50, () => expect(b.value, LogicValue.one));

await Simulator.run();
});

test('invalid trigger after valid trigger still causes x prop', () async {
final c1 = Logic()..put(0);
final c2 = Logic()..put(LogicValue.x);

final a = Logic()..put(1);
final b = Logic();

Sequential.multi([c1, c2], [b < a]);

Simulator.registerAction(20, () => c1.put(1));

Simulator.registerAction(30, () => expect(b.value, LogicValue.x));

await Simulator.run();
});
}
Loading