Repository: intel/rohd (dart)

Issue:
`SynthBuilder` should check modules are already built
### Describe the bug

It's possible to generate outputs using a `Synthesizer` and `SynthBuilder` rather than through `Module.generateSynth`.  However, the `SynthBuilder` does not check that `Module.build` is called, only `generateSynth` does.  This can produce unexpected outputs (e.g. SystemVerilog with no submodules) without any helpful error message indicating why.

### To Reproduce

Use `SynthBuilder` to generate output verilog without calling `build` on the `Module`.

### Expected behavior

`ModuleNotBuiltException` is thrown

### Actual behavior

Silent incorrect results

### Additional: Dart SDK info

_No response_

### Additional: pubspec.yaml

_No response_

### Additional: Context

_No response_

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

It's possible to generate outputs using a Synthesizer and SynthBuilder rather than through Module.generateSynth. However, the SynthBuilder does not check that Module.build is called, only generateSynth does. This can produce unexpected outputs (e.g. SystemVerilog with no submodules) without any helpful error message indicating why.

## Related Issue(s)

Fix #246 

## Testing

Tested with module not built and catch exception. 

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible? If yes, how so?

Yes. 

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

No. Just added validation and test.

Patch:
diff --git a/lib/src/synthesizers/synth_builder.dart b/lib/src/synthesizers/synth_builder.dart
index 57f581735..ef953f5d1 100644
--- a/lib/src/synthesizers/synth_builder.dart
+++ b/lib/src/synthesizers/synth_builder.dart
@@ -10,6 +10,7 @@
 
 import 'package:collection/collection.dart';
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/module/module_not_built_exception.dart';
 import 'package:rohd/src/utilities/uniquifier.dart';
 
 /// A generic class which can convert a module into a generated output using
@@ -38,6 +39,10 @@ class SynthBuilder {
   /// Constructs a [SynthBuilder] based on the [top] module and
   /// using [synthesizer] for generating outputs.
   SynthBuilder(this.top, this.synthesizer) {
+    if (!top.hasBuilt) {
+      throw ModuleNotBuiltException();
+    }
+
     final modulesToParse = <Module>[top];
     for (var i = 0; i < modulesToParse.length; i++) {
       final moduleI = modulesToParse[i];


Test patch:
diff --git a/test/synth_builder_test.dart b/test/synth_builder_test.dart
new file mode 100644
index 000000000..860fe15ad
--- /dev/null
+++ b/test/synth_builder_test.dart
@@ -0,0 +1,74 @@
+/// Copyright (C) 2021-2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// synth_builder_test.dart
+/// Unit tests for generation of the system verilog using synth builder.
+///
+/// 2023 April 10
+/// Author: Yao Jing Quek <yao.jing.quek@intel.com>
+///
+import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/module/module_not_built_exception.dart';
+import 'package:test/test.dart';
+
+class TopModule extends Module {
+  TopModule(Logic a, Logic b) : super(name: 'topmodule') {
+    a = addInput('a', a, width: a.width);
+    b = addInput('b', b, width: b.width);
+    final y = addOutput('y', width: a.width);
+    final z = addOutput('z', width: b.width);
+    final z2 = addOutput('z2', width: b.width);
+
+    y <= AModule(a).y;
+    z <= BModule(b).zz;
+    z2 <= BModule(b).zz;
+  }
+}
+
+class AModule extends Module {
+  Logic get y => output('y');
+
+  AModule(Logic a) : super(name: 'amodule') {
+    a = addInput('a', a, width: a.width);
+    final y = addOutput('y', width: a.width);
+
+    final tmp = Logic(width: a.width);
+    y <= tmp;
+    tmp <= a;
+  }
+}
+
+class BModule extends Module {
+  Logic get zz => output('zz');
+  BModule(Logic bb) : super(name: 'bmodule') {
+    bb = addInput('bb', bb, width: bb.width);
+    final zz = addOutput('zz', width: bb.width);
+
+    zz <= ~bb;
+  }
+}
+
+void main() {
+  tearDown(() async {
+    await Simulator.reset();
+  });
+
+  group('synth builder', () {
+    test('should throw exception if module is not built', () async {
+      final mod = TopModule(Logic(width: 4), Logic());
+      expect(() async {
+        SynthBuilder(mod, SystemVerilogSynthesizer());
+      }, throwsA((dynamic e) => e is ModuleNotBuiltException));
+    });
+
+    test('should able to create submodule in system verilog rtl', () async {
+      final mod = TopModule(Logic(width: 4), Logic());
+      await mod.build();
+
+      for (final submod in mod.subModules) {
+        final synth = SynthBuilder(submod, SystemVerilogSynthesizer());
+        expect(synth.getFileContents()[0], contains(submod.definitionName));
+      }
+    });
+  });
+}