diff --git a/lib/src/synthesizers/utilities/synth_logic.dart b/lib/src/synthesizers/utilities/synth_logic.dart index 709487a88..64ed3bed1 100644 --- a/lib/src/synthesizers/utilities/synth_logic.dart +++ b/lib/src/synthesizers/utilities/synth_logic.dart @@ -172,7 +172,7 @@ class SynthLogic { .any(parentSynthModuleDefinition.logicHasPresentSynthLogic)); /// Two [SynthLogic]s that are not [mergeable] cannot be merged with each - /// other. If onlyt one of them is not [mergeable], it can adopt the elements + /// other. If only one of them is not [mergeable], it can adopt the elements /// from the other. bool get mergeable => _reservedLogic == null && _constLogic == null && _renameableLogic == null; @@ -293,6 +293,13 @@ class SynthLogic { return (removed: b, kept: a); } + if ((a.isConstant && b.constNameDisallowed) || + (b.isConstant && a.constNameDisallowed)) { + // we cannot merge a constant if the other disallows the constant name + // since we can lose a constant assignment + return null; + } + if (!a.mergeable && !b.mergeable) { return null; } diff --git a/lib/src/synthesizers/utilities/synth_module_definition.dart b/lib/src/synthesizers/utilities/synth_module_definition.dart index 6193b1fc0..570976e7f 100644 --- a/lib/src/synthesizers/utilities/synth_module_definition.dart +++ b/lib/src/synthesizers/utilities/synth_module_definition.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synth_module_definition.dart @@ -885,13 +885,25 @@ class SynthModuleDefinition { while (prevAssignmentCount != assignments.length) { // keep looping until it stops shrinking final reducedAssignments = []; - for (final assignment in assignments) { + for (final assignment in CombinedIterableView([ + // we look at non-constant assignments first to maximize merging in case + // some constant merge scenario is disallowed by a module (e.g. subset) + assignments.where((a) => !a.src.isConstant && !a.dst.isConstant), + assignments.where((a) => a.src.isConstant || a.dst.isConstant), + ])) { assert(assignment is! PartialSynthAssignment, 'Partial assignments should have been removed before this.'); final dst = assignment.dst; final src = assignment.src; + if (src == dst && src.isConstant) { + // looks like this assignment does nothing -- some sort of circular + // constant assignment, can just remove it + + continue; + } + assert(dst != src, 'No circular assignment allowed between $dst and $src.'); diff --git a/test/sv_gen_test.dart b/test/sv_gen_test.dart index cb6aa9fd6..7abb9067b 100644 --- a/test/sv_gen_test.dart +++ b/test/sv_gen_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // sv_gen_test.dart @@ -494,11 +494,165 @@ class ModWithPartialArrayAssignment extends Module { } } +class ModWithConstInlineUnaryOp extends Module { + ModWithConstInlineUnaryOp() { + addOutput('b', width: 8) <= ~Const(0, width: 8); + } +} + +class TieOffSubsetTop extends Module { + LogicArray get outApple => output('outApple') as LogicArray; + LogicArray get outBanana => output('outBanana') as LogicArray; + + TieOffSubsetTop(Logic clk, {required bool withRedirect}) { + clk = addInput('clk', clk); + + var tieoffApple = + Const(0, width: 2).named('apple_tieoff', naming: Naming.mergeable); + + final apple = LogicArray([4], 4, name: 'apple'); + + if (withRedirect) { + tieoffApple = Logic(width: 2)..gets(tieoffApple); + } + + apple.elements[1].assignSubset(tieoffApple.elements, start: 1); + + final submod = TieOffSubsetSub(clk, apple, withRedirect: withRedirect); + + addOutputArray('outApple', dimensions: [4], elementWidth: 4).gets(apple); + addOutputArray('outBanana', dimensions: [4], elementWidth: 4) + .gets(submod.outBanana); + } +} + +class TieOffSubsetSub extends Module { + LogicArray get outBanana => output('banana') as LogicArray; + + TieOffSubsetSub(Logic clk, Logic apple, {required bool withRedirect}) + : super(name: 'submod') { + apple = addInputArray('apple', apple, dimensions: [4], elementWidth: 4); + clk = addInput('clk', clk); + + var tieoffBanana = + Const(0, width: 2).named('banana_tieoff', naming: Naming.mergeable); + + if (withRedirect) { + tieoffBanana = Logic(width: 2)..gets(tieoffBanana); + } + + final banana = addOutputArray('banana', dimensions: [4], elementWidth: 4); + + banana.elements[1].assignSubset(tieoffBanana.elements, start: 1); + } +} + +class TieOffPortTop extends Module { + TieOffPortTop(Logic clk, {required bool withRedirect}) { + clk = addInput('clk', clk); + + var tieoffApple = Const(0).named('apple_tieoff', naming: Naming.mergeable); + + final apple = Logic(name: 'apple', naming: Naming.mergeable); + + if (withRedirect) { + tieoffApple = Logic()..gets(tieoffApple); + } + + apple <= tieoffApple; + + final submod = TieOffPortSub(apple, withRedirect: withRedirect); + + addOutput('outApple') <= submod.outApple; + addOutput('outBanana') <= submod.banana; + } +} + +class TieOffPortSub extends Module { + late final Logic banana; + late final Logic outApple; + TieOffPortSub(Logic apple, {required bool withRedirect}) { + apple = addInput('apple', apple); + + var tieoffBanana = + Const(0).named('banana_tieoff', naming: Naming.mergeable); + + if (withRedirect) { + tieoffBanana = Logic()..gets(tieoffBanana); + } + + banana = addOutput('banana'); + outApple = addOutput('outApple')..gets(apple); + + banana <= tieoffBanana; + } +} + void main() { tearDown(() async { await Simulator.reset(); }); + test('const unary inline op', () async { + final mod = ModWithConstInlineUnaryOp(); + await mod.build(); + + final vectors = [ + Vector({}, {'b': 0xff}), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + + group('tieoff ', () { + for (final redirect in [true, false]) { + group('with redirect=$redirect', () { + test('subset of array', () async { + final mod = TieOffSubsetTop(Logic(), withRedirect: redirect); + await mod.build(); + + final sv = mod.generateSynth(); + + expect(sv, contains("assign banana_tieoff = 2'h0;")); + expect(sv, contains("assign apple_tieoff = 2'h0;")); + + // simcompare to make sure simulation works as expected + final vectors = [ + Vector({}, { + 'outApple': 'zzzzzzzzz00zzzzz', + 'outBanana': 'zzzzzzzzz00zzzzz', + }), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + + test('full port', () async { + final mod = TieOffPortTop(Logic(), withRedirect: redirect); + await mod.build(); + + final sv = mod.generateSynth(); + + expect(sv, contains("assign banana = 1'h0;")); + expect(sv, contains(".apple(1'h0)")); + + // simcompare to make sure simulation works as expected + final vectors = [ + Vector({}, { + 'outApple': 0, + 'outBanana': 0, + }), + ]; + + await SimCompare.checkFunctionalVector(mod, vectors); + SimCompare.checkIverilogVector(mod, vectors); + }); + }); + } + }); + group('signal declaration order', () { void checkSignalDeclarationOrder(String sv, List signalNames) { final expected =