diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6bb9116ce..5898a26b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -124,7 +124,7 @@ Please include the SPDX tag near the top of any new files you create: Here is an example of a recommended file header template: ```dart -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // example.dart diff --git a/benchmark/many_submodules_benchmark.dart b/benchmark/many_submodules_benchmark.dart index 763261a4c..1f95f8488 100644 --- a/benchmark/many_submodules_benchmark.dart +++ b/benchmark/many_submodules_benchmark.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // many_submodules_benchmark.dart @@ -33,7 +33,7 @@ class ManySubmodulesBenchmark extends AsyncBenchmarkBase { Future run() async { final dut = ManySubmodulesModule(Logic(), numSubModules: 10000); await dut.build(); - dut.generateSynth(); + SystemVerilogService(dut).output; } } diff --git a/benchmark/wave_dump_benchmark.dart b/benchmark/wave_dump_benchmark.dart index 777b42eb0..dbae9c58a 100644 --- a/benchmark/wave_dump_benchmark.dart +++ b/benchmark/wave_dump_benchmark.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // wave_dump_benchmark.dart @@ -56,7 +56,7 @@ class WaveDumpBenchmark extends AsyncBenchmarkBase { _mod = _ModuleToDump(Logic(), _clk); await _mod.build(); - WaveDumper(_mod, outputPath: _vcdTemporaryPath); + WaveformService(_mod, outputPath: _vcdTemporaryPath); await Simulator.run(); diff --git a/doc/tutorials/chapter_2/helper.dart b/doc/tutorials/chapter_2/helper.dart index ecd0b5d98..e39a80938 100644 --- a/doc/tutorials/chapter_2/helper.dart +++ b/doc/tutorials/chapter_2/helper.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // helper.dart @@ -13,7 +13,8 @@ import 'package:rohd/rohd.dart'; Future displaySystemVerilog(Module mod) async { await mod.build(); - print('\nYour System Verilog Equivalent Code: \n ${mod.generateSynth()}'); + print('\nYour System Verilog Equivalent Code: \n ' + '${SystemVerilogService(mod).output}'); } class LogicInitialization extends Module { diff --git a/doc/tutorials/chapter_3/answers/exercise_sv.dart b/doc/tutorials/chapter_3/answers/exercise_sv.dart index b4ead1f2c..70cd01e49 100644 --- a/doc/tutorials/chapter_3/answers/exercise_sv.dart +++ b/doc/tutorials/chapter_3/answers/exercise_sv.dart @@ -33,7 +33,7 @@ void main() async { await fSub.build(); // ignore: avoid_print - print(fSub.generateSynth()); + print(SystemVerilogService(fSub).output); test('should return 0 when a and b equal 1', () async { a.put(1); diff --git a/doc/tutorials/chapter_3/full_adder.dart b/doc/tutorials/chapter_3/full_adder.dart index 5e798a69d..5b000437a 100644 --- a/doc/tutorials/chapter_3/full_adder.dart +++ b/doc/tutorials/chapter_3/full_adder.dart @@ -77,5 +77,5 @@ void main() async { final mod = FullAdderModule(a, b, cIn, faOps); await mod.build(); - print(mod.generateSynth()); + print(SystemVerilogService(mod).output); } diff --git a/doc/tutorials/chapter_4/answers/exercise_1_sv.dart b/doc/tutorials/chapter_4/answers/exercise_1_sv.dart index b324d4201..f24926ea5 100644 --- a/doc/tutorials/chapter_4/answers/exercise_1_sv.dart +++ b/doc/tutorials/chapter_4/answers/exercise_1_sv.dart @@ -10,7 +10,7 @@ void main() async { final mod = NBitAdder(a, b); await mod.build(); - print(mod.generateSynth()); + print(SystemVerilogService(mod).output); test('should return 255 when both inputs are added', () { a.put(127); diff --git a/doc/tutorials/chapter_4/answers/exercise_2_sv.dart b/doc/tutorials/chapter_4/answers/exercise_2_sv.dart index c4d3137db..542158e97 100644 --- a/doc/tutorials/chapter_4/answers/exercise_2_sv.dart +++ b/doc/tutorials/chapter_4/answers/exercise_2_sv.dart @@ -9,7 +9,7 @@ void main() async { final mod = NBitSubtractor(a, b); await mod.build(); - print(mod.generateSynth()); + print(SystemVerilogService(mod).output); test('should return 5 when a is 25 and b is 20', () { a.put(25); diff --git a/doc/tutorials/chapter_4/basic_generation_sv.dart b/doc/tutorials/chapter_4/basic_generation_sv.dart index 4c3ff86c0..ccf2a7aa1 100644 --- a/doc/tutorials/chapter_4/basic_generation_sv.dart +++ b/doc/tutorials/chapter_4/basic_generation_sv.dart @@ -78,7 +78,7 @@ void main() async { await nbitAdder.build(); - print(nbitAdder.generateSynth()); + print(SystemVerilogService(nbitAdder).output); test('should return 10 when both inputs are 5.', () async { a.put(5); diff --git a/doc/tutorials/chapter_5/answers/full_adder.dart b/doc/tutorials/chapter_5/answers/full_adder.dart index b7b54d5ed..b94d18beb 100644 --- a/doc/tutorials/chapter_5/answers/full_adder.dart +++ b/doc/tutorials/chapter_5/answers/full_adder.dart @@ -49,7 +49,7 @@ void main() async { final mod = FullAdder(a: a, b: b, carryIn: cIn); await mod.build(); - print(mod.generateSynth()); + print(SystemVerilogService(mod).output); test('should return true if result sum similar to truth table.', () async { for (var i = 0; i <= 1; i++) { diff --git a/doc/tutorials/chapter_5/answers/full_subtractor.dart b/doc/tutorials/chapter_5/answers/full_subtractor.dart index 162b2d72c..ae30383f7 100644 --- a/doc/tutorials/chapter_5/answers/full_subtractor.dart +++ b/doc/tutorials/chapter_5/answers/full_subtractor.dart @@ -44,7 +44,7 @@ Future main() async { await diff.build(); - print(diff.generateSynth()); + print(SystemVerilogService(diff).output); test('should return true if results matched truth table', () async { for (var i = 0; i <= 1; i++) { diff --git a/doc/tutorials/chapter_5/answers/n_bit_subtractor.dart b/doc/tutorials/chapter_5/answers/n_bit_subtractor.dart index 2f12ab6fe..05ec10926 100644 --- a/doc/tutorials/chapter_5/answers/n_bit_subtractor.dart +++ b/doc/tutorials/chapter_5/answers/n_bit_subtractor.dart @@ -36,7 +36,7 @@ Future main() async { final mod = NBitFullSubtractor(a, b); await mod.build(); - print(mod.generateSynth()); + print(SystemVerilogService(mod).output); test('should return 1 when a is 8 and b is 7.', () { a.put(8); diff --git a/doc/tutorials/chapter_5/n_bit_adder.dart b/doc/tutorials/chapter_5/n_bit_adder.dart index 8a4a61944..066a9a878 100644 --- a/doc/tutorials/chapter_5/n_bit_adder.dart +++ b/doc/tutorials/chapter_5/n_bit_adder.dart @@ -79,7 +79,7 @@ void main() async { await nbitAdder.build(); - // print(nbitAdder.generateSynth()); + // print(SystemVerilogService(nbitAdder).output); test('should return 20 when A and B perform add.', () async { a.put(15); diff --git a/doc/tutorials/chapter_7/answers/exercise_1_d_flip_flop.dart b/doc/tutorials/chapter_7/answers/exercise_1_d_flip_flop.dart index 15395d7ec..449099794 100644 --- a/doc/tutorials/chapter_7/answers/exercise_1_d_flip_flop.dart +++ b/doc/tutorials/chapter_7/answers/exercise_1_d_flip_flop.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // exercise_1_d_flip_flop.dart @@ -44,7 +44,7 @@ Future main() async { final dff = DFlipFlop(data, reset, clk); await dff.build(); - print(dff.generateSynth()); + print(SystemVerilogService(dff).output); data.inject(1); reset.inject(1); @@ -60,7 +60,7 @@ Future main() async { unawaited(Simulator.run()); - WaveDumper(dff, + WaveformService(dff, outputPath: 'doc/tutorials/chapter_7/answers/d_flip_flop.vcd'); printFlop('Before'); diff --git a/doc/tutorials/chapter_7/shift_register.dart b/doc/tutorials/chapter_7/shift_register.dart index 1dc98c4f1..c2528e644 100644 --- a/doc/tutorials/chapter_7/shift_register.dart +++ b/doc/tutorials/chapter_7/shift_register.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // shift_register.dart @@ -69,7 +69,7 @@ void main() async { // kick-off the simulator, but we don't want to wait unawaited(Simulator.run()); - WaveDumper(shiftReg, + WaveformService(shiftReg, outputPath: 'doc/tutorials/chapter_7/shift_register.vcd'); printFlop('Before'); diff --git a/doc/tutorials/chapter_8/answers/exercise_1_spi.dart b/doc/tutorials/chapter_8/answers/exercise_1_spi.dart index d8315156d..d56ce685f 100644 --- a/doc/tutorials/chapter_8/answers/exercise_1_spi.dart +++ b/doc/tutorials/chapter_8/answers/exercise_1_spi.dart @@ -139,7 +139,7 @@ void main() async { await tb.build(); - print(tb.generateSynth()); + print(SystemVerilogService(tb).output); testInterface.cs.inject(0); testInterface.sdi.inject(0); @@ -163,7 +163,7 @@ void main() async { Simulator.setMaxSimTime(100); unawaited(Simulator.run()); - WaveDumper(peri, outputPath: 'doc/tutorials/chapter_8/spi-new.vcd'); + WaveformService(peri, outputPath: 'doc/tutorials/chapter_8/spi-new.vcd'); await drive(LogicValue.ofString('01010101')); } diff --git a/doc/tutorials/chapter_8/answers/exercise_2_toycapsule_fsm.dart b/doc/tutorials/chapter_8/answers/exercise_2_toycapsule_fsm.dart index 6912f1313..89b571537 100644 --- a/doc/tutorials/chapter_8/answers/exercise_2_toycapsule_fsm.dart +++ b/doc/tutorials/chapter_8/answers/exercise_2_toycapsule_fsm.dart @@ -49,13 +49,13 @@ Future main(List args) async { final toyCap = ToyCapsuleFSM(clk, reset, dispenseBtn, coin); await toyCap.build(); - print(toyCap.generateSynth()); + print(SystemVerilogService(toyCap).output); toyCap.toyCapsuleStateMachine.generateDiagram(); reset.inject(1); - WaveDumper(toyCap, outputPath: 'toyCapsuleFSM.vcd'); + WaveformService(toyCap, outputPath: 'toyCapsuleFSM.vcd'); Simulator.setMaxSimTime(100); Simulator.registerAction(25, () { diff --git a/doc/tutorials/chapter_8/answers/exercise_3_pipeline.dart b/doc/tutorials/chapter_8/answers/exercise_3_pipeline.dart index ef93701b3..057b45be4 100644 --- a/doc/tutorials/chapter_8/answers/exercise_3_pipeline.dart +++ b/doc/tutorials/chapter_8/answers/exercise_3_pipeline.dart @@ -34,14 +34,14 @@ void main(List args) async { final pipe = Pipeline4Stages(clk, reset, a); await pipe.build(); - // print(pipe.generateSynth()); + // print(SystemVerilogService(pipe).output); a.inject(5); reset.inject(1); Simulator.registerAction(10, () => reset.put(0)); - WaveDumper(pipe, outputPath: 'answer_1.vcd'); + WaveformService(pipe, outputPath: 'answer_1.vcd'); Simulator.registerAction(50, () async { // stage 4 / result: 30 + (30 * 3) = 120 diff --git a/doc/tutorials/chapter_8/carry_save_multiplier.dart b/doc/tutorials/chapter_8/carry_save_multiplier.dart index 82b9521da..783e54f6f 100644 --- a/doc/tutorials/chapter_8/carry_save_multiplier.dart +++ b/doc/tutorials/chapter_8/carry_save_multiplier.dart @@ -108,7 +108,7 @@ void main() async { reset.inject(1); // Attach a waveform dumper so we can see what happens. - WaveDumper(csm, outputPath: 'csm.vcd'); + WaveformService(csm, outputPath: 'csm.vcd'); Simulator.registerAction(10, () { reset.inject(0); diff --git a/doc/tutorials/chapter_8/counter_interface.dart b/doc/tutorials/chapter_8/counter_interface.dart index 41a49eb0e..d8f3fed7d 100644 --- a/doc/tutorials/chapter_8/counter_interface.dart +++ b/doc/tutorials/chapter_8/counter_interface.dart @@ -63,9 +63,9 @@ Future main() async { await counter.build(); - print(counter.generateSynth()); + print(SystemVerilogService(counter).output); - WaveDumper(counter, + WaveformService(counter, outputPath: 'doc/tutorials/chapter_8/counter_interface.vcd'); Simulator.registerAction(25, () { intf.en.put(1); diff --git a/doc/tutorials/chapter_8/oven_fsm.dart b/doc/tutorials/chapter_8/oven_fsm.dart index 172d83006..01f653198 100644 --- a/doc/tutorials/chapter_8/oven_fsm.dart +++ b/doc/tutorials/chapter_8/oven_fsm.dart @@ -192,7 +192,7 @@ Future main({bool noPrint = false}) async { // Attach a waveform dumper so we can see what happens. if (!noPrint) { - WaveDumper(oven, outputPath: 'doc/tutorials/chapter_8/oven.vcd'); + WaveformService(oven, outputPath: 'doc/tutorials/chapter_8/oven.vcd'); } if (!noPrint) { diff --git a/doc/tutorials/chapter_9/rohd_vf_example/lib/rohd_vf_example.dart b/doc/tutorials/chapter_9/rohd_vf_example/lib/rohd_vf_example.dart index 4b1ef9c34..77d2a5a32 100644 --- a/doc/tutorials/chapter_9/rohd_vf_example/lib/rohd_vf_example.dart +++ b/doc/tutorials/chapter_9/rohd_vf_example/lib/rohd_vf_example.dart @@ -315,7 +315,7 @@ Future main({Level loggerLevel = Level.FINER}) async { await tb.counter.build(); // dump wave here - WaveDumper(tb.counter); + WaveformService(tb.counter); // Set a maximum simulation time so it doesn't run forever Simulator.setMaxSimTime(300); diff --git a/example/example.dart b/example/example.dart index 2ddbfc738..55688241a 100644 --- a/example/example.dart +++ b/example/example.dart @@ -61,7 +61,7 @@ Future main({bool noPrint = false}) async { // Let's see what this module looks like as SystemVerilog, so we can pass it // to other tools. - final systemVerilogCode = counter.generateSynth(); + final systemVerilogCode = SystemVerilogService(counter).output; if (!noPrint) { print(systemVerilogCode); } @@ -70,7 +70,7 @@ Future main({bool noPrint = false}) async { // Attach a waveform dumper so we can see what happens. if (!noPrint) { - WaveDumper(counter); + WaveformService(counter); } // Let's also print a message every time the value on the counter changes, diff --git a/example/fir_filter.dart b/example/fir_filter.dart index 1f17f0d3d..2b8424818 100644 --- a/example/fir_filter.dart +++ b/example/fir_filter.dart @@ -96,7 +96,7 @@ Future main({bool noPrint = false}) async { await firFilter.build(); // Generate SystemVerilog code. - final systemVerilogCode = firFilter.generateSynth(); + final systemVerilogCode = SystemVerilogService(firFilter).output; if (!noPrint) { // Print SystemVerilog code to console. print(systemVerilogCode); @@ -108,7 +108,7 @@ Future main({bool noPrint = false}) async { // Attach a waveform dumper. if (!noPrint) { - WaveDumper(firFilter); + WaveformService(firFilter); } // Let's set the initial setting. diff --git a/example/logic_array.dart b/example/logic_array.dart index f772c4929..9a6800f3c 100644 --- a/example/logic_array.dart +++ b/example/logic_array.dart @@ -58,14 +58,14 @@ Future main({bool noPrint = false}) async { // Build the module await logicArrayExample.build(); - final systemVerilogCode = logicArrayExample.generateSynth(); + final systemVerilogCode = SystemVerilogService(logicArrayExample).output; if (!noPrint) { print(systemVerilogCode); } // Simulate the module if (!noPrint) { - WaveDumper(logicArrayExample); + WaveformService(logicArrayExample); } // Set the input values diff --git a/example/oven_fsm.dart b/example/oven_fsm.dart index 2788baa55..f3ac0cf18 100644 --- a/example/oven_fsm.dart +++ b/example/oven_fsm.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // oven_fsm.dart @@ -225,7 +225,7 @@ Future main({bool noPrint = false}) async { // Attach a waveform dumper so we can see what happens. if (!noPrint) { - WaveDumper(oven, outputPath: 'oven.vcd'); + WaveformService(oven, outputPath: 'oven.vcd'); } // Kick off the simulation. diff --git a/example/tree.dart b/example/tree.dart index f5c30a979..1c75fcd3c 100644 --- a/example/tree.dart +++ b/example/tree.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // tree.dart @@ -85,7 +85,7 @@ Future main({bool noPrint = false}) async { // Below will generate an output of the ROHD-generated SystemVerilog: await tree.build(); - final generatedSystemVerilog = tree.generateSynth(); + final generatedSystemVerilog = SystemVerilogService(tree).output; if (!noPrint) { print(generatedSystemVerilog); } diff --git a/lib/rohd.dart b/lib/rohd.dart index 841505590..bda2229c1 100644 --- a/lib/rohd.dart +++ b/lib/rohd.dart @@ -1,6 +1,13 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause +// +// rohd.dart +// Main public API exports for the ROHD framework. +// +// 2026 July +// Author: ROHD Contributors +export 'src/diagnostics/diagnostics.dart'; export 'src/exceptions/exceptions.dart'; export 'src/external.dart'; export 'src/finite_state_machine.dart'; @@ -12,6 +19,7 @@ export 'src/signals/signals.dart'; export 'src/simulator.dart'; export 'src/swizzle.dart'; export 'src/synthesizers/synthesizers.dart'; +export 'src/synthesizers/systemverilog/system_verilog_service.dart'; export 'src/utilities/naming.dart'; export 'src/values/values.dart'; export 'src/wave_dumper.dart'; diff --git a/lib/src/diagnostics/diagnostics.dart b/lib/src/diagnostics/diagnostics.dart new file mode 100644 index 000000000..8ff34cf81 --- /dev/null +++ b/lib/src/diagnostics/diagnostics.dart @@ -0,0 +1,12 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// diagnostics.dart +// Barrel export for the diagnostics library. +// +// 2026 July 16 +// Author: Desmond Kirkpatrick + +export 'module_service.dart'; +export 'module_services.dart'; +export 'waveform_service.dart'; diff --git a/lib/src/diagnostics/inspector_service.dart b/lib/src/diagnostics/inspector_service.dart index 28a9f60dc..f51e5050d 100644 --- a/lib/src/diagnostics/inspector_service.dart +++ b/lib/src/diagnostics/inspector_service.dart @@ -8,6 +8,7 @@ // Author: Yao Jing Quek import 'dart:convert'; +import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; extension _LogicDevToolUtils on Logic { @@ -74,8 +75,8 @@ extension _ModuleDevToolUtils on Module { /// `ModuleTree` implements the Singleton design pattern /// to ensure there is only one instance of it during runtime. /// -/// This class is used to maintain a tree-like structure -/// for managing modules in an application. +/// This class preserves the legacy DevTools inspector entry point for the +/// built module hierarchy. class ModuleTree { /// Private constructor used to initialize the Singleton instance. ModuleTree._(); @@ -86,8 +87,22 @@ class ModuleTree { static ModuleTree get instance => _instance; static final _instance = ModuleTree._(); - /// Stores the root Module instance. - static Module? rootModuleInstance; + Module? _rootModule; + + /// The root [Module] registered for hierarchy inspection. + @internal + Module? get rootModule => _rootModule; + + /// Sets the root [Module] used to produce downstream hierarchy JSON. + /// + /// This is kept as an internal setter instead of a writable field so callers + /// make the bridge explicit: [ModuleServices] is the public service registry, + /// while [ModuleTree] owns the legacy DevTools hierarchy JSON surface + /// consumed by downstream hierarchy adapters. + @internal + set rootModule(Module? module) { + _rootModule = module; + } /// Returns the `hierarchyString` as JSON. /// @@ -95,10 +110,12 @@ class ModuleTree { /// /// Returns: string representing hierarchical structure of modules in JSON /// format. - String get hierarchyJSON => - rootModuleInstance?.buildModuleTreeJsonSchema(rootModuleInstance!) ?? - json.encode({ - 'status': 'fail', - 'reason': 'module not yet build', - }); + String get hierarchyJSON { + final rootModule = _rootModule; + return rootModule?.buildModuleTreeJsonSchema(rootModule) ?? + json.encode({ + 'status': 'fail', + 'reason': 'module not yet build', + }); + } } diff --git a/lib/src/diagnostics/module_service.dart b/lib/src/diagnostics/module_service.dart new file mode 100644 index 000000000..993d98d9a --- /dev/null +++ b/lib/src/diagnostics/module_service.dart @@ -0,0 +1,61 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// module_service.dart +// Common base types shared by all module-scoped services. +// +// 2026 June 23 +// Author: Desmond Kirkpatrick + +import 'package:rohd/rohd.dart'; + +/// The common contract implemented by every module-scoped service that +/// registers with [ModuleServices]. +/// +/// A service wraps some derived view of a built [Module] (synthesis output, +/// netlist, source trace, waveform, etc.) and exposes a JSON-serialisable +/// summary via [toJson]. Concrete services additionally expose their own +/// format-specific accessors; consumers reach them through +/// [ModuleServices.lookup] or the service's own `current` accessor rather than +/// through getters on the registry. +abstract interface class ModuleService { + /// The top-level [Module] this service operates on. + Module get module; + + /// A JSON-serialisable summary of this service. + Map toJson(); +} + +/// A [ModuleService] that emits output to one or more files. +/// +/// Establishes the common output convention shared by synthesis, netlist, +/// trace, and waveform services: +/// - [outputPath] — the default file or directory written by [write]. +/// - [multiFile] — whether [write] emits one file per module definition +/// (a directory) or a single combined file. +/// - [write] — performs the write, honouring [multiFile]. +abstract class OutputService implements ModuleService { + /// The default location written by [write]. + /// + /// Interpreted as a directory when [multiFile] is `true`, otherwise as a + /// single file path. May be `null` when no default has been configured, in + /// which case a path must be passed to [write]. + String? get outputPath; + + /// Whether [write] emits one file per module definition (`true`) or a single + /// combined file (`false`). + bool get multiFile; + + /// Writes this service's output to [path], or to [outputPath] when [path] is + /// omitted. + void write([String? path]); +} + +/// An [OutputService] that generates source-code text. +/// +/// Shared by language code-generation services, which all produce a combined +/// single-file [output]. +abstract class CodeGenService extends OutputService { + /// The combined single-file generated output (including any header). + String get output; +} diff --git a/lib/src/diagnostics/module_services.dart b/lib/src/diagnostics/module_services.dart new file mode 100644 index 000000000..52d61503f --- /dev/null +++ b/lib/src/diagnostics/module_services.dart @@ -0,0 +1,79 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// module_services.dart +// Slim, type-keyed registry of module-scoped services for DevTools and other +// inspection tools. +// +// 2026 April 25 +// Author: Desmond Kirkpatrick + +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/diagnostics/inspector_service.dart'; + +/// A slim, type-keyed registry of [ModuleService]s. +/// +/// Services register themselves here on construction (keyed by their concrete +/// type) and are retrieved with [lookup]. The registry intentionally exposes +/// no per-format accessors: each service owns its own JSON and output methods, +/// reached through [lookup] or the service's own static `current` accessor. +/// +/// The registry references no specific service type, so it is identical across +/// all feature branches that contribute services. +/// +/// **Auto-registered:** +/// - [rootModule] / [hierarchyJSON] — set by [Module.build]. +class ModuleServices { + ModuleServices._(); + + /// The singleton instance. + static final ModuleServices instance = ModuleServices._(); + + // ─── Hierarchy (auto-registered by Module.build) ────────────── + + /// The most recently built top-level [Module]. + /// + /// Set automatically at the end of [Module.build]. + Module? get rootModule => ModuleTree.instance.rootModule; + + /// Sets the most recently built top-level [Module]. + /// + /// Intended for internal use by [Module.build] and test reset paths. + @internal + set rootModule(Module? module) { + ModuleTree.instance.rootModule = module; + } + + /// Returns the module hierarchy as a JSON string. + /// + /// DevTools evaluates this via `EvalOnDartLibrary` to display the module + /// hierarchy. Richer design views (e.g. a slim netlist) are composed by the + /// DevTools client from the relevant registered service. + String get hierarchyJSON => ModuleTree.instance.hierarchyJSON; + + // ─── Type-keyed service registry ────────────────────────────── + + final Map _services = {}; + + /// Registers [service] under the type argument [T]. + /// + /// Replaces any previously registered service of the same type. + void register(T service) { + _services[T] = service; + } + + /// Returns the registered service of type [T], or `null` if none. + T? lookup() => _services[T] as T?; + + /// Removes the registered service of type [T], if any. + void unregister() { + _services.remove(T); + } + + /// Resets all services. Intended for test teardown. + void reset() { + rootModule = null; + _services.clear(); + } +} diff --git a/lib/src/diagnostics/waveform_service.dart b/lib/src/diagnostics/waveform_service.dart new file mode 100644 index 000000000..25e47f19c --- /dev/null +++ b/lib/src/diagnostics/waveform_service.dart @@ -0,0 +1,435 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// waveform_service.dart +// Base waveform service: file output with filtering, timescale, and +// flush/overwrite control. Designed to be subclassed by the DevTools +// streaming variant. +// +// 2026 June +// Author: Desmond Kirkpatrick + +import 'dart:collection'; +import 'dart:io'; + +import 'package:meta/meta.dart'; +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/utilities/config.dart'; +import 'package:rohd/src/utilities/sanitizer.dart'; +import 'package:rohd/src/utilities/timestamper.dart'; +import 'package:rohd/src/utilities/uniquifier.dart'; + +// ─── Supporting types ──────────────────────────────────────────────────────── + +/// The output format for waveform capture. +enum WaveOutputFormat { + /// Value Change Dump — the classic text-based waveform format. + vcd, + + /// Fast Signal Trace — a compact binary format. + /// + /// Requires an FST writer to be available; see the DevTools subclass for + /// a fully FST-backed implementation. + fst, +} + +/// Policy applied when the output file already exists at construction time. +enum OverwritePolicy { + /// Silently overwrite any existing file. + overwrite, + + /// Throw a [FileSystemException] if the file already exists. + failIfExists, +} + +// ─── Service ───────────────────────────────────────────────────────────────── + +/// A waveform capture service that writes signal changes to a file. +/// +/// This is the base class for waveform capture. It handles: +/// - Signal collection (with optional [signalFilter]) +/// - VCD file output with configurable [timescale] +/// - Selective recording via [startTime] / [stopTime] +/// - Periodic buffer flushing and [overwritePolicy] +/// - Optional registration with [ModuleServices] +/// +/// **Subclassing for DevTools streaming:** +/// +/// Override the protected hooks below to intercept the simulation event loop +/// without re-implementing the file-writing logic: +/// +/// - [onSignalCollected] — called once per tracked signal at startup; use +/// it to register signals in a VM-service index. +/// - [onValueChange] — called for every value-change event within the +/// [startTime]/[stopTime] window; use it to feed an in-memory store for +/// streaming. +/// - [onTimestampCapture] — called once per simulation timestamp that +/// contains at least one change; the full changed-signal set is passed. +/// - [onSimulationEnd] — called after the final timestamp is written and +/// the file is closed; use it to finalise any streaming buffers. +/// +/// Example subclass skeleton: +/// ```dart +/// class DevToolsWaveformService extends WaveformService { +/// DevToolsWaveformService(super.module, {super.outputPath}); +/// +/// @override +/// void onSignalCollected(Logic signal) { +/// super.onSignalCollected(signal); +/// _registerWithVmService(signal); +/// } +/// +/// @override +/// void onValueChange(Logic signal, int timestamp) { +/// super.onValueChange(signal, timestamp); +/// _recordInMemory(signal, timestamp); +/// } +/// } +/// ``` +class WaveformService implements ModuleService { + /// The most recently registered [WaveformService], or `null`. + static WaveformService? current; + + /// The top-level [Module] being captured. + @override + final Module module; + + /// Path of the output waveform file. + /// + /// The parent directory is created if necessary. + final String outputPath; + + /// Output format. + final WaveOutputFormat format; + + /// Optional predicate that determines whether a given [Logic] signal is + /// captured. + /// + /// When `null`, all non-[Const] signals in the hierarchy are captured, + /// matching the legacy waveform dumper behaviour. + final bool Function(Logic signal)? signalFilter; + + /// VCD timescale string, e.g. `'1ps'`, `'1ns'`. + final String timescale; + + /// Simulation time at which recording begins. + /// + /// Signals are still collected before this time so they appear in the scope + /// definition, but value-change events are suppressed until [startTime] is + /// reached. `null` means "from the very start". + final int? startTime; + + /// Simulation time at which recording ends. + /// + /// Value-change events after this time are suppressed. `null` means "until + /// end of simulation". + final int? stopTime; + + /// Number of characters accumulated in the write buffer before it is flushed + /// to disk. + final int flushBufferSize; + + /// What to do when the output file already exists. + final OverwritePolicy overwritePolicy; + + /// Whether to register this service with [ModuleServices] for inspection. + final bool register; + + /// Whether to enable DevTools streaming. + /// + /// The base [WaveformService] stores this flag but takes no action on it. + /// The DevTools subclass uses it to conditionally register extensions. + final bool enableDevToolsStreaming; + + // ─── Internal file-writing state ───────────────────────────── + + /// The output file. + late final File _outputFile; + + /// Sink writing into [_outputFile]. + late final IOSink _outFileSink; + + /// Write buffer; flushed when it exceeds [flushBufferSize]. + final StringBuffer _fileBuffer = StringBuffer(); + + /// Counter for assigning compact signal markers in the VCD. + int _signalMarkerIdx = 0; + + /// Maps each captured [Logic] to its VCD marker string. + final Map _signalToMarkerMap = {}; + + /// Signals that changed during the current simulation timestamp. + final Set _changedThisTimestamp = HashSet(); + + /// The timestamp currently being accumulated. + int _currentDumpingTimestamp = Simulator.time; + + // ─── Constructor ───────────────────────────────────────────── + + /// Creates a [WaveformService] for [module]. + /// + /// [module] must be built before construction. + /// + /// Use the optional constructor parameters to configure format, path, + /// filtering, timescale, start/stop times, flush size, and overwrite policy. + WaveformService( + this.module, { + this.outputPath = 'waves.vcd', + this.format = WaveOutputFormat.vcd, + this.signalFilter, + this.timescale = '1ps', + this.startTime, + this.stopTime, + this.flushBufferSize = 100000, + this.overwritePolicy = OverwritePolicy.overwrite, + this.register = true, + this.enableDevToolsStreaming = false, + }) { + if (!module.hasBuilt) { + throw Exception( + 'Module must be built before creating WaveformService. ' + 'Call build() first.', + ); + } + + if (overwritePolicy == OverwritePolicy.failIfExists) { + final f = File(outputPath); + if (f.existsSync()) { + throw FileSystemException( + 'Waveform output file already exists and overwritePolicy is ' + 'failIfExists.', + outputPath, + ); + } + } + + _outputFile = File(outputPath)..createSync(recursive: true); + _outFileSink = _outputFile.openWrite(); + + _collectSignals(); + _writeHeader(); + _writeScope(); + + Simulator.preTick.listen((_) { + if (Simulator.time != _currentDumpingTimestamp) { + if (_changedThisTimestamp.isNotEmpty) { + _captureTimestamp(_currentDumpingTimestamp); + } + _currentDumpingTimestamp = Simulator.time; + } + }); + + Simulator.registerEndOfSimulationAction(() async { + _captureTimestamp(Simulator.time); + await _terminate(); + onSimulationEnd(); + }); + + if (register) { + current = this; + ModuleServices.instance.register(this); + } + } + + // ─── Extensibility hooks ────────────────────────────────────── + + /// Called once for each [Logic] signal that passes + /// [signalFilter] during initial signal collection. + /// + /// Override in a subclass to register signals with an in-memory store, + /// VM service index, or FST handle map. Always call `super` first. + @protected + void onSignalCollected(Logic signal) {} + + /// Called for every value-change event on [signal] at [timestamp]. + /// + /// Only called within the [startTime] / [stopTime] window. + /// + /// Override in a subclass to feed an in-memory waveform store or + /// streaming buffer. Always call `super` first. + @protected + void onValueChange(Logic signal, int timestamp) {} + + /// Called once per simulation timestamp that contains at least one change, + /// after all value-change events for that timestamp have been processed. + /// + /// [changed] is the set of signals that changed at [timestamp]. + /// + /// Override in a subclass to flush incremental streaming payloads. + /// Always call `super` first. + @protected + void onTimestampCapture(int timestamp, Set changed) {} + + /// Called after the final timestamp has been written and the file is closed. + /// + /// Override in a subclass to finalise any streaming buffers or emit + /// end-of-simulation notifications. + @protected + void onSimulationEnd() {} + + // ─── Internal signal collection ────────────────────────────── + + void _collectSignals() { + final modulesToParse = [module]; + for (var i = 0; i < modulesToParse.length; i++) { + final m = modulesToParse[i]; + for (final sig in m.signals) { + if (sig is Const) { + continue; + } + if (signalFilter != null && !signalFilter!(sig)) { + continue; + } + + _signalToMarkerMap[sig] = 's${_signalMarkerIdx++}'; + onSignalCollected(sig); + + sig.changed.listen((_) { + _changedThisTimestamp.add(sig); + }); + } + + for (final subm in m.subModules) { + if (subm is InlineSystemVerilog) { + continue; + } + modulesToParse.add(subm); + } + } + } + + // ─── VCD output helpers ─────────────────────────────────────── + + void _writeHeader() { + final header = ''' +\$date + ${Timestamper.stamp()} +\$end +\$version + ROHD v${Config.version} +\$end +\$comment + Generated by ROHD - www.github.com/intel/rohd +\$end +\$timescale $timescale \$end +'''; + _writeToBuffer(header); + } + + void _writeScope() { + var scopeString = _computeScopeString(module); + scopeString += '\$enddefinitions \$end\n'; + scopeString += '\$dumpvars\n'; + _writeToBuffer(scopeString); + _signalToMarkerMap.keys.forEach(_writeSignalValueUpdate); + _writeToBuffer('\$end\n'); + } + + String _computeScopeString(Module m, {int indent = 0}) { + final moduleSignalUniquifier = Uniquifier(); + final padding = List.filled(indent, ' ').join(); + var scopeString = '$padding\$scope module ${m.uniqueInstanceName} \$end\n'; + final innerScopeString = StringBuffer(); + + for (final sig in m.signals) { + if (!_signalToMarkerMap.containsKey(sig)) { + continue; + } + final width = sig.width; + final marker = _signalToMarkerMap[sig]; + var signalName = Sanitizer.sanitizeSV(sig.name); + signalName = moduleSignalUniquifier.getUniqueName( + initialName: signalName, + reserved: sig.isPort, + ); + innerScopeString.write( + ' $padding\$var wire $width $marker $signalName \$end\n', + ); + } + for (final subModule in m.subModules) { + innerScopeString.write( + _computeScopeString(subModule, indent: indent + 1), + ); + } + if (innerScopeString.isEmpty) { + return ''; + } + + scopeString += innerScopeString.toString(); + scopeString += '$padding\$upscope \$end\n'; + return scopeString; + } + + bool _isInRecordingWindow(int timestamp) { + if (startTime != null && timestamp < startTime!) { + return false; + } + if (stopTime != null && timestamp > stopTime!) { + return false; + } + return true; + } + + void _captureTimestamp(int timestamp) { + if (!_isInRecordingWindow(timestamp)) { + _changedThisTimestamp.clear(); + return; + } + + _writeToBuffer('#$timestamp\n'); + + final snapshot = Set.of(_changedThisTimestamp); + for (final sig in snapshot) { + _writeSignalValueUpdate(sig); + onValueChange(sig, timestamp); + } + _changedThisTimestamp.clear(); + + onTimestampCapture(timestamp, snapshot); + } + + void _writeSignalValueUpdate(Logic signal) { + final binaryValue = signal.value.reversed + .toList() + .map((e) => e.toString(includeWidth: false)) + .join(); + final updateValue = signal.width > 1 + ? 'b$binaryValue ' + : signal.value.toString(includeWidth: false); + final marker = _signalToMarkerMap[signal]; + _writeToBuffer('$updateValue$marker\n'); + } + + // ─── Buffered I/O ───────────────────────────────────────────── + + void _writeToBuffer(String contents) { + _fileBuffer.write(contents); + if (_fileBuffer.length > flushBufferSize) { + _flushBuffer(); + } + } + + void _flushBuffer() { + _outFileSink.write(_fileBuffer.toString()); + _fileBuffer.clear(); + } + + Future _terminate() async { + _flushBuffer(); + await _outFileSink.flush(); + await _outFileSink.close(); + } + + // ─── Inspection ─────────────────────────────────────────────── + + /// Returns a JSON-serialisable summary of this service. + @override + Map toJson() => { + 'outputPath': outputPath, + 'format': format.name, + 'signalCount': _signalToMarkerMap.length, + 'timescale': timescale, + if (startTime != null) 'startTime': startTime!, + if (stopTime != null) 'stopTime': stopTime!, + }; +} diff --git a/lib/src/module.dart b/lib/src/module.dart index a1cb8ec5c..065620e3b 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -13,11 +13,8 @@ import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:rohd/rohd.dart'; import 'package:rohd/src/collections/traverseable_collection.dart'; -import 'package:rohd/src/diagnostics/inspector_service.dart'; -import 'package:rohd/src/utilities/config.dart'; import 'package:rohd/src/utilities/namer.dart'; import 'package:rohd/src/utilities/sanitizer.dart'; -import 'package:rohd/src/utilities/timestamper.dart'; import 'package:rohd/src/utilities/uniquifier.dart'; /// Represents a synthesizable hardware entity with clearly defined interface @@ -341,7 +338,7 @@ abstract class Module { _hasBuilt = true; - ModuleTree.rootModuleInstance = this; + ModuleServices.instance.rootModule = this; } /// Confirms that the post-[build] hierarchy is valid. @@ -1135,10 +1132,15 @@ abstract class Module { /// Returns a synthesized version of this [Module]. /// - /// Currently returns one long file in SystemVerilog, but in the future - /// may have other output formats, languages, files, etc. + /// Currently returns one long file in SystemVerilog, but in the future may + /// have other output formats, languages, files, etc. /// + /// For richer access to per-module file contents, named maps, and individual + /// file writing, see [SystemVerilogService] (and + /// [SystemVerilogService.output] for the equivalent one-shot string). /// The [configuration] controls options specific to SystemVerilog output. + @Deprecated('Use SystemVerilogService(this,' + ' configuration: ...).output instead.') String generateSynth({ SystemVerilogSynthesizerConfiguration configuration = const SystemVerilogSynthesizerConfiguration(), @@ -1147,19 +1149,10 @@ abstract class Module { throw ModuleNotBuiltException(this); } - final synthHeader = ''' -/** - * Generated by ROHD - www.github.com/intel/rohd - * Generation time: ${Timestamper.stamp()} - * ROHD Version: ${Config.version} - */ - -'''; - return synthHeader + - SynthBuilder( - this, - SystemVerilogSynthesizer(configuration: configuration), - ).getSynthFileContents().join('\n\n////////////////////\n\n'); + return SystemVerilogService( + this, + configuration: configuration, + ).output; } } diff --git a/lib/src/modules/conditionals/flop.dart b/lib/src/modules/conditionals/flop.dart index cd9aa8750..c8e461ab2 100644 --- a/lib/src/modules/conditionals/flop.dart +++ b/lib/src/modules/conditionals/flop.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // flop.dart diff --git a/lib/src/signals/wire_net.dart b/lib/src/signals/wire_net.dart index f93529b0f..881bbdd84 100644 --- a/lib/src/signals/wire_net.dart +++ b/lib/src/signals/wire_net.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // wire_net.dart diff --git a/lib/src/synthesizers/synthesizers.dart b/lib/src/synthesizers/synthesizers.dart index b8c8523ec..53fdeb889 100644 --- a/lib/src/synthesizers/synthesizers.dart +++ b/lib/src/synthesizers/synthesizers.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause export 'synth_builder.dart'; diff --git a/lib/src/synthesizers/systemverilog/system_verilog_service.dart b/lib/src/synthesizers/systemverilog/system_verilog_service.dart new file mode 100644 index 000000000..8e5a333ae --- /dev/null +++ b/lib/src/synthesizers/systemverilog/system_verilog_service.dart @@ -0,0 +1,200 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// system_verilog_service.dart +// Service wrapper for SystemVerilog synthesis. +// +// 2026 April 25 +// Author: Desmond Kirkpatrick + +import 'dart:io'; + +import 'package:rohd/rohd.dart'; +import 'package:rohd/src/utilities/config.dart'; +import 'package:rohd/src/utilities/timestamper.dart'; + +/// A service that wraps SystemVerilog synthesis of a [Module] hierarchy. +/// +/// Provides access to the generated SV file contents and per-module +/// synthesis results, and optionally registers with [ModuleServices] +/// for DevTools inspection. +/// +/// Example: +/// ```dart +/// final dut = MyModule(...); +/// await dut.build(); +/// final sv = SystemVerilogService(dut); +/// +/// // Write individual .sv files: +/// sv.writeFiles('build/'); +/// +/// // Or get the concatenated output (like generateSynth): +/// print(sv.output); +/// ``` +class SystemVerilogService extends CodeGenService { + /// The separator inserted between module definitions in the + /// concatenated single-file output from [allContents]. + /// + /// Matches the format historically produced by `Module.generateSynth()`. + static const moduleSeparator = '\n\n////////////////////\n\n'; + + /// The top-level [Module] being synthesized. + @override + final Module module; + + /// The default location written by [write]. + /// + /// A directory when [multiFile] is `true`, otherwise a single file path. + @override + final String? outputPath; + + /// Whether [write] emits one `.sv` file per module definition (`true`) or a + /// single concatenated file (`false`). + @override + final bool multiFile; + + /// Whether generated SV files include the ROHD generation header. + /// + /// Defaults to `true` for single-file output and `false` for multi-file + /// output, preserving the historical layout. Set explicitly to select any + /// combination of header and file layout. + final bool includeHeader; + + /// Configuration controlling generated SystemVerilog. + final SystemVerilogSynthesizerConfiguration configuration; + + /// The underlying [SynthBuilder] that drove synthesis. + late final SynthBuilder synthBuilder; + + /// The generated file contents (one per unique module definition). + late final List fileContents; + + /// Creates a [SystemVerilogService] for [module]. + /// + /// [module] must already be built. + /// + /// If [outputPath] is provided, output is written immediately: a directory + /// of per-module files when [multiFile] is `true`, otherwise the + /// concatenated SV output to that single file. [includeHeader] defaults to + /// `!multiFile` to preserve the historical layout. + SystemVerilogService( + this.module, { + this.outputPath, + this.multiFile = false, + bool? includeHeader, + this.configuration = const SystemVerilogSynthesizerConfiguration(), + }) : includeHeader = includeHeader ?? !multiFile { + if (!module.hasBuilt) { + throw Exception( + 'Module must be built before creating SystemVerilogService. ' + 'Call build() first.', + ); + } + + synthBuilder = SynthBuilder( + module, SystemVerilogSynthesizer(configuration: configuration)); + fileContents = synthBuilder.getSynthFileContents(); + + if (outputPath != null) { + write(); + } + } + + /// All [SynthesisResult]s produced by synthesis. + Set get synthesisResults => synthBuilder.synthesisResults; + + /// Returns the concatenated SystemVerilog module definitions as a single + /// string, without the generation header. + /// + /// For the full output with header (matching `Module.generateSynth()`), + /// use [output]. + String get allContents => + fileContents.map((fc) => fc.contents).join(moduleSeparator); + + /// The ROHD generation header prepended to single-file output. + String get synthHeader => ''' +/** + * Generated by ROHD - www.github.com/intel/rohd + * Generation time: ${Timestamper.stamp()} + * ROHD Version: ${Config.version} + */ + +'''; + + /// The generation header included in each emitted SV file, when enabled. + /// + /// Cached so output and emitted files always use the same timestamp. + late final String header = includeHeader ? synthHeader : ''; + + /// Returns the full single-file SystemVerilog output with header, + /// identical to `Module.generateSynth()`. + /// + /// Computed once and cached so the timestamped header is stable for the + /// lifetime of this service. + @override + late final String output = header + allContents; + + /// Returns SV output for a generated module [instanceTypeName], or `null` + /// when that instance type was not generated. + /// + /// The instance type name is [SynthesisResult.instanceTypeName], the + /// uniquified definition name used in the generated SV. + String? instanceTypeOutput(String instanceTypeName) { + for (final fc in fileContents) { + if (fc.name == instanceTypeName) { + return fc.contents; + } + } + return null; + } + + /// Returns a map from generated module instance type name to its SV contents. + /// + /// Keys are [SynthesisResult.instanceTypeName] (the uniquified definition + /// name used in the generated SV). + @Deprecated('Use instanceTypeOutput(instanceTypeName) for lookup or ' + 'fileContents for iteration instead.') + Map get contentsByName => { + for (final fc in fileContents) fc.name: fc.contents, + }; + + /// Writes each module's SV to a separate file in [directory]. + /// + /// Files are named `.sv`. + void writeFiles(String directory) { + final dir = Directory(directory)..createSync(recursive: true); + for (final fc in fileContents) { + File('${dir.path}/${fc.name}.sv').writeAsStringSync(header + fc.contents); + } + } + + /// Writes the SV output to [path], or to [outputPath] when [path] is omitted. + /// + /// When [multiFile] is `true`, writes one `.sv` file per module definition + /// into the target directory (see [writeFiles]); otherwise writes the + /// concatenated [output] to the target file. + @override + void write([String? path]) { + final target = path ?? outputPath; + if (target == null) { + throw ArgumentError( + 'No output path provided: pass a path to write() or set outputPath.', + ); + } + if (multiFile) { + writeFiles(target); + } else { + File(target) + ..parent.createSync(recursive: true) + ..writeAsStringSync(output); + } + } + + /// Returns a JSON-serialisable summary of the SV synthesis. + /// + /// Contains the list of generated module definition names. + @override + Map toJson() => { + 'modules': [for (final fc in fileContents) fc.name], + }; +} diff --git a/lib/src/synthesizers/systemverilog/systemverilog.dart b/lib/src/synthesizers/systemverilog/systemverilog.dart index 6990cbe2a..0db21104f 100644 --- a/lib/src/synthesizers/systemverilog/systemverilog.dart +++ b/lib/src/synthesizers/systemverilog/systemverilog.dart @@ -1,6 +1,7 @@ // Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause +export 'system_verilog_service.dart'; export 'systemverilog_mixins.dart'; export 'systemverilog_synthesizer.dart'; export 'systemverilog_synthesizer_configuration.dart'; diff --git a/lib/src/synthesizers/utilities/synth_assignment.dart b/lib/src/synthesizers/utilities/synth_assignment.dart index aaafda601..0fcb132b6 100644 --- a/lib/src/synthesizers/utilities/synth_assignment.dart +++ b/lib/src/synthesizers/utilities/synth_assignment.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // synth_assignment.dart diff --git a/lib/src/utilities/simcompare.dart b/lib/src/utilities/simcompare.dart index f8c07eb71..bd993546b 100644 --- a/lib/src/utilities/simcompare.dart +++ b/lib/src/utilities/simcompare.dart @@ -92,8 +92,10 @@ class Vector { } return arrAssigns.toString(); } else { - final signalVal = - LogicValue.of(inputValues[signalName], width: signal.width); + final signalVal = LogicValue.of( + inputValues[signalName], + width: signal.width, + ); return '$signalName = $signalVal;'; } }).join('\n'); @@ -114,8 +116,10 @@ class Vector { var index = 0; for (final leaf in outputPort.leafElements) { final subVal = expectedValue.getRange(index, index + leaf.width); - checksList.add(_errorCheckString( - leaf.structureName, subVal, subVal, inputStimulus)); + checksList.add( + _errorCheckString( + leaf.structureName, subVal, subVal, inputStimulus), + ); index += leaf.width; } } else { @@ -169,51 +173,56 @@ abstract class SimCompare { } if (enableChecking) { - unawaited(Simulator.postTick.first.then((value) { - for (final signalName in vector.expectedOutputValues.keys) { - final value = vector.expectedOutputValues[signalName]; - final o = - module.tryOutput(signalName) ?? module.inOut(signalName); - - final errorReason = - 'For vector #${vectors.indexOf(vector)} $vector,' - ' expected $o to be $value, but it was ${o.value}.'; - if (value is int) { - expect(o.value.isValid, isTrue, reason: errorReason); - expect(o.value.toBigInt(), - equals(BigInt.from(value).toUnsigned(o.width)), - reason: errorReason); - } else if (value is BigInt) { - expect(o.value.isValid, isTrue, reason: errorReason); - expect(o.value.toBigInt(), equals(value), reason: errorReason); - } else if (value is LogicValue) { - if (o.width > 1 && - (value == LogicValue.x || value == LogicValue.z)) { - for (final oBit in o.value.toList()) { - expect(oBit, equals(value), reason: errorReason); + unawaited( + Simulator.postTick.first.then((value) { + for (final signalName in vector.expectedOutputValues.keys) { + final value = vector.expectedOutputValues[signalName]; + final o = + module.tryOutput(signalName) ?? module.inOut(signalName); + + final errorReason = + 'For vector #${vectors.indexOf(vector)} $vector,' + ' expected $o to be $value, but it was ${o.value}.'; + if (value is int) { + expect(o.value.isValid, isTrue, reason: errorReason); + expect(o.value.toBigInt(), + equals(BigInt.from(value).toUnsigned(o.width)), + reason: errorReason); + } else if (value is BigInt) { + expect(o.value.isValid, isTrue, reason: errorReason); + expect(o.value.toBigInt(), equals(value), + reason: errorReason); + } else if (value is LogicValue) { + if (o.width > 1 && + (value == LogicValue.x || value == LogicValue.z)) { + for (final oBit in o.value.toList()) { + expect(oBit, equals(value), reason: errorReason); + } + } else { + expect(o.value, equals(value), reason: errorReason); } + } else if (value is String) { + expect(o.value, LogicValue.of(value, width: o.width), + reason: errorReason); } else { - expect(o.value, equals(value), reason: errorReason); + throw NonSupportedTypeException(value); } - } else if (value is String) { - expect(o.value, LogicValue.of(value, width: o.width), - reason: errorReason); - } else { - throw NonSupportedTypeException(value); } - } - }).catchError( - test: (error) => error is Exception, - (Object err, StackTrace stackTrace) { - Simulator.throwException(err as Exception, stackTrace); - }, - )); + }).catchError( + test: (error) => error is Exception, + (Object err, StackTrace stackTrace) { + Simulator.throwException(err as Exception, stackTrace); + }, + ), + ); } }); timestamp += Vector._period; } - Simulator.registerAction(timestamp + Vector._period, - () {}); // just so it does one more thing at the end + Simulator.registerAction( + timestamp + Vector._period, + () {}, + ); // just so it does one more thing at the end Simulator.setMaxSimTime(timestamp + 2 * Vector._period); await Simulator.run(); } @@ -347,9 +356,9 @@ abstract class SimCompare { allSignals.map((e) => '.$e(${logicToWireMapping[e] ?? e})').join(', '); final moduleInstance = '$topModule dut($moduleConnections);'; final stimulus = vectors.map((e) => e.toTbVerilog(module)).join('\n'); - final generatedVerilog = module.generateSynth( - configuration: synthesizerConfiguration, - ); + final generatedVerilog = + SystemVerilogService(module, configuration: synthesizerConfiguration) + .output; // so that when they run in parallel, they dont step on each other final uniqueId = @@ -403,13 +412,10 @@ abstract class SimCompare { print(maskedOutput); } - return output.toString().contains(RegExp( - [ - 'error', - 'unable', - if (!allowWarnings) 'warning', - ].join('|'), - caseSensitive: false)); + return output.toString().contains( + RegExp(['error', 'unable', if (!allowWarnings) 'warning'].join('|'), + caseSensitive: false), + ); } if (printIfContentsAndCheckError(compileResult.stdout)) { @@ -431,14 +437,22 @@ abstract class SimCompare { if (!dontDeleteTmpFiles) { try { - File(tmpOutput).deleteSync(); - File(tmpTestFile).deleteSync(); + final outFile = File(tmpOutput); + if (outFile.existsSync()) { + outFile.deleteSync(); + } + final testFile = File(tmpTestFile); + if (testFile.existsSync()) { + testFile.deleteSync(); + } if (dumpWaves) { - File(tmpVcdFile).deleteSync(); + final vcdFile = File(tmpVcdFile); + if (vcdFile.existsSync()) { + vcdFile.deleteSync(); + } } } on Exception catch (e) { print("Couldn't delete: $e"); - return false; } } return true; diff --git a/lib/src/values/logic_value.dart b/lib/src/values/logic_value.dart index 4efeef4ab..814125a73 100644 --- a/lib/src/values/logic_value.dart +++ b/lib/src/values/logic_value.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // logic_values.dart diff --git a/lib/src/wave_dumper.dart b/lib/src/wave_dumper.dart index 3a37e55ea..61b7ad643 100644 --- a/lib/src/wave_dumper.dart +++ b/lib/src/wave_dumper.dart @@ -1,233 +1,61 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // wave_dumper.dart -// Waveform dumper for a given module hierarchy, dumps to ".vcd" file. +// Deprecated waveform dumper; use WaveformService instead. // // 2021 May 7 // Author: Max Korbel -import 'dart:collection'; -import 'dart:io'; import 'package:rohd/rohd.dart'; -import 'package:rohd/src/utilities/config.dart'; -import 'package:rohd/src/utilities/sanitizer.dart'; -import 'package:rohd/src/utilities/timestamper.dart'; -import 'package:rohd/src/utilities/uniquifier.dart'; -/// A waveform dumper for simulations. +/// Deprecated: use [WaveformService] instead. /// -/// Outputs to vcd format at [outputPath]. [module] must be built prior to -/// attaching the [WaveDumper]. +/// [WaveDumper] is a simple wrapper around [WaveformService] for backward +/// compatibility. It provides the legacy API for recording all signal changes +/// in a simulation to a VCD file. /// -/// The waves will only dump to the file periodically and then once the -/// simulation has completed. +/// **Migration guide:** +/// +/// Replace: +/// ```dart +/// var dumper = WaveDumper(module, outputPath: 'output.vcd'); +/// ``` +/// +/// With: +/// ```dart +/// var service = WaveformService(module, outputPath: 'output.vcd'); +/// ``` +/// +/// For more control over filtering, timescale, and recording windows, see +/// [WaveformService] constructor parameters. +@Deprecated('Use WaveformService instead') class WaveDumper { + /// The underlying [WaveformService]. + final WaveformService _service; + /// The [Module] being dumped. - final Module module; + Module get module => _service.module; /// The output filepath of the generated waveforms. - final String outputPath; - - /// The file to write dumped output waveform to. - final File _outputFile; - - /// A sink to write contents into [_outputFile]. - late final IOSink _outFileSink; - - /// A buffer for contents before writing to the file sink. - final StringBuffer _fileBuffer = StringBuffer(); - - /// A counter for tracking signal names in the VCD file. - int _signalMarkerIdx = 0; - - /// Stores the mapping from [Logic] to signal marker in the VCD file. - final Map _signalToMarkerMap = {}; - - /// A set of all [Logic]s that have changed in this timestamp so far. - /// - /// This spans across multiple inject or changed events if they are in the - /// same timestamp of the [Simulator]. - final Set _changedLogicsThisTimestamp = HashSet(); - - /// The timestamp which is currently being collected for a dump. - /// - /// When the [Simulator] time progresses beyond this, it will dump all the - /// signals that have changed up until that point at this saved time value. - int _currentDumpingTimestamp = Simulator.time; + String get outputPath => _service.outputPath; /// Attaches a [WaveDumper] to record all signal changes in a simulation of /// [module] in a VCD file at [outputPath]. - WaveDumper(this.module, {this.outputPath = 'waves.vcd'}) - : _outputFile = File(outputPath)..createSync(recursive: true) { - if (!module.hasBuilt) { - throw Exception( - 'Module must be built before passed to dumper. Call build() first.'); - } - - _outFileSink = _outputFile.openWrite(); - - _collectAllSignals(); - - _writeHeader(); - _writeScope(); - - Simulator.preTick.listen((args) { - if (Simulator.time != _currentDumpingTimestamp) { - if (_changedLogicsThisTimestamp.isNotEmpty) { - // no need to write blank timestamps - _captureTimestamp(_currentDumpingTimestamp); - } - _currentDumpingTimestamp = Simulator.time; - } - }); - - Simulator.registerEndOfSimulationAction(() async { - _captureTimestamp(Simulator.time); - - await _terminate(); - }); - } - - /// Number of characters in the buffer after which it will - /// write contents to the output file. - static const _fileBufferLimit = 100000; - - /// Buffers [contents] to be written to the output file. - void _writeToBuffer(String contents) { - _fileBuffer.write(contents); - - if (_fileBuffer.length > _fileBufferLimit) { - _writeToFile(); - } - } - - /// Writes all pending items in the [_fileBuffer] to the file. - void _writeToFile() { - _outFileSink.write(_fileBuffer.toString()); - _fileBuffer.clear(); - } - - /// Terminates the waveform dumping, including closing the file. - Future _terminate() async { - _writeToFile(); - await _outFileSink.flush(); - await _outFileSink.close(); - } - - /// Registers all signal value changes to write updates to the dumped VCD. - void _collectAllSignals() { - final modulesToParse = [module]; - for (var i = 0; i < modulesToParse.length; i++) { - final m = modulesToParse[i]; - for (final sig in m.signals) { - if (sig is Const) { - // constant values are "boring" to inspect - continue; - } - - _signalToMarkerMap[sig] = 's${_signalMarkerIdx++}'; - sig.changed.listen((args) { - _changedLogicsThisTimestamp.add(sig); - }); - } - for (final subm in m.subModules) { - if (subm is InlineSystemVerilog) { - // the InlineSystemVerilog modules are "boring" to inspect - continue; - } - modulesToParse.add(subm); - } - } - } - - /// Writes the top header for the VCD file. - void _writeHeader() { - final dateString = Timestamper.stamp(); - const timescale = '1ps'; - final header = ''' -\$date - $dateString -\$end -\$version - ROHD v${Config.version} -\$end -\$comment - Generated by ROHD - www.github.com/intel/rohd -\$end -\$timescale $timescale \$end -'''; - _writeToBuffer(header); - } - - /// Writes the scope of the VCD, including signal and hierarchy declarations, - /// as well as initial values. - void _writeScope() { - var scopeString = _computeScopeString(module); - scopeString += '\$enddefinitions \$end\n'; - scopeString += '\$dumpvars\n'; - _writeToBuffer(scopeString); - _signalToMarkerMap.keys.forEach(_writeSignalValueUpdate); - _writeToBuffer('\$end\n'); - } - - /// Generates the top of the scope string (signal and hierarchy definitions). - String _computeScopeString(Module m, {int indent = 0}) { - final moduleSignalUniquifier = Uniquifier(); - final padding = List.filled(indent, ' ').join(); - var scopeString = '$padding\$scope module ${m.uniqueInstanceName} \$end\n'; - final innerScopeString = StringBuffer(); - for (final sig in m.signals) { - if (!_signalToMarkerMap.containsKey(sig)) { - continue; - } - - final width = sig.width; - final marker = _signalToMarkerMap[sig]; - var signalName = Sanitizer.sanitizeSV(sig.name); - signalName = moduleSignalUniquifier.getUniqueName( - initialName: signalName, reserved: sig.isPort); - innerScopeString - .write(' $padding\$var wire $width $marker $signalName \$end\n'); - } - for (final subModule in m.subModules) { - innerScopeString - .write(_computeScopeString(subModule, indent: indent + 1)); - } - if (innerScopeString.isEmpty) { - // no need to dump empty scopes - return ''; - } - scopeString += innerScopeString.toString(); - scopeString += '$padding\$upscope \$end\n'; - return scopeString; - } - - /// Writes the current timestamp to the VCD. - void _captureTimestamp(int timestamp) { - final timestampString = '#$timestamp\n'; - _writeToBuffer(timestampString); - - _changedLogicsThisTimestamp - ..forEach(_writeSignalValueUpdate) - ..clear(); - } - - /// Writes the current value of [signal] to the VCD. - void _writeSignalValueUpdate(Logic signal) { - final binaryValue = signal.value.reversed - .toList() - .map((e) => e.toString(includeWidth: false)) - .join(); - final updateValue = signal.width > 1 - ? 'b$binaryValue ' - : signal.value.toString(includeWidth: false); - final marker = _signalToMarkerMap[signal]; - final updateString = '$updateValue$marker\n'; - _writeToBuffer(updateString); - } + /// + /// [module] must be built prior to construction. + /// + /// **Deprecated:** Use [WaveformService] instead, which provides more + /// configuration options (signal filtering, custom timescale, recording + /// windows) and extensibility hooks for streaming applications. + @Deprecated('Use WaveformService instead') + WaveDumper(Module module, {String outputPath = 'waves.vcd'}) + : _service = WaveformService( + module, + outputPath: outputPath, + ); } -/// Deprecated: use [WaveDumper] instead. -@Deprecated('Use WaveDumper instead') +/// Deprecated: use [WaveformService] instead. +@Deprecated('Use WaveformService instead') typedef Dumper = WaveDumper; diff --git a/packages/rohd_hierarchy/LICENSE b/packages/rohd_hierarchy/LICENSE index cfbbee995..9e00cc822 100644 --- a/packages/rohd_hierarchy/LICENSE +++ b/packages/rohd_hierarchy/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (C) 2021-2023 Intel Corporation +Copyright (C) 2021-2026 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/rohd_devtools_extension/LICENSE b/rohd_devtools_extension/LICENSE index cfbbee995..9e00cc822 100644 --- a/rohd_devtools_extension/LICENSE +++ b/rohd_devtools_extension/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (C) 2021-2023 Intel Corporation +Copyright (C) 2021-2026 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/rohd_devtools_extension/lib/rohd_devtools/ui/signal_table.dart b/rohd_devtools_extension/lib/rohd_devtools/ui/signal_table.dart index 8c341c87f..83a02589a 100644 --- a/rohd_devtools_extension/lib/rohd_devtools/ui/signal_table.dart +++ b/rohd_devtools_extension/lib/rohd_devtools/ui/signal_table.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024-2025 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // signal_table.dart diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/LICENSE b/rohd_devtools_extension/packages/rohd_devtools_widgets/LICENSE index cfbbee995..9e00cc822 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/LICENSE +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (C) 2021-2023 Intel Corporation +Copyright (C) 2021-2026 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/rohd_extension/dart/LICENSE b/rohd_extension/dart/LICENSE index cfbbee995..9e00cc822 100644 --- a/rohd_extension/dart/LICENSE +++ b/rohd_extension/dart/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (C) 2021-2023 Intel Corporation +Copyright (C) 2021-2026 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/test/array_collapsing_test.dart b/test/array_collapsing_test.dart index a287cba6f..e1ee42deb 100644 --- a/test/array_collapsing_test.dart +++ b/test/array_collapsing_test.dart @@ -2248,7 +2248,7 @@ void main() { test('simple 1d collapse', () async { final mod = SimpleLAPassthrough(LogicArray([4], 1)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign laOut = laIn;')); }); @@ -2256,7 +2256,7 @@ void main() { test('array collapse for cross-module connection', () async { final mod = ArrayTopMod(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains(RegExp(r'ArraySubModIn.*\.inp\(inp\)'))); expect(sv, contains(RegExp(r'ArraySubModOut.*\.arrOut\(inp\)'))); @@ -2267,7 +2267,7 @@ void main() { LogicArray([3, 3], 1), LogicArray([3, 3], 1)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('net_connect #(.WIDTH(9)) net_connect (intermediate, a);')); expect(sv, @@ -2284,7 +2284,7 @@ void main() { test('partial array assignments collapse into range assignment', () async { final mod = PartialArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[4:2] = src[4:2];')); @@ -2306,7 +2306,7 @@ void main() { () async { final mod = ChainedPartialArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[4:2] = src[4:2];')); @@ -2328,7 +2328,7 @@ void main() { () async { final mod = ChainedSubrangeArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[3:2] = src[6:5];')); @@ -2353,7 +2353,7 @@ void main() { test('three-deep chained range assignments collapse iteratively', () async { final mod = ThreeDeepChainedPartialArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[4:2] = src[4:2];')); @@ -2376,7 +2376,7 @@ void main() { () async { final mod = LongChainedPartialArrayRangeAssignment(); await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); expect(topBody, contains('assign dst[4:2] = src[4:2];')); expect(topBody, isNot(contains('intermediate'))); @@ -2396,7 +2396,7 @@ void main() { test('multi-use chained range intermediate stays expanded', () async { final mod = ChainedPartialArrayRangeAssignment(exposeIntermediate: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2] = src[4:2];'))); @@ -2419,7 +2419,7 @@ void main() { test('renameable chained range intermediate stays expanded', () async { final mod = ChainedPartialArrayRangeAssignment(intermediateNaming: null); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2] = src[4:2];'))); @@ -2442,7 +2442,7 @@ void main() { () async { final mod = PartialBusToArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[5:2] = src[5:2];')); @@ -2467,7 +2467,7 @@ void main() { test('full array-to-bus assignSubset has no subset intermediate', () async { final mod = ArrayToBusAssignSubsetRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('_subset'))); @@ -2486,7 +2486,7 @@ void main() { () async { final mod = ArrayToBusAssignSubsetRangeAssignment(partial: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[5:2] = src[5:2];')); @@ -2523,7 +2523,7 @@ void main() { driveLowBits: config.driveLowBits, ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); if (config.driveLowBits) { @@ -2560,7 +2560,7 @@ void main() { test('bus subset helpers with extra consumers are preserved', () async { final mod = BusSubsetBitsWithExtraConsumers(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[3:0] = src[5:2];')); @@ -2582,7 +2582,7 @@ void main() { test('partial slice helper with extra consumer is preserved', () async { final mod = PartialSliceWithExtraConsumer(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[8:5] = src[9:6];')); @@ -2613,7 +2613,7 @@ void main() { test('sparse bus runs feeding assignSubset collapse independently', () async { final mod = SparseBusRunsToAssignSubsetRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[31:20] = srcA[15:4];')); @@ -2646,7 +2646,7 @@ void main() { test('constant-backed upper range remains tied off after collapse', () async { final mod = TiedRangeToAssignSubsetRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data(({')); @@ -2671,7 +2671,7 @@ void main() { tieNaming: tieNaming, ); await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); expect(topBody, contains('logic [7:0] tie;')); expect(topBody, contains("assign tie = 8'h0;")); @@ -2690,7 +2690,7 @@ void main() { busNaming: Naming.renameable, ); await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); expect(topBody, contains('logic [31:0] bus;')); expect(topBody, contains('.data(bus)')); @@ -2706,7 +2706,7 @@ void main() { test('constant-backed range concatenates with sibling output', () async { final mod = TiedSiblingRangeToAssignSubsetAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data(({')); @@ -2727,7 +2727,7 @@ void main() { test('constant-backed range concatenates into late child input', () async { final mod = TiedSiblingRangeToLateInputSource(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data(({')); @@ -2747,7 +2747,7 @@ void main() { test('named constant subsets survive scalar output collapse', () async { final mod = ScalarSiblingOutputsWithNamedTieTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data(({')); @@ -2776,7 +2776,7 @@ void main() { () async { final mod = InteriorNamedTieWithMappedOutputTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains("assign bus[6:4] = 3'h0;")); @@ -2811,7 +2811,7 @@ void main() { fanout: fanout, ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data(({')); @@ -2849,7 +2849,7 @@ void main() { () async { final mod = InvalidConstantsToAssignSubsetTop(); await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); expect(topBody, contains("2'bxx")); expect(topBody, isNot(contains("2'bzz"))); @@ -2875,7 +2875,7 @@ void main() { () async { final mod = InternalBusRunsToAssignSubsetRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[44:13] = src[31:0];')); @@ -2909,7 +2909,7 @@ void main() { computedSource: true, ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[44:13] = srcStage[31:0];')); @@ -2939,7 +2939,7 @@ void main() { () async { final mod = WideTemporarySliceToArrayWords(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign y[0][15:0] = src[47:32];')); @@ -2968,7 +2968,7 @@ void main() { () async { final mod = WideTemporarySliceToArrayWords(extraConsumers: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign y[0][15:0] = src[47:32];')); @@ -2996,7 +2996,7 @@ void main() { () async { final mod = ManualSubsetNamedArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('manual_subset')); @@ -3018,7 +3018,7 @@ void main() { test('reordered bus-to-array assignments stay expanded', () async { final mod = PartialBusToArrayRangeAssignment(reversed: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[5:2] = src[5:2];'))); @@ -3046,7 +3046,7 @@ void main() { test('bus-to-unpacked-array assignments stay expanded', () async { final mod = PartialBusToArrayRangeAssignment(numUnpackedDimensions: 1); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[5:2] = src[5:2];'))); @@ -3073,7 +3073,7 @@ void main() { test('non-contiguous partial array assignments stay expanded', () async { final mod = PartialArrayRangeAssignment(reversed: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2]'))); @@ -3097,7 +3097,7 @@ void main() { () async { final mod = PartialInnerArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[1][3:1] = src[1][3:1];')); @@ -3122,7 +3122,7 @@ void main() { test('unpacked outer dimension still collapses inner packed range', () async { final mod = PartialInnerArrayRangeAssignment(numUnpackedDimensions: 1); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('assign dst[1][3:1] = src[1][3:1];')); @@ -3146,7 +3146,7 @@ void main() { test('unpacked one-dimensional partial assignments stay expanded', () async { final mod = PartialUnpackedArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2] = src[4:2];'))); @@ -3168,7 +3168,7 @@ void main() { test('wide element partial array assignments stay expanded', () async { final mod = PartialWideArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[2:1] = src[2:1];'))); @@ -3194,7 +3194,7 @@ void main() { test('net array partial assignments stay in net connection flow', () async { final mod = PartialNetArrayRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2] = src[4:2];'))); @@ -3217,7 +3217,7 @@ void main() { () async { final mod = PartialLogicNetRangeAssignment(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('assign dst[4:2] = src[4:2];'))); @@ -3239,7 +3239,7 @@ void main() { final mod = ArrayWithShuffledAssignment(LogicArray([4], 1)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign b[0] = a[3];')); expect(sv, contains('assign b[3] = a[0];')); @@ -3256,7 +3256,7 @@ void main() { LogicArray([3, 3], 1, numUnpackedDimensions: 2)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('net_connect #(.WIDTH(9)) net_connect (intermediate, a);')); expect(sv, @@ -3273,7 +3273,7 @@ void main() { final mod = ArrayModule(LogicArray([4, 4], 1)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign d = c[0];')); expect(sv, contains('assign b = a;')); @@ -3300,7 +3300,7 @@ void main() { name: 'constant_leaf_array_assignment_${cfg.name.replaceAll(' ', '_')}', ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); for (final row in [0, 1]) { @@ -3366,7 +3366,7 @@ void main() { elementWidth: cfg.elementWidth, reversed: cfg.reversed); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // the intermediate array (and every declaration of it) must be gone expect(sv, isNot(contains('arr'))); @@ -3401,7 +3401,7 @@ void main() { LogicNet(width: total), LogicNet(width: total), dimensions: cfg.dimensions, elementWidth: cfg.elementWidth); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // the intermediate array and its net_connects must be gone expect(sv, isNot(contains('arr'))); @@ -3425,7 +3425,7 @@ void main() { final mod = PartiallyDrivenArray(Logic(width: total - 2), dimensions: dimensions); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // the array must remain declared since undriven bits must stay `z` expect(sv, contains('arr')); @@ -3442,7 +3442,7 @@ void main() { test('aggregate-used array is not inlined', () async { final mod = ArrayElementsWithAggregateUse(Logic(width: 4)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // the array stays (aggregate use), so elements are not inlined into ports expect(sv, contains('arr')); @@ -3459,7 +3459,7 @@ void main() { test('input-array port elements are not inlined away', () async { final mod = ArrayPortElementsToSubmodules(LogicArray([2, 2], 2)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // the array port must remain declared expect(sv, contains('a')); @@ -3492,7 +3492,7 @@ void main() { () async { final mod = ConstantToSingleElementArrayInputTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains(".data((8'h0))")); @@ -3509,7 +3509,7 @@ void main() { () async { final mod = ConstantToSingleElementArrayInputTop(value: 0xa5); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains(".data((8'ha5))")); @@ -3543,7 +3543,7 @@ void main() { elementWidth: cfg.elementWidth, perm: cfg.perm); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the intermediate array (and every per-element assignment) is gone, @@ -3580,7 +3580,7 @@ void main() { const n = 4; final mod = MergedSourcesToArrayPort(List.generate(n, (_) => Logic())); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('intermediate'))); @@ -3606,7 +3606,7 @@ void main() { () async { final mod = RangeSourcesToArrayPort(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains(RegExp(r'\.a\(\(\{\s*src,')))); @@ -3649,7 +3649,7 @@ void main() { List.generate(cfg.n, (_) => LogicNet()), LogicNet(width: cfg.n), perm: cfg.perm); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the intermediate array and its net_connects are gone, replaced by a @@ -3680,7 +3680,7 @@ void main() { // restriction prevents collapsing and the array stays declared final mod = MultiUseAggregate(List.generate(4, (_) => Logic())); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // with two whole-array uses, the array stays declared and its per-element @@ -3725,7 +3725,7 @@ void main() { final mod = ArrayPortToIndividualNets( List.generate(4, (_) => LogicNet()), LogicNet(width: 4)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the intermediate array and its net_connects collapse into a single @@ -3752,7 +3752,7 @@ void main() { // result must still be correct. final mod = RearrangeOneArray(LogicArray([4], 1)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // this pass did not fabricate a consolidating concatenation on the port @@ -3780,7 +3780,7 @@ void main() { final mod = IndividualSignalsToExpressionlessPort( List.generate(4, (_) => Logic())); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // no inline concatenation on the expressionless port; per-element @@ -3806,7 +3806,7 @@ void main() { () async { final mod = WholeNetBusCollapseNamingCollision(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('bussubset ('))); @@ -3828,7 +3828,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), busNaming: busNaming); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); if (busNaming == Naming.mergeable) { @@ -3861,7 +3861,7 @@ void main() { final mod = WholeNetBusToPortWithInlineSubsetConsumer( List.generate(n, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data')); @@ -3887,7 +3887,7 @@ void main() { final mod = WholeNetBusToPortWithReadOnlyInlineSubsetConsumer(LogicNet(width: n)); await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); expect(topBody, contains('wire [3:0] bus')); expect(topBody, contains(RegExp('net_connect.*_subset_0_0_bus'))); @@ -3900,7 +3900,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), busNaming: Naming.reserved); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // a reserved name must be preserved, so the bus and its net_connects stay @@ -3925,7 +3925,7 @@ void main() { final mod = WholeNetBusMultiUse(List.generate(n, (_) => LogicNet()), LogicNet(width: n), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // used as a whole twice, so the single-use restriction keeps the bus @@ -3957,7 +3957,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), busNaming: busNaming); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); if (busNaming == Naming.mergeable) { @@ -3991,7 +3991,7 @@ void main() { final mod = BitwiseNetBusToArrayPortWithInlineSubsetConsumer( List.generate(n, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.data')); @@ -4019,7 +4019,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), busNaming: Naming.reserved); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // a reserved bus name must be preserved, so it is not traced away expect(sv, contains('bus')); @@ -4047,7 +4047,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), toArray: toArray); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the self-connection leaves a per-bit net_connect structure intact @@ -4078,7 +4078,7 @@ void main() { () async { final mod = PureSelfLoopNetBus(LogicNet(width: 2), toArray: toArray); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the bus collapses into an inline concatenation of the merged net, and @@ -4101,7 +4101,7 @@ void main() { final mod = AssignSubsetReceiver( List.generate(n, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // no intermediate subset array, and no per-bit net_connects remain @@ -4126,7 +4126,7 @@ void main() { final mod = AssignSubsetReceiverScrambled( List.generate(n, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('_subset'))); @@ -4152,7 +4152,7 @@ void main() { List.generate(n, (_) => LogicNet()), LogicNet(width: n), busNaming: Naming.renameable); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the named bus is preserved, but the per-bit `*_subset` pass-through and @@ -4180,7 +4180,7 @@ void main() { final mod = AssignSubsetDriver( List.generate(n, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the per-bit `*_subset` pass-throughs and per-bit `net_connect`s are @@ -4205,7 +4205,7 @@ void main() { const n = 4; final mod = AssignSubsetLogicDriver(List.generate(n, (_) => Logic())); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // the intermediate `sig_subset` array is forwarded straight into the @@ -4230,7 +4230,7 @@ void main() { () async { final mod = LateSubsetInputTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4248,7 +4248,7 @@ void main() { () async { final mod = LateSlicedSubsetInputTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4266,7 +4266,7 @@ void main() { test('sibling output can drive subset of sibling input source', () async { final mod = SiblingOutputToInputSubsetTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4283,7 +4283,7 @@ void main() { test('sibling full output can drive sibling full input source', () async { final mod = SiblingFullOutputToInputSubsetTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4300,7 +4300,7 @@ void main() { test('sibling output stays connected beside range assignments', () async { final mod = SiblingOutputWithRangeAssignmentsTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.result(bus[7])')); @@ -4326,7 +4326,7 @@ void main() { () async { final mod = IndexedSiblingOutputWithRangeAssignmentsTop(outputIndex); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, contains('.result(bus[$outputIndex])')); @@ -4353,7 +4353,7 @@ void main() { test('multiple sibling outputs stay connected in packed concat', () async { final mod = MultipleSiblingOutputsWithRangeAssignmentsTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.result()'))); @@ -4385,7 +4385,7 @@ void main() { () async { final mod = FanoutSiblingOutputWithRangeAssignmentsTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.result()'))); @@ -4411,7 +4411,7 @@ void main() { test('wide sibling output stays connected after range collapse', () async { final mod = WideSiblingOutputWithRangeAssignmentsTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.result()'))); @@ -4436,7 +4436,7 @@ void main() { test('wide sibling output keeps fanout between constant ranges', () async { final mod = WideSiblingOutputWithConstantsAndFanoutTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.result()'))); @@ -4463,7 +4463,7 @@ void main() { () async { final mod = SiblingArrayOutputToInputSubsetTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4481,7 +4481,7 @@ void main() { () async { final mod = SiblingStructOutputToInputSubsetTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4498,7 +4498,7 @@ void main() { test('sibling inout can drive subset of sibling inout source', () async { final mod = SiblingInOutToInOutSubsetTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); expect(topBody, isNot(contains('.data()'))); @@ -4515,7 +4515,7 @@ void main() { test('sibling boundary kitchen sink keeps mixed source mappings', () async { final mod = SiblingBoundaryProductTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); for (final portName in [ @@ -4556,7 +4556,7 @@ void main() { final mod = AssignSubsetPartial( List.generate(n ~/ 2, (_) => LogicNet()), LogicNet(width: n)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final topBody = _topModuleBody(sv); // not every element is a pass-through, so the subset array is preserved @@ -4660,7 +4660,7 @@ void main() { } await mod.build(); - final topBody = _topModuleBody(mod.generateSynth()); + final topBody = _topModuleBody(SystemVerilogService(mod).output); // --- structural expectations (only where confidently predictable) --- if (config.noSubset) { diff --git a/test/assignment_test.dart b/test/assignment_test.dart index 712ebd9ee..aedc8a624 100644 --- a/test/assignment_test.dart +++ b/test/assignment_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // assignment_test.dart diff --git a/test/benchmark_test.dart b/test/benchmark_test.dart index a65020166..c660b505e 100644 --- a/test/benchmark_test.dart +++ b/test/benchmark_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // benchmark_test.dart diff --git a/test/bus_test.dart b/test/bus_test.dart index 08ccb4c9b..a44226650 100644 --- a/test/bus_test.dart +++ b/test/bus_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // bus_test.dart @@ -228,7 +228,7 @@ void main() { final mod = SingleBitBusSubsetMod(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign result = oneBit')); final vectors = [ @@ -401,7 +401,7 @@ void main() { await SimCompare.checkFunctionalVector(mod, vectors); SimCompare.checkIverilogVector(mod, vectors); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv.contains("assign const_subset = 16'habcd;"), true); }); }); diff --git a/test/collapse_test.dart b/test/collapse_test.dart index 8128eea2d..b60d1fa4c 100644 --- a/test/collapse_test.dart +++ b/test/collapse_test.dart @@ -66,7 +66,7 @@ void main() { test('collapse pretty', () async { final mod = CollapseTestModule(Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // make sure e=a&b&c is in there, to prove there was some inlining expect(sv, contains(RegExp('e.*=.*a.*&.*b.*&.*c'))); @@ -78,7 +78,7 @@ void main() { final mod = CombinationalLoopCollapseModule(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains(' | a')); expect(sv, contains(' == a')); }); diff --git a/test/comb_math_test.dart b/test/comb_math_test.dart index b2a7165ed..79a095a0d 100644 --- a/test/comb_math_test.dart +++ b/test/comb_math_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // comb_math_test.dart diff --git a/test/comb_mod_test.dart b/test/comb_mod_test.dart index 280d19e2e..3e5fe5775 100644 --- a/test/comb_mod_test.dart +++ b/test/comb_mod_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // comb_mod_test.dart diff --git a/test/conditionals_test.dart b/test/conditionals_test.dart index d35a8e5bb..07c1dfcbf 100644 --- a/test/conditionals_test.dart +++ b/test/conditionals_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // conditionals_test.dart diff --git a/test/config_test.dart b/test/config_test.dart index 28cd2e7d8..5f340a2b2 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // version_hash_dumper_test.dart @@ -14,7 +14,7 @@ import 'package:rohd/src/utilities/config.dart'; import 'package:rohd/src/utilities/web.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; -import 'wave_dumper_test.dart'; +import 'waveform_service_test.dart'; class SimpleModule extends Module { SimpleModule(Logic a, Logic b) { @@ -46,6 +46,20 @@ void main() async { final mod = SimpleModule(Logic(), Logic()); await mod.build(); + final sv = SystemVerilogService(mod).output; + + expect(sv, contains(version)); + }); + + test( + 'should contains ROHD version number when deprecated synth is generated.', + () async { + const version = Config.version; + + final mod = SimpleModule(Logic(), Logic()); + await mod.build(); + + // ignore: deprecated_member_use_from_same_package final sv = mod.generateSynth(); expect(sv, contains(version)); diff --git a/test/const_radix_test.dart b/test/const_radix_test.dart index 4e9beb4eb..55a1e1121 100644 --- a/test/const_radix_test.dart +++ b/test/const_radix_test.dart @@ -140,7 +140,7 @@ void main() { final module = _ConstRadixModule(); await module.build(); - final systemVerilog = module.generateSynth(); + final systemVerilog = SystemVerilogService(module).output; expect(systemVerilog, contains("assign autoHex = 8'h2a;")); expect(systemVerilog, contains("assign binaryValue = 8'b101010;")); @@ -154,7 +154,7 @@ void main() { final module = _ExpressionlessRadixTop(); await module.build(); - final systemVerilog = module.generateSynth(); + final systemVerilog = SystemVerilogService(module).output; expect(systemVerilog, contains("assign in = 8'd42;")); expect(systemVerilog, contains('.in(in)')); diff --git a/test/counter_wintf_test.dart b/test/counter_wintf_test.dart index 7889369cc..86c6b8b33 100644 --- a/test/counter_wintf_test.dart +++ b/test/counter_wintf_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // counter_wintf_test.dart @@ -145,7 +145,7 @@ void main() { test('interface ports dont get doubled up', () async { final mod = Counter(CounterInterface(8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(!sv.contains('en_0'), true); }); diff --git a/test/external_test.dart b/test/external_test.dart index 09ac84c87..89c492495 100644 --- a/test/external_test.dart +++ b/test/external_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // external_test.dart @@ -31,7 +31,7 @@ void main() { test('instantiate', () async { final mod = TopModule(Logic(width: 2)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // make sure we instantiate the external module properly expect( diff --git a/test/flop_test.dart b/test/flop_test.dart index 4e3def505..1372877e7 100644 --- a/test/flop_test.dart +++ b/test/flop_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // flop_test.dart diff --git a/test/fsm_test.dart b/test/fsm_test.dart index b5f010a56..9e581c3d3 100644 --- a/test/fsm_test.dart +++ b/test/fsm_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2024 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // fsm_test.dart @@ -183,7 +183,7 @@ void main() { final mod = TestModule(Logic(), Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains("b = 1'h0;")); }); @@ -192,7 +192,7 @@ void main() { final mod = TestModule(Logic(), Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('priority case')); }); @@ -201,7 +201,7 @@ void main() { final mod = TestModule(Logic(), Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('MyStates_state1 : begin')); }); diff --git a/test/gate_test.dart b/test/gate_test.dart index 905c912d9..aeefe9f0d 100644 --- a/test/gate_test.dart +++ b/test/gate_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // gate_test.dart @@ -537,7 +537,7 @@ void main() { final gtm = ShiftTestModule(Logic(width: 3), Logic(width: 8), constant: 0); await gtm.build(); - final sv = gtm.generateSynth(); + final sv = SystemVerilogService(gtm).output; expect(sv, isNot(contains("0'h0"))); diff --git a/test/inout_loopback_test.dart b/test/inout_loopback_test.dart index 0c3b5b343..47dda7161 100644 --- a/test/inout_loopback_test.dart +++ b/test/inout_loopback_test.dart @@ -237,7 +237,7 @@ void main() { ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // The outer module should NOT contain an internal net_connect // for the loopback — the submodule ports should just be wired to the @@ -268,7 +268,7 @@ void main() { final mod = SimpleOuterLoopback(LogicNet(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final outerModuleSv = _extractModuleSv(sv, 'simpleOuter'); expect(outerModuleSv, isNot(contains('net_connect')), @@ -284,7 +284,7 @@ void main() { final mod = LoopbackPairTop(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Check for net_connect in the top module final topModuleSv = _extractModuleSv(sv, 'LoopbackPairTop'); @@ -309,7 +309,7 @@ void main() { ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // The inner module SHOULD have a net_connect (connecting ioA <= ioB). final innerModuleSv = _extractModuleSv(sv, 'innerConnected'); @@ -347,7 +347,7 @@ void main() { final mod = OuterClkLoopback(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // The outer module should NOT have a net_connect — the loopback net // is only used as port connections in the inner instantiation. diff --git a/test/interface_test.dart b/test/interface_test.dart index eaf4433d2..4c924a86f 100644 --- a/test/interface_test.dart +++ b/test/interface_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // interface_test.dart diff --git a/test/logic_array_test.dart b/test/logic_array_test.dart index de5d72513..dbbf9212b 100644 --- a/test/logic_array_test.dart +++ b/test/logic_array_test.dart @@ -751,7 +751,7 @@ void main() { ]; if (checkNoSwizzle) { - expect(mod.generateSynth().contains('swizzle'), false, + expect(SystemVerilogService(mod).output.contains('swizzle'), false, reason: 'Expected no swizzles but found one.'); } @@ -800,7 +800,7 @@ void main() { // unpacked array assignment not fully supported in iverilog await testArrayPassthrough(mod, noSvSim: true); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv.contains(RegExp(r'\[7:0\]\s*laIn\s*\[2:0\]')), true); expect(sv.contains(RegExp(r'\[7:0\]\s*laOut\s*\[2:0\]')), true); }); @@ -818,7 +818,7 @@ void main() { // unpacked array assignment not fully supported in iverilog await testArrayPassthrough(mod, noSvSim: true); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect( sv.contains(RegExp( r'\[2:0\]\s*\[1:0\]\s*\[7:0\]\s*laIn\s*\[4:0\]\s*\[3:0\]')), @@ -846,7 +846,7 @@ void main() { await testArrayPassthrough(mod); // ensure ports with interface are still an array - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('input wire logic [2:0][1:0][2:0][7:0] laIn')); expect(sv, contains('output var logic [2:0][1:0][2:0][7:0] laOut')); }); @@ -861,7 +861,7 @@ void main() { await testArrayPassthrough(mod, noSvSim: true); // ensure ports with interface are still an array - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('input wire logic [1:0][2:0][7:0] laIn [2:0]')); expect(sv, contains('output var logic [1:0][2:0][7:0] laOut [2:0]')); }); @@ -928,7 +928,7 @@ void main() { // unpacked array assignment not fully supported in iverilog await testArrayPassthrough(mod, noSvSim: true); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv.contains('logic [2:0][3:0][7:0] intermediate [1:0]'), true); }); }); @@ -981,7 +981,7 @@ void main() { test('3d', () async { final mod = SimpleArraysAndHierarchy(LogicArray([2], 8)); await testArrayPassthrough(mod); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('SimpleLAPassthrough simple_la_passthrough')); }); @@ -992,7 +992,8 @@ void main() { // unpacked array assignment not fully supported in iverilog await testArrayPassthrough(mod, noSvSim: true); - expect(mod.generateSynth(), contains('SimpleLAPassthrough')); + expect( + SystemVerilogService(mod).output, contains('SimpleLAPassthrough')); }); }); @@ -1001,7 +1002,7 @@ void main() { final mod = FancyArraysAndHierarchy(LogicArray([4, 3, 2], 8)); await testArrayPassthrough(mod, checkNoSwizzle: false); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // make sure the 4th one is there (since we expect 4) expect(sv, contains('SimpleLAPassthrough simple_la_passthrough_2')); @@ -1043,7 +1044,8 @@ void main() { final mod = WithSetArrayOffsetModule(LogicArray([2, 2], 8)); await testArrayPassthrough(mod, checkNoSwizzle: false); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); // make sure we're reassigning both times it overlaps! expect( diff --git a/test/logic_name_config_test.dart b/test/logic_name_config_test.dart index 1a5fb1d98..0f6769b4d 100644 --- a/test/logic_name_config_test.dart +++ b/test/logic_name_config_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // logic_name_config_test.dart @@ -30,7 +30,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains('intermediate')); }); @@ -45,7 +45,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // no intermediate expect(sv.contains('intermediate'), isFalse); @@ -58,7 +58,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // just the ports expect('logic'.allMatches(sv).length, 3); @@ -71,7 +71,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // just the ports expect('logic'.allMatches(sv).length, 3); @@ -90,7 +90,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // held one sticks expect(sv, contains('intermediate_1 = in1')); @@ -108,7 +108,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - dut.generateSynth(); + SystemVerilogService(dut).output; fail('expected an exception!'); } on Exception catch (e) { expect(e, isA()); @@ -124,7 +124,7 @@ void main() { out1 <= intermediate; }); await dut.build(); - dut.generateSynth(); + SystemVerilogService(dut).output; fail('expected an exception!'); } on Exception catch (e) { expect(e, isA()); @@ -145,7 +145,7 @@ void main() { out1 <= intermediate | intermediate2; }); await dut.build(); - dut.generateSynth(); + SystemVerilogService(dut).output; fail('expected an exception!'); } on Exception catch (e) { expect(e, isA()); @@ -167,7 +167,7 @@ void main() { out1 <= ~intermediatePost; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains('goodname')); }); @@ -220,7 +220,7 @@ void main() { out1 <= ~prev; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains(expectedName), reason: 'Amongst ${l.map((e) => e.name).toList()},' @@ -235,7 +235,7 @@ void main() { intermediate <= in1; }); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains('intermediate')); }); diff --git a/test/logic_name_test.dart b/test/logic_name_test.dart index 8ce9d5f40..1325c0d59 100644 --- a/test/logic_name_test.dart +++ b/test/logic_name_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2025 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // logic_name_test.dart @@ -225,13 +225,13 @@ void main() { final mod = LogicWithInternalSignalModule(Logic()); await mod.build(); - expect(mod.generateSynth(), contains('shouldExist')); + expect(SystemVerilogService(mod).output, contains('shouldExist')); }); test('unconnected port does not duplicate internal signal', () async { final pMod = ParentMod(Logic(), Logic()); await pMod.build(); - final sv = pMod.generateSynth(); + final sv = SystemVerilogService(pMod).output; expect(RegExp('logic a[,;\n]').allMatches(sv).length, 2); }); @@ -239,7 +239,7 @@ void main() { test('assigns and gates', () async { final mod = SensitiveNaming(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('e = a & d')); expect(sv, contains('b = a')); expect(sv, contains('d = c')); @@ -248,7 +248,7 @@ void main() { test('bus subset', () async { final mod = BusSubsetNaming(Logic(width: 32)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('c = b[3]')); }); }); @@ -257,7 +257,7 @@ void main() { test('unconnected floating', () async { final mod = DrivenOutputModule(null); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // shouldn't add a Z in there if left floating expect(!sv.contains('z'), true); @@ -266,7 +266,7 @@ void main() { test('driven to z', () async { final mod = DrivenOutputModule(Const('z')); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // should add a Z if it's explicitly added expect(sv, contains('z')); @@ -279,7 +279,7 @@ void main() { portANaming: Naming.renameable, ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect( sv, @@ -295,7 +295,7 @@ void main() { () async { final mod = NameCollisionArrayTop(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect( sv, @@ -312,7 +312,7 @@ void main() { await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains('_wow_______')); }); @@ -321,7 +321,7 @@ void main() { final mod = StructElementNamingModule(VariousNamingStruct()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign outp[0] = outp_renameable;')); expect(sv, contains('assign outp[1] = reserved_outp;')); diff --git a/test/logic_structure_test.dart b/test/logic_structure_test.dart index fdc522e96..531fd3a4b 100644 --- a/test/logic_structure_test.dart +++ b/test/logic_structure_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // logic_structure_test.dart @@ -175,7 +175,7 @@ void main() { final mod = StructModuleWithInstrumentation(Const(0, width: 2)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv.contains('swizzle'), isFalse, reason: 'Should not pack from instrumentation!'); diff --git a/test/logic_test.dart b/test/logic_test.dart index cd3169cab..21a8299a7 100644 --- a/test/logic_test.dart +++ b/test/logic_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2025 Intel Corporation +// Copyright (C) 2025-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // logic_test.dart diff --git a/test/math_test.dart b/test/math_test.dart index d9ada00a0..3e738e940 100644 --- a/test/math_test.dart +++ b/test/math_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // math_test.dart @@ -91,7 +91,7 @@ void main() { final mod = AddWithCarryMod(Logic(width: 8), Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign {carry, sum} = a + b')); }); @@ -119,7 +119,7 @@ void main() { final gtm = MathTestModule(Logic(width: 8), Logic(width: 8)); await gtm.build(); - final sv = gtm.generateSynth(); + final sv = SystemVerilogService(gtm).output; final lines = sv.split('\n'); // ensure we never lshift by a constant directly diff --git a/test/module_merging_test.dart b/test/module_merging_test.dart index 5a5590ce9..d8e98afd3 100644 --- a/test/module_merging_test.dart +++ b/test/module_merging_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // module_merging_test.dart @@ -91,7 +91,7 @@ void main() async { () async { final dut = TrunkWithLeaves(Logic(), Logic()); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect('module ComplicatedLeaf'.allMatches(sv).length, 1); }); @@ -99,7 +99,7 @@ void main() async { test('different reserved definition name modules stay separate', () async { final dut = ParentOfDifferentModuleDefNames(Logic()); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect(sv, contains('module def1')); expect(sv, contains('module def2')); diff --git a/test/module_services_test.dart b/test/module_services_test.dart new file mode 100644 index 000000000..4d09bf513 --- /dev/null +++ b/test/module_services_test.dart @@ -0,0 +1,212 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// module_services_test.dart +// Unit tests for ModuleServices, the service base types, and +// SystemVerilogService. +// +// 2026 April 25 Author: Desmond Kirkpatrick + +@TestOn('vm') +library; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:rohd/rohd.dart'; +import 'package:test/test.dart'; + +class SimpleModule extends Module { + SimpleModule(Logic a) : super(name: 'simple') { + a = addInput('a', a); + addOutput('b') <= ~a; + } +} + +/// A minimal [ModuleService] used to exercise the type-keyed registry. +class FakeService implements ModuleService { + FakeService(this.module); + + @override + final Module module; + + @override + Map toJson() => {'kind': 'fake'}; +} + +void main() { + tearDown(ModuleServices.instance.reset); + + group('ModuleServices registry', () { + test('rootModule is set after build', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + expect(ModuleServices.instance.rootModule, equals(mod)); + }); + + test('hierarchyJSON returns valid JSON', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final json = ModuleServices.instance.hierarchyJSON; + expect(() => jsonDecode(json), returnsNormally); + }); + + test('register and lookup round-trips a service', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final fake = FakeService(mod); + ModuleServices.instance.register(fake); + expect(ModuleServices.instance.lookup(), same(fake)); + }); + + test('lookup returns null when no service registered', () { + expect(ModuleServices.instance.lookup(), isNull); + }); + + test('unregister removes a service', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + ModuleServices.instance.register(FakeService(mod)); + ModuleServices.instance.unregister(); + expect(ModuleServices.instance.lookup(), isNull); + }); + + test('reset clears rootModule and all services', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + ModuleServices.instance.register(FakeService(mod)); + expect(ModuleServices.instance.rootModule, isNotNull); + + ModuleServices.instance.reset(); + expect(ModuleServices.instance.rootModule, isNull); + expect(ModuleServices.instance.lookup(), isNull); + }); + }); + + group('SystemVerilogService', () { + test('is a CodeGenService', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + expect(SystemVerilogService(mod), isA()); + }); + + test('allContents is non-empty', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + expect(sv.allContents, isNotEmpty); + }); + + test('output is non-empty', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + expect(sv.output, isNotEmpty); + }); + + test('instanceTypeOutput returns the instance type contents', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + + final contents = sv.fileContents.single; + expect(sv.instanceTypeOutput(contents.name), equals(contents.contents)); + expect(sv.instanceTypeOutput('DoesNotExist'), isNull); + }); + + test('toJson lists generated modules', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + expect(sv.toJson()['modules'], isList); + }); + + test('writeFiles creates SV files', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + final dir = Directory.systemTemp.createTempSync('sv_test_'); + try { + sv.writeFiles(dir.path); + final files = dir.listSync().whereType().toList(); + expect(files, isNotEmpty); + expect(files.any((f) => f.path.endsWith('.sv')), isTrue); + } finally { + dir.deleteSync(recursive: true); + } + }); + + test('write() emits a single file', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final sv = SystemVerilogService(mod); + final dir = Directory.systemTemp.createTempSync('sv_test_'); + try { + final path = '${dir.path}/out.sv'; + sv.write(path); + expect(File(path).readAsStringSync(), equals(sv.output)); + } finally { + dir.deleteSync(recursive: true); + } + }); + + test('write() with multiFile emits a directory of files', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final dir = Directory.systemTemp.createTempSync('sv_test_'); + try { + // Construction with outputPath writes immediately. + SystemVerilogService(mod, outputPath: dir.path, multiFile: true); + final files = dir.listSync().whereType().toList(); + expect(files.any((f) => f.path.endsWith('.sv')), isTrue); + } finally { + dir.deleteSync(recursive: true); + } + }); + + test('defaults headers by output layout', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + + final singleFile = SystemVerilogService(mod); + final multiFile = SystemVerilogService(mod, multiFile: true); + + expect(singleFile.includeHeader, isTrue); + expect(singleFile.output, startsWith(singleFile.header)); + expect(multiFile.includeHeader, isFalse); + expect(multiFile.header, isEmpty); + }); + + test('writes headers in either output layout when requested', () async { + final mod = SimpleModule(Logic()); + await mod.build(); + final dir = Directory.systemTemp.createTempSync('sv_test_'); + try { + final singlePath = '${dir.path}/single.sv'; + final singleFile = SystemVerilogService(mod, includeHeader: false) + ..write(singlePath); + expect( + File(singlePath).readAsStringSync(), + equals(singleFile.allContents), + ); + + final multiFile = SystemVerilogService( + mod, + multiFile: true, + includeHeader: true, + )..writeFiles(dir.path); + final output = + File('${dir.path}/${multiFile.fileContents.single.name}.sv') + .readAsStringSync(); + expect(output, startsWith(multiFile.header)); + } finally { + dir.deleteSync(recursive: true); + } + }); + + test('throws if module not built', () { + final mod = SimpleModule(Logic()); + expect(() => SystemVerilogService(mod), throwsException); + }); + }); +} diff --git a/test/module_test.dart b/test/module_test.dart index 08502d6f8..4f075a3d0 100644 --- a/test/module_test.dart +++ b/test/module_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // module_test.dart @@ -303,8 +303,8 @@ void main() { disconnectOutputs: disconnectOutputs); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); if (!disconnectOutputs) { expect(sv, contains("assign o = {1'h1,(a ? 1'h0 : 1'h1)}")); @@ -320,8 +320,8 @@ void main() { disconnectOutputs: disconnectOutputs); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); if (!disconnectOutputs) { expect(sv, contains("assign o = {1'h1,a}")); @@ -336,7 +336,8 @@ void main() { TopStructInoutWrap(LogicNet(), LogicNet(), LogicNet(width: 2)); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -352,7 +353,7 @@ void main() { expect( mod.internalSignals.firstWhereOrNull((e) => e.name == 't0'), isNotNull); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign a_concat[0] = t0;')); }); @@ -363,7 +364,7 @@ void main() { expect(mod.internalSignals.firstWhereOrNull((e) => e.name == 'unconnected'), isNotNull); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign a_arr[1] = unconnected;')); }); diff --git a/test/multimodule4_test.dart b/test/multimodule4_test.dart index 52470dc8c..7689aa857 100644 --- a/test/multimodule4_test.dart +++ b/test/multimodule4_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2023 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // multimodule4_test.dart @@ -54,7 +54,7 @@ void main() { .isNotEmpty, 'Should find a z two levels deep'); - final synth = ftm.generateSynth(); + final synth = SystemVerilogService(ftm).output; // "z = 1" means it correctly traversed down from inputs assert(synth.contains('z = 1'), diff --git a/test/multimodule5_test.dart b/test/multimodule5_test.dart index b7642bb24..ccd554e26 100644 --- a/test/multimodule5_test.dart +++ b/test/multimodule5_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2022-2023 Intel Corporation +// Copyright (C) 2022-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // multimodule5_test.dart @@ -35,7 +35,7 @@ void main() { final mod = TopModule(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('Passthrough')); }); diff --git a/test/name_test.dart b/test/name_test.dart index afa757cc8..b1fef564f 100644 --- a/test/name_test.dart +++ b/test/name_test.dart @@ -194,20 +194,20 @@ void main() { test('respected with no conflicts', () async { final mod = SpeciallyNamedModule(Logic(), false, false); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('module specialName (')); }); test('uniquified with conflicts', () async { final mod = TopModule(Logic(), false, false); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('module specialName (')); expect(sv, contains('module specialName_0 (')); }); test('reserved throws exception with conflicts', () async { final mod = TopModule(Logic(), true, false); await mod.build(); - expect(mod.generateSynth, throwsException); + expect(() => SystemVerilogService(mod).output, throwsException); }); }); @@ -215,7 +215,7 @@ void main() { test('uniquified with conflicts', () async { final mod = TopModule(Logic(), false, false); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('specialInstanceName(')); expect(sv, contains('specialInstanceName_0(')); diff --git a/test/naming_cases_test.dart b/test/naming_cases_test.dart index 28128e0f5..aaaa08c5d 100644 --- a/test/naming_cases_test.dart +++ b/test/naming_cases_test.dart @@ -554,7 +554,7 @@ void main() { // ── Golden SV snapshot ────────────────────────────────────── test('golden SV output snapshot', () { - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Port declarations. expect(sv, contains('input wire logic [7:0] inp')); diff --git a/test/naming_namespace_test.dart b/test/naming_namespace_test.dart index 9f1c0c31f..fe2e503c4 100644 --- a/test/naming_namespace_test.dart +++ b/test/naming_namespace_test.dart @@ -82,7 +82,7 @@ void main() { test('constant value appears as literal in SV output', () async { final dut = _ConstantNamingModule(); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // The constant "1" should appear as a literal 1'h1 in the output, // not as a declared signal. @@ -92,7 +92,7 @@ void main() { test('constNameDisallowed falls through to signal naming', () async { final dut = _ConstNameDisallowedModule(); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // The output assignment should NOT use the raw constant literal // as a wire name; a proper signal name should be used instead. @@ -109,7 +109,7 @@ void main() { 'in the shared namespace', () async { final dut = _InstanceSignalCollision(); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // With a single shared namespace, one of the two "inner" identifiers // must be suffixed to avoid collision. @@ -129,7 +129,7 @@ void main() { reason: 'Instance should win the shared namespace ' 'and keep the bare name'); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // The wire (signal) must carry the suffix, not the instance. expect(sv, contains('inner_0'), reason: 'Colliding signal should be renamed to inner_0'); @@ -148,8 +148,8 @@ void main() { String stripHeader(String sv) => sv.replaceFirst(RegExp(r'/\*\*.*?\*/\n', dotAll: true), ''); - final sv1 = stripHeader(dut.generateSynth()); - final sv2 = stripHeader(dut.generateSynth()); + final sv1 = stripHeader(SystemVerilogService(dut).output); + final sv2 = stripHeader(SystemVerilogService(dut).output); expect(sv2, equals(sv1), reason: 'Repeated synthesis passes must produce identical ' @@ -159,7 +159,7 @@ void main() { test('duplicate instance names get uniquified', () async { final dut = _DuplicateInstances(); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; // Two instances named 'blk' — one should be 'blk', the other 'blk_0'. expect(sv, contains('blk')); diff --git a/test/net_bus_test.dart b/test/net_bus_test.dart index 73e8c451e..b91372be7 100644 --- a/test/net_bus_test.dart +++ b/test/net_bus_test.dart @@ -255,7 +255,8 @@ void main() { final mod = NicePortPassingTop(LogicNet(width: 8), LogicNet(width: 8)); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv.contains('net_connect'), isFalse); expect(sv, @@ -314,7 +315,7 @@ void main() { final dut = DoubleNetPassthrough(LogicNet(width: 8), LogicNet(width: 8)); await dut.build(); - final sv = dut.generateSynth(); + final sv = SystemVerilogService(dut).output; expect( sv, @@ -455,7 +456,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect( sv, contains( @@ -517,7 +518,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect( sv, @@ -590,7 +591,7 @@ void main() { await mod.build(); final sv = SvCleaner.removeSwizzleAnnotationComments( - mod.generateSynth()); + SystemVerilogService(mod).output); if (netTypeName == LogicNet) { expect( sv, @@ -620,7 +621,7 @@ void main() { await mod.build(); final sv = SvCleaner.removeSwizzleAnnotationComments( - mod.generateSynth()); + SystemVerilogService(mod).output); if (netTypeName == LogicNet) { expect( sv, @@ -750,8 +751,8 @@ void main() { await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('net_connect (swizzled, ({in0[0],in1[0]}));')); }); @@ -764,8 +765,8 @@ void main() { await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -781,8 +782,8 @@ void main() { await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -799,8 +800,8 @@ void main() { await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -817,8 +818,8 @@ void main() { await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -835,8 +836,8 @@ void main() { ]); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign _in1 = in0;')); expect( @@ -852,8 +853,8 @@ void main() { ]); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -942,7 +943,7 @@ void main() { await mod.build(); final sv = SvCleaner.removeSwizzleAnnotationComments( - mod.generateSynth()); + SystemVerilogService(mod).output); checkSV(sv); final vectors = [ @@ -962,7 +963,7 @@ void main() { await mod.build(); final sv = SvCleaner.removeSwizzleAnnotationComments( - mod.generateSynth()); + SystemVerilogService(mod).output); checkSV(sv); final vectors = [ @@ -1204,8 +1205,8 @@ void main() { final mod = ReplicateMod(LogicNet(width: 4), 2); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, @@ -1225,8 +1226,8 @@ void main() { final mod = ReplicateMod(LogicNet(width: 4), 2); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect( sv, diff --git a/test/net_test.dart b/test/net_test.dart index c8d15b7d7..135a8bcf6 100644 --- a/test/net_test.dart +++ b/test/net_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024-2025 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // net_test.dart @@ -461,7 +461,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('intermediate1')); expect(sv, contains('intermediate2')); expect(sv, contains('intermediate3')); @@ -504,7 +504,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect('SubModInoutOnly submod'.allMatches(sv).length, 1); }); @@ -515,7 +515,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect('SubModInoutOnly submod'.allMatches(sv).length, 1); }); @@ -526,7 +526,7 @@ void main() { await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(' submod'.allMatches(sv).length, 2); }); }); @@ -611,7 +611,7 @@ void main() { isNotNull); } - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // test that " _b;" is not present (indication that a leftover internal // signal was there) @@ -631,7 +631,7 @@ void main() { final mod = NetArrayTopMod(Logic(width: 8), NetArrayIntf()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // print(sv); expect(sv, contains('wire [1:0][1:0][7:0] bd3')); }); @@ -677,7 +677,7 @@ void main() { ); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign c = _a_and_b;')); expect(sv, contains('assign d = _aIntermediate_or_bIntermediate;')); diff --git a/test/pair_interface_hier_test.dart b/test/pair_interface_hier_test.dart index 41567b089..42fc3ad43 100644 --- a/test/pair_interface_hier_test.dart +++ b/test/pair_interface_hier_test.dart @@ -91,7 +91,7 @@ void main() { final mod = HierTop(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('HierConsumer unnamed_module')); expect(sv, contains('HierProducer unnamed_module')); diff --git a/test/pair_interface_hier_w_modify_test.dart b/test/pair_interface_hier_w_modify_test.dart index a3926ba05..52e76306d 100644 --- a/test/pair_interface_hier_w_modify_test.dart +++ b/test/pair_interface_hier_w_modify_test.dart @@ -93,7 +93,7 @@ void main() { final mod = HierTop(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('HierConsumer unnamed_module')); expect(sv, contains('HierProducer unnamed_module')); diff --git a/test/pair_interface_test.dart b/test/pair_interface_test.dart index 724c1f5cb..cfc4fe827 100644 --- a/test/pair_interface_test.dart +++ b/test/pair_interface_test.dart @@ -192,7 +192,7 @@ void main() { await mod.build(); // Make sure the "modify" went through: - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('input wire logic simple_clk')); }); diff --git a/test/pipeline_test.dart b/test/pipeline_test.dart index 31bf38bd9..267ee22db 100644 --- a/test/pipeline_test.dart +++ b/test/pipeline_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2025 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // pipeline_test.dart diff --git a/test/provider_consumer_test.dart b/test/provider_consumer_test.dart index a82b4536e..2a5026abe 100644 --- a/test/provider_consumer_test.dart +++ b/test/provider_consumer_test.dart @@ -176,7 +176,7 @@ void main() { Vector({}, {'rsp_data': 9}), ]; - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains(''' module Provider ( diff --git a/test/provider_consumer_w_modify_test.dart b/test/provider_consumer_w_modify_test.dart index d750c000d..cc3c49ef1 100644 --- a/test/provider_consumer_w_modify_test.dart +++ b/test/provider_consumer_w_modify_test.dart @@ -146,7 +146,7 @@ void main() { Vector({}, {'rsp_data': 9}), ]; - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains(''' module Provider ( diff --git a/test/sequential_test.dart b/test/sequential_test.dart index ade256cf3..a9d73db93 100644 --- a/test/sequential_test.dart +++ b/test/sequential_test.dart @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // // sequential_test.dart // Unit test for Sequential @@ -241,7 +241,7 @@ void main() { final mod = NegedgeTriggeredSeq(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('always_ff @(negedge')); final vectors = [ diff --git a/test/ssa_test.dart b/test/ssa_test.dart index d16f5fb2d..fdd8535c9 100644 --- a/test/ssa_test.dart +++ b/test/ssa_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Intel Corporation +// Copyright (C) 2023-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // ssa_test.dart diff --git a/test/sv_gen_test.dart b/test/sv_gen_test.dart index 13fe8032f..e93e39a87 100644 --- a/test/sv_gen_test.dart +++ b/test/sv_gen_test.dart @@ -698,7 +698,7 @@ void main() { final mod = TieOffSubsetTop(Logic(), withRedirect: redirect); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains("assign banana_tieoff = 2'h0;")); expect(sv, contains("assign apple_tieoff = 2'h0;")); @@ -719,7 +719,7 @@ void main() { final mod = TieOffPortTop(Logic(), withRedirect: redirect); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains("assign banana = 1'h0;")); expect(sv, contains(".apple(1'h0)")); @@ -751,7 +751,7 @@ void main() { test('input, output, and internal signals are sorted', () async { final mod = AlphabeticalModule(Logic(), Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // as instantiated checkSignalDeclarationOrder(sv, ['l', 'a', 'w']); @@ -768,7 +768,7 @@ void main() { () async { final mod = AlphabeticalWidthsModule(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // as instantiated checkSignalDeclarationOrder(sv, ['l', 'a', 'w']); @@ -794,7 +794,7 @@ void main() { final mod = AlphabeticalSubmodulePorts(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; checkPortConnectionOrder(sv, ['l', 'a', 'w', 'm', 'x', 'b']); }); @@ -803,7 +803,7 @@ void main() { final mod = TopWithExpressions(Logic(), Logic(width: 5)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('.a((a | (b[2])))')); }); @@ -812,7 +812,7 @@ void main() { final mod = ModuleWithFloatingSignals(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // only expect 1 assignment to xylophone expect('assign'.allMatches(sv).length, 1); @@ -826,8 +826,8 @@ void main() { final mod = TopCustomSvWrap(Logic(), Logic(), useOld: useOld, banExpressions: banExpressions); await mod.build(); - final sv = - SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); if (banExpressions) { expect(sv, contains('assign my_fancy_new_signal <= ^fer_swizzle;')); @@ -846,7 +846,7 @@ void main() { final mod = ModuleWithCustomDefinitionEmptyPorts(Logic(), acceptsEmptyPortConnections: acceptsEmptyPortConnections); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; if (acceptsEmptyPortConnections) { expect(sv, contains('.b()')); @@ -861,7 +861,7 @@ void main() { test('custom definition', () async { final mod = TopWithCustomDef(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('module CustomDefinitionModule (')); expect(sv, contains('// this is a custom definition!')); @@ -879,7 +879,7 @@ void main() { final mod = ModWithUselessWireMods(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, isNot(contains('swizzle'))); expect(sv, isNot(contains('replicate'))); @@ -897,7 +897,7 @@ endmodule : ModWithUselessWireMods''')); test('partial array assignment sv', () async { final mod = ModWithPartialArrayAssignment(Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign b = aArr[0];')); expect(sv, contains('assign aArr[0] = a;')); @@ -1041,7 +1041,7 @@ endmodule : ModWithUselessWireMods''')); final mod = OutToInOutTop(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('assign myNet = myOut;')); @@ -1057,7 +1057,7 @@ endmodule : ModWithUselessWireMods''')); () async { final mod = _StructLeafNamingModule(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, isNot(contains('_in0')), reason: 'Struct leaf from unnamed Logic() should use its ' @@ -1067,7 +1067,7 @@ endmodule : ModWithUselessWireMods''')); test('const merge not blocked by constNameDisallowed', () async { final mod = _ConstNamingModule(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; final constAssignments = RegExp(r"assign \w+ = 8'h0;").allMatches(sv).length; diff --git a/test/sv_param_passthrough_test.dart b/test/sv_param_passthrough_test.dart index e7b0876dd..e36150d97 100644 --- a/test/sv_param_passthrough_test.dart +++ b/test/sv_param_passthrough_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2024 Intel Corporation +// Copyright (C) 2024-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // sv_param_passthrough_test.dart @@ -162,7 +162,7 @@ void main() { () async { final mod = TopForEmptyParams(Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv.contains('#'), isFalse); }); } diff --git a/test/swizzle_test.dart b/test/swizzle_test.dart index d6ccf9d4f..473570c08 100644 --- a/test/swizzle_test.dart +++ b/test/swizzle_test.dart @@ -188,7 +188,7 @@ void main() { final mod = SwizzleVariety(Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('/*')); expect(sv, contains('*/')); @@ -211,7 +211,7 @@ void main() { final mod = SingleElementSwizzle(Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Single element should not have braces or bit range annotations // Look for bit range annotations specifically (/* number */) @@ -236,7 +236,7 @@ void main() { final mod = AllSingleBitSwizzle(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Should have bit range annotations for single bits expect(sv, contains('/*')); @@ -267,7 +267,7 @@ void main() { final mod = NestedSwizzle(Logic(width: 4), Logic(width: 3)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Should contain annotations for both inner and outer swizzles expect(sv, contains('/*')); @@ -287,7 +287,7 @@ void main() { final mod = InlinedSwizzle(Logic(width: 4), Logic(width: 4)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Should have annotations even when swizzle is part of larger expression expect(sv, contains('/*')); @@ -307,7 +307,7 @@ void main() { final mod = VariedWidthSwizzle(); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // Should have aligned bit range annotations expect(sv, contains('/*')); @@ -345,7 +345,7 @@ void main() { // Create a module with indices requiring different digit widths final largeModule = LargeWidthSwizzle(); await largeModule.build(); - final sv = largeModule.generateSynth(); + final sv = SystemVerilogService(largeModule).output; // Should have properly aligned annotations despite different digit counts expect(sv, contains('/*')); @@ -389,7 +389,8 @@ void main() { final mod = SwizzleAdjacentBitSlices(); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign out = {a[7:5],a[2:0]};')); expect(sv, isNot(contains('a[7],a[6]'))); @@ -412,7 +413,8 @@ void main() { final mod = SwizzleAllAdjacentBitSlices(); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign out = a[7:5];')); expect(sv, isNot(contains('assign out = {a[7:5]};'))); @@ -430,7 +432,8 @@ void main() { final mod = SwizzleAscendingBitSlices(); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, isNot(contains('a[2:0]'))); expect(sv, contains('a[0]')); @@ -457,7 +460,8 @@ void main() { final mod = SwizzleNestedAdjacentBitSlices(); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign out = {(a[7:5]),a[4:3]};')); expect(sv, isNot(contains('a[7:3]'))); @@ -475,7 +479,8 @@ void main() { final mod = SwizzleAdjacentRanges(); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign out = {(a[5:2]),(a[1:0])};')); expect(sv, isNot(contains('a[5:0]'))); @@ -494,7 +499,8 @@ void main() { final mod = SwizzlePackedArrayElementBits(LogicArray([2, 2], 1)); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, contains('assign out = {arr[1][1:0],arr[0][1:0]};')); expect(sv, isNot(contains('arr[1][1],arr[1][0]'))); @@ -514,7 +520,8 @@ void main() { ); await mod.build(); - final sv = SvCleaner.removeSwizzleAnnotationComments(mod.generateSynth()); + final sv = SvCleaner.removeSwizzleAnnotationComments( + SystemVerilogService(mod).output); expect(sv, isNot(contains('arr[3:0]'))); expect(sv, contains('arr[3]')); @@ -528,7 +535,7 @@ void main() { final mod = SwizzleVariety(Logic(width: 8)); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains(''' assign b = { diff --git a/test/systemverilog_port_types_test.dart b/test/systemverilog_port_types_test.dart index 17f586cbd..ea1b8b59f 100644 --- a/test/systemverilog_port_types_test.dart +++ b/test/systemverilog_port_types_test.dart @@ -104,7 +104,10 @@ void main() { final module = _PortTypesModule(); await module.build(); - final sv = module.generateSynth(configuration: testCase.configuration); + final sv = SystemVerilogService( + module, + configuration: testCase.configuration, + ).output; final declarations = { testCase.inputPrefix: [ diff --git a/test/translations_test.dart b/test/translations_test.dart index 6d53d4f74..eb5fdf41c 100644 --- a/test/translations_test.dart +++ b/test/translations_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // // translations_test.dart diff --git a/test/typed_port_test.dart b/test/typed_port_test.dart index 67aba51ab..f29bf11e4 100644 --- a/test/typed_port_test.dart +++ b/test/typed_port_test.dart @@ -229,7 +229,7 @@ void main() { final mod = SimpleStructModuleContainer(Logic(), Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, isNot(contains('internal_struct'))); @@ -251,7 +251,7 @@ void main() { expect(mod.anyOut, isA()); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, contains('input wire logic [3:0][1:0] anyIn')); expect(sv, contains('output var logic [3:0][1:0] anyOut')); @@ -277,7 +277,7 @@ void main() { final mod = ParentModuleWithStructsContainingPorts(Logic()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // if naming is wrong, these names will appear in the SV in ports expect( @@ -358,7 +358,7 @@ void main() { SimpleStructModuleContainer(LogicNet(), LogicNet(), asNet: true); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; expect(sv, isNot(contains('internal_struct'))); @@ -503,7 +503,7 @@ void main() { final mod = ModuleWithOneBitStructPort(OneBitStruct()); await mod.build(); - final sv = mod.generateSynth(); + final sv = SystemVerilogService(mod).output; // no slicing on single-bit signals expect(sv, contains('assign outStruct = outStruct_oneBit')); diff --git a/test/wave_dumper_test.dart b/test/waveform_service_test.dart similarity index 82% rename from test/wave_dumper_test.dart rename to test/waveform_service_test.dart index 07aafc8c8..1a5b30f97 100644 --- a/test/wave_dumper_test.dart +++ b/test/waveform_service_test.dart @@ -1,8 +1,8 @@ -// Copyright (C) 2021-2024 Intel Corporation +// Copyright (C) 2021-2026 Intel Corporation // SPDX-License-Identifier: BSD-3-Clause // -// wave_dumper_test.dart -// Tests for the WaveDumper +// waveform_service_test.dart +// Tests for the WaveformService // // 2021 November 4 // Author: Max Korbel @@ -40,10 +40,19 @@ const tempDumpDir = 'tmp_test'; /// Gets the path of the VCD file based on a name. String temporaryDumpPath(String name) => '$tempDumpDir/temp_dump_$name.vcd'; -/// Attaches a [WaveDumper] to [module] to VCD with [name]. +/// Attaches a [WaveformService] to [module] to VCD with [name]. void createTemporaryDump(Module module, String name) { Directory(tempDumpDir).createSync(recursive: true); final tmpDumpFile = temporaryDumpPath(name); + WaveformService(module, outputPath: tmpDumpFile); +} + +// ignore: deprecated_member_use_from_same_package +/// Attaches the deprecated [WaveDumper] to [module] to VCD with [name]. +void createTemporaryWaveDumperDump(Module module, String name) { + Directory(tempDumpDir).createSync(recursive: true); + final tmpDumpFile = temporaryDumpPath(name); + // ignore: deprecated_member_use_from_same_package WaveDumper(module, outputPath: tmpDumpFile); } @@ -86,6 +95,34 @@ void main() { deleteTemporaryDump(dumpName); }); + test('attach deprecated wave dumper after put', () async { + final a = Logic(name: 'a'); + final mod = SimpleModule(a); + await mod.build(); + + const dumpName = 'deprecatedDumpAfterPut'; + + a.put(1); + createTemporaryWaveDumperDump(mod, dumpName); + + Simulator.registerAction(10, () => a.put(0)); + await Simulator.run(); + + final vcdContents = File(temporaryDumpPath(dumpName)).readAsStringSync(); + + expect( + VcdParser.confirmValue(vcdContents, 'a', 0, LogicValue.ofString('1')), + equals(true)); + expect( + VcdParser.confirmValue(vcdContents, 'a', 5, LogicValue.ofString('1')), + equals(true)); + expect( + VcdParser.confirmValue(vcdContents, 'a', 10, LogicValue.ofString('0')), + equals(true)); + + deleteTemporaryDump(dumpName); + }); + test('attach dumper before put', () async { final a = Logic(name: 'a'); final mod = SimpleModule(a); @@ -241,11 +278,12 @@ void main() { const dir1Path = '$tempDumpDir/dir1'; - final waveDumper = WaveDumper(mod, outputPath: '$dir1Path/dir2/waves.vcd'); + final waveformService = + WaveformService(mod, outputPath: '$dir1Path/dir2/waves.vcd'); - expect(File(waveDumper.outputPath).existsSync(), equals(true)); + expect(File(waveformService.outputPath).existsSync(), equals(true)); - if (File(waveDumper.outputPath).existsSync()) { + if (File(waveformService.outputPath).existsSync()) { File(dir1Path).deleteSync(recursive: true); } }); @@ -263,7 +301,7 @@ void main() { Simulator.registerAction(13, () => reset.put(1)); reset.put(0); - // add wave dumper *after* the put to reset + // add waveform service *after* the put to reset createTemporaryDump(mod, dumpName); // check functional matches diff --git a/tool/gh_actions/check_tmp_test.sh b/tool/gh_actions/check_tmp_test.sh index a21154d8d..b506317ea 100755 --- a/tool/gh_actions/check_tmp_test.sh +++ b/tool/gh_actions/check_tmp_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2022-2024 Intel Corporation +# Copyright (C) 2022-2026 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # # check_tmp_test.sh diff --git a/tool/gh_actions/install_dependencies.sh b/tool/gh_actions/install_dependencies.sh index 7aef34082..aaa0567b5 100755 --- a/tool/gh_actions/install_dependencies.sh +++ b/tool/gh_actions/install_dependencies.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2022-2023 Intel Corporation +# Copyright (C) 2022-2026 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # # install_dependencies.sh @@ -12,3 +12,11 @@ set -euo pipefail dart pub get + +# Install dependencies for sub-packages +for pkg in packages/*/; do + if [ -f "${pkg}pubspec.yaml" ]; then + echo "Installing dependencies for ${pkg}..." + (cd "$pkg" && dart pub get) + fi +done diff --git a/tool/gh_actions/install_node.sh b/tool/gh_actions/install_node.sh index 8e1b5b74b..01a29748a 100755 --- a/tool/gh_actions/install_node.sh +++ b/tool/gh_actions/install_node.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2026 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # # install_node.sh diff --git a/tool/gh_actions/run_tests.sh b/tool/gh_actions/run_tests.sh index 160352cd2..eb0400458 100755 --- a/tool/gh_actions/run_tests.sh +++ b/tool/gh_actions/run_tests.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2022-2023 Intel Corporation +# Copyright (C) 2022-2026 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # # run_tests.sh @@ -11,8 +11,17 @@ set -euo pipefail +# Run main package tests (auto-discovers test/) dart test -# run tests in JS (increase heap size also) +# Run tests in sub-packages +for pkg in packages/*/; do + if [ -d "${pkg}test" ]; then + echo "Running tests in ${pkg}..." + (cd "$pkg" && dart test) + fi +done + +# Run main package tests in JS (increase heap size for large synthesis tests) export NODE_OPTIONS="--max-old-space-size=8192" dart test --platform node \ No newline at end of file diff --git a/tool/gh_codespaces/run_setup.sh b/tool/gh_codespaces/run_setup.sh index 6523e4147..30db822e5 100755 --- a/tool/gh_codespaces/run_setup.sh +++ b/tool/gh_codespaces/run_setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2026 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # # run_setup.sh