diff --git a/lib/src/interfaces/interface.dart b/lib/src/interfaces/interface.dart index 41f8e0218..a5d0b8557 100644 --- a/lib/src/interfaces/interface.dart +++ b/lib/src/interfaces/interface.dart @@ -48,6 +48,9 @@ class Interface { ? _ports[name]! : throw Exception('Port name "$name" not found on this interface.'); + /// Provides the [port] named [name] if it exists, otherwise `null`. + Logic? tryPort(String name) => _ports[name]; + /// Connects [module]'s inputs and outputs up to [srcInterface] and this /// [Interface]. /// diff --git a/lib/src/module.dart b/lib/src/module.dart index 86caa90b5..8b6388db3 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -91,13 +91,19 @@ abstract class Module { /// Accesses the [Logic] associated with this [Module]s input port /// named [name]. /// - /// Logic within this [Module] should consume this signal. + /// Only logic within this [Module] should consume this signal. @protected Logic input(String name) => _inputs.containsKey(name) ? _inputs[name]! : throw Exception( 'Input name "$name" not found as an input to this Module.'); + /// Provides the [input] named [name] if it exists, otherwise `null`. + /// + /// Only logic within this [Module] should consume this signal. + @protected + Logic? tryInput(String name) => _inputs[name]; + /// Accesses the [Logic] associated with this [Module]s output port /// named [name]. /// @@ -108,6 +114,9 @@ abstract class Module { : throw Exception( 'Output name "$name" not found as an output of this Module.'); + /// Provides the [output] named [name] if it exists, otherwise `null`. + Logic? tryOutput(String name) => _outputs[name]; + /// Returns true iff [net] is the same [Logic] as the input port of this /// [Module] with the same name. bool isInput(Logic net) => diff --git a/lib/src/modules/conditional.dart b/lib/src/modules/conditional.dart index 6860c16aa..db3b8f064 100644 --- a/lib/src/modules/conditional.dart +++ b/lib/src/modules/conditional.dart @@ -2,7 +2,8 @@ // SPDX-License-Identifier: BSD-3-Clause // // conditional.dart -// Definitions of conditionallly executed hardware constructs (if/else statements, always_comb, always_ff, etc.) +// Definitions of conditionallly executed hardware constructs +// (if/else statements, always_comb, always_ff, etc.) // // 2021 May 7 // Author: Max Korbel @@ -1548,25 +1549,49 @@ ${padding}end '''); /// Constructs a positive edge triggered flip flop on [clk]. /// -/// It returns [FlipFlop.q]. When optional [en] is provided, an additional -/// input will be created for flop. If optional [en] is high or not provided, -/// output will vary as per input[d]. For low [en], output remains frozen -/// irrespective of input [d] -Logic flop(Logic clk, Logic d, {Logic? en}) => FlipFlop(clk, d, en: en).q; +/// It returns [FlipFlop.q]. +/// +/// When the optional [en] is provided, an additional input will be created for +/// flop. If optional [en] is high or not provided, output will vary as per +/// input[d]. For low [en], output remains frozen irrespective of input [d]. +/// +/// When the optional [reset] is provided, the flop will be reset (active-high). +/// If no [resetValue] is provided, the reset value is always `0`. Otherwise, +/// it will reset to the provided [resetValue]. +Logic flop( + Logic clk, + Logic d, { + Logic? en, + Logic? reset, + dynamic resetValue, +}) => + FlipFlop( + clk, + d, + en: en, + reset: reset, + resetValue: resetValue, + ).q; /// Represents a single flip-flop with no reset. class FlipFlop extends Module with CustomSystemVerilog { /// Name for the enable input of this flop - late final String _enName; + final String _enName = Module.unpreferredName('en'); /// Name for the clk of this flop. - late final String _clkName; + final String _clkName = Module.unpreferredName('clk'); /// Name for the input of this flop. - late final String _dName; + final String _dName = Module.unpreferredName('d'); /// Name for the output of this flop. - late final String _qName; + final String _qName = Module.unpreferredName('q'); + + /// Name for the reset of this flop. + final String _resetName = Module.unpreferredName('reset'); + + /// Name for the reset value of this flop. + final String _resetValueName = Module.unpreferredName('resetValue'); /// The clock, posedge triggered. late final Logic _clk = input(_clkName); @@ -1576,7 +1601,10 @@ class FlipFlop extends Module with CustomSystemVerilog { /// If enable is high or enable is not provided then flop output will vary /// on the basis of clock [_clk] and input [_d]. If enable is low, then /// output of the flop remains frozen irrespective of the input [_d]. - late final Logic _en = input(_enName); + late final Logic? _en = tryInput(_enName); + + /// Optional reset input to the flop. + late final Logic? _reset = tryInput(_resetName); /// The input to the flop. late final Logic _d = input(_dName); @@ -1584,77 +1612,114 @@ class FlipFlop extends Module with CustomSystemVerilog { /// The output of the flop. late final Logic q = output(_qName); - /// To track if optional enable is provided or not. - late final bool _isEnableProvided; + /// The reset value for this flop, if it was a port. + Logic? _resetValuePort; + + /// The reset value for this flop, if it was a constant. + /// + /// Only initialized if a constant value is provided. + late LogicValue _resetValueConst; /// Constructs a flip flop which is positive edge triggered on [clk]. /// /// When optional [en] is provided, an additional input will be created for /// flop. If optional [en] is high or not provided, output will vary as per /// input[d]. For low [en], output remains frozen irrespective of input [d] - FlipFlop(Logic clk, Logic d, {Logic? en, super.name = 'flipflop'}) { + /// + /// When the optional [reset] is provided, the flop will be reset active-high. + /// If no [resetValue] is provided, the reset value is always `0`. Otherwise, + /// it will reset to the provided [resetValue]. The type of [resetValue] must + /// be a valid driver of a [ConditionalAssign] (e.g. [Logic], [LogicValue], + /// [int], etc.). + FlipFlop( + Logic clk, + Logic d, { + Logic? en, + Logic? reset, + dynamic resetValue, + super.name = 'flipflop', + }) { if (clk.width != 1) { throw Exception('clk must be 1 bit'); } - _clkName = Module.unpreferredName('clk'); - _dName = Module.unpreferredName('d'); - _qName = Module.unpreferredName('q'); - addInput(_clkName, clk); addInput(_dName, d, width: d.width); addOutput(_qName, width: d.width); if (en != null) { - if (en.width != 1) { - throw PortWidthMismatchException(en, 1); - } - _enName = Module.unpreferredName('en'); addInput(_enName, en); - _isEnableProvided = true; + } - _setupWithEnable(); - } else { - _isEnableProvided = false; + if (reset != null) { + addInput(_resetName, reset); - _setup(); + if (resetValue != null && resetValue is Logic) { + _resetValuePort = addInput(_resetValueName, resetValue, width: d.width); + } else { + _resetValueConst = LogicValue.of(resetValue ?? 0, width: d.width); + } } + + _setup(); } /// Performs setup for custom functional behavior. void _setup() { - Sequential(_clk, [q < _d]); - } + var contents = [q < _d]; + + if (_en != null) { + contents = [If(_en!, then: contents)]; + } - /// Performs setup for custom functional behavior with enable - void _setupWithEnable() { - Sequential(_clk, [ - If(_en, then: [q < _d]) - ]); + Sequential( + _clk, + contents, + reset: _reset, + resetValues: + _reset != null ? {q: _resetValuePort ?? _resetValueConst} : null, + ); } @override String instantiationVerilog(String instanceType, String instanceName, Map inputs, Map outputs) { - if (_isEnableProvided) { - if (inputs.length != 3 || outputs.length != 1) { - throw Exception('FlipFlop has exactly three inputs and one output.'); - } - } else { - if (inputs.length != 2 || outputs.length != 1) { - throw Exception('FlipFlop has exactly two inputs and one output.'); - } + var expectedInputs = 2; + if (_en != null) { + expectedInputs++; + } + if (_reset != null) { + expectedInputs++; + } + if (_resetValuePort != null) { + expectedInputs++; + } + + if (inputs.length != expectedInputs || outputs.length != 1) { + throw Exception( + 'FlipFlop has exactly $expectedInputs inputs and one output.'); } final clk = inputs[_clkName]!; final d = inputs[_dName]!; final q = outputs[_qName]!; - if (_isEnableProvided) { - final en = inputs[_enName]!; - return 'always_ff @(posedge $clk) if($en) $q <= $d; // $instanceName'; - } else { - return 'always_ff @(posedge $clk) $q <= $d; // $instanceName'; + final svBuffer = StringBuffer('always_ff @(posedge $clk) '); + + if (_reset != null) { + final resetValueString = _resetValuePort != null + ? inputs[_resetValueName]! + : _resetValueConst.toString(); + svBuffer + .write('if(${inputs[_resetName]}) $q <= $resetValueString; else '); } + + if (_en != null) { + svBuffer.write('if(${inputs[_enName]!}) '); + } + + svBuffer.write('$q <= $d; // $instanceName'); + + return svBuffer.toString(); } } diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart index fa7462204..5c17c7eb7 100644 --- a/lib/src/values/logic_value.dart +++ b/lib/src/values/logic_value.dart @@ -256,6 +256,8 @@ abstract class LogicValue implements Comparable { return LogicValue.ofIterable(val).zeroExtend(width); } } + } else if (val == null) { + throw LogicValueConstructionException('Cannot construct from `null`.'); } else { throw LogicValueConstructionException('Unrecognized value type "$val" - ' 'Unknown type ${val.runtimeType}' diff --git a/test/flop_test.dart b/test/flop_test.dart index 97e020e7e..4e3def505 100644 --- a/test/flop_test.dart +++ b/test/flop_test.dart @@ -12,24 +12,26 @@ import 'package:rohd/src/utilities/simcompare.dart'; import 'package:test/test.dart'; class FlopTestModule extends Module { - Logic get a => input('a'); - Logic get y => output('y'); - Logic get en => input('en'); - - FlopTestModule(Logic a, {Logic? en}) : super(name: 'floptestmodule') { + FlopTestModule(Logic a, {Logic? en, Logic? reset, dynamic resetValue}) + : super(name: 'floptestmodule') { a = addInput('a', a, width: a.width); + if (en != null) { - en = addInput('en', en, width: en.width); + en = addInput('en', en); + } + + if (reset != null) { + reset = addInput('reset', reset); + } + + if (resetValue != null && resetValue is Logic) { + resetValue = addInput('resetValue', resetValue, width: a.width); } final y = addOutput('y', width: a.width); final clk = SimpleClockGenerator(10).clk; - if (en != null) { - y <= flop(clk, a, en: en); - } else { - y <= flop(clk, a); - } + y <= flop(clk, a, en: en, reset: reset, resetValue: resetValue); } } @@ -50,9 +52,7 @@ void main() { Vector({'a': 0}, {'y': 0}), ]; await SimCompare.checkFunctionalVector(ftm, vectors); - final simResult = SimCompare.iverilogVector(ftm, vectors); - expect(simResult, equals(true)); - // expect(true, true); + SimCompare.checkIverilogVector(ftm, vectors); }); test('flop bit with enable', () async { @@ -73,9 +73,7 @@ void main() { Vector({'a': 1, 'en': 0}, {'y': 1}), ]; await SimCompare.checkFunctionalVector(ftm, vectors); - final simResult = SimCompare.iverilogVector(ftm, vectors); - expect(simResult, equals(true)); - // expect(true, true); + SimCompare.checkIverilogVector(ftm, vectors); }); test('flop bus', () async { @@ -89,8 +87,7 @@ void main() { Vector({'a': 0x1}, {'y': 0x55}), ]; await SimCompare.checkFunctionalVector(ftm, vectors); - final simResult = SimCompare.iverilogVector(ftm, vectors); - expect(simResult, equals(true)); + SimCompare.checkIverilogVector(ftm, vectors); }); test('flop bus with enable', () async { @@ -113,8 +110,90 @@ void main() { Vector({'a': 0x1, 'en': 1}, {'y': 0x1}), ]; await SimCompare.checkFunctionalVector(ftm, vectors); - final simResult = SimCompare.iverilogVector(ftm, vectors); - expect(simResult, equals(true)); + SimCompare.checkIverilogVector(ftm, vectors); + }); + + test('flop bus reset, no reset value', () async { + final ftm = FlopTestModule(Logic(width: 8), reset: Logic()); + await ftm.build(); + final vectors = [ + Vector({'reset': 1}, {}), + Vector({'reset': 0, 'a': 0xa5}, {'y': 0}), + Vector({'a': 0xff}, {'y': 0xa5}), + Vector({}, {'y': 0xff}), + ]; + await SimCompare.checkFunctionalVector(ftm, vectors); + SimCompare.checkIverilogVector(ftm, vectors); + }); + + test('flop bus reset, const reset value', () async { + final ftm = FlopTestModule( + Logic(width: 8), + reset: Logic(), + resetValue: 3, + ); + await ftm.build(); + final vectors = [ + Vector({'reset': 1}, {}), + Vector({'reset': 0, 'a': 0xa5}, {'y': 3}), + Vector({'a': 0xff}, {'y': 0xa5}), + Vector({}, {'y': 0xff}), + ]; + await SimCompare.checkFunctionalVector(ftm, vectors); + SimCompare.checkIverilogVector(ftm, vectors); + }); + + test('flop bus reset, logic reset value', () async { + final ftm = FlopTestModule( + Logic(width: 8), + reset: Logic(), + resetValue: Logic(width: 8), + ); + await ftm.build(); + final vectors = [ + Vector({'reset': 1, 'resetValue': 5}, {}), + Vector({'reset': 0, 'a': 0xa5}, {'y': 5}), + Vector({'a': 0xff}, {'y': 0xa5}), + Vector({}, {'y': 0xff}), + ]; + await SimCompare.checkFunctionalVector(ftm, vectors); + SimCompare.checkIverilogVector(ftm, vectors); + }); + + test('flop bus no reset, const reset value', () async { + final ftm = FlopTestModule( + Logic(width: 8), + resetValue: 9, + ); + await ftm.build(); + final vectors = [ + Vector({}, {}), + Vector({'a': 0xa5}, {}), + Vector({'a': 0xff}, {'y': 0xa5}), + Vector({}, {'y': 0xff}), + ]; + await SimCompare.checkFunctionalVector(ftm, vectors); + SimCompare.checkIverilogVector(ftm, vectors); + }); + + test('flop bus, enable, reset, const reset value', () async { + final ftm = FlopTestModule( + Logic(width: 8), + en: Logic(), + reset: Logic(), + resetValue: 12, + ); + await ftm.build(); + final vectors = [ + Vector({'reset': 1, 'en': 0}, {}), + Vector({'reset': 0, 'a': 0xa5}, {'y': 12}), + Vector({}, {'y': 12}), + Vector({'en': 1}, {'y': 12}), + Vector({'a': 0xff}, {'y': 0xa5}), + Vector({}, {'y': 0xff}), + ]; + await SimCompare.checkFunctionalVector(ftm, vectors); + SimCompare.checkIverilogVector(ftm, vectors); }); }); } diff --git a/test/interface_test.dart b/test/interface_test.dart index f0844c2db..b2e7421c9 100644 --- a/test/interface_test.dart +++ b/test/interface_test.dart @@ -37,6 +37,16 @@ class UncleanPortInterface extends Interface { } } +class MaybePortInterface extends Interface { + Logic? get p => tryPort('p'); + + MaybePortInterface({required bool includePort}) { + if (includePort) { + setPorts([Port('p')], {MyDirection.dir1}); + } + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -56,4 +66,16 @@ void main() { UncleanPortInterface(); }, throwsException); }); + + group('maybe port', () { + test('tryPort, exists', () { + final intf = MaybePortInterface(includePort: true); + expect(intf.p, isNotNull); + }); + + test('tryPort, doesnt exist', () { + final intf = MaybePortInterface(includePort: false); + expect(intf.p, null); + }); + }); } diff --git a/test/module_test.dart b/test/module_test.dart new file mode 100644 index 000000000..78bc9aecd --- /dev/null +++ b/test/module_test.dart @@ -0,0 +1,46 @@ +// Copyright (C) 2023 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// module_test.dart +// Unit tests for Module APIs +// +// 2023 September 11 +// Author: Max Korbel + +import 'package:rohd/rohd.dart'; +import 'package:test/test.dart'; + +class ModuleWithMaybePorts extends Module { + Logic? get i => tryInput('i'); + Logic? get o => tryOutput('o'); + ModuleWithMaybePorts({required bool addIn, required bool addOut}) { + if (addIn) { + addInput('i', Logic()); + } + if (addOut) { + addOutput('o'); + } + } +} + +void main() { + test('tryInput, exists', () { + final mod = ModuleWithMaybePorts(addIn: true, addOut: false); + expect(mod.i, isNotNull); + }); + + test('tryInput, doesnt exist', () { + final mod = ModuleWithMaybePorts(addIn: false, addOut: false); + expect(mod.i, null); + }); + + test('tryOutput, exists', () { + final mod = ModuleWithMaybePorts(addIn: false, addOut: true); + expect(mod.o, isNotNull); + }); + + test('tryOutput, doesnt exist', () { + final mod = ModuleWithMaybePorts(addIn: false, addOut: false); + expect(mod.o, null); + }); +} diff --git a/test/sequential_test.dart b/test/sequential_test.dart index 2e97920df..834494c86 100644 --- a/test/sequential_test.dart +++ b/test/sequential_test.dart @@ -96,6 +96,35 @@ class ShorthandSeqModule extends Module { } } +class SeqResetValTypes extends Module { + SeqResetValTypes(Logic reset, Logic i) : super(name: 'seqResetValTypes') { + reset = addInput('reset', reset); + i = addInput('i', i, width: 8); + final clk = SimpleClockGenerator(10).clk; + + final a = addOutput('a', width: 8); // int + final b = addOutput('b', width: 8); // LogicValue + final c = addOutput('c', width: 8); // Logic + final d = addOutput('d', width: 8); // none + + Sequential( + clk, + reset: reset, + resetValues: { + a: 7, + b: LogicValue.ofInt(12, 8), + c: i, + }, + [ + a < 8, + b < LogicValue.ofInt(13, 8), + c < i + 1, + d < 4, + ], + ); + } +} + void main() { tearDown(() async { await Simulator.reset(); @@ -161,4 +190,22 @@ void main() { throwsException); }); }); + + test('reset and value types', () async { + final dut = SeqResetValTypes( + Logic(), + Logic(width: 8), + ); + await dut.build(); + + final vectors = [ + Vector({'reset': 1, 'i': 17}, {}), + Vector({'reset': 1}, {'a': 7, 'b': 12, 'c': 17, 'd': 0}), + Vector({'reset': 0}, {'a': 7, 'b': 12, 'c': 17, 'd': 0}), + Vector({'reset': 0}, {'a': 8, 'b': 13, 'c': 18, 'd': 4}), + ]; + + await SimCompare.checkFunctionalVector(dut, vectors); + SimCompare.checkIverilogVector(dut, vectors); + }); }