Repository: intel/rohd (dart)

Issue:
Provide API to access the "driver" of `Module` `input` and `inOut` ports
### Motivation

When `addInput`, `addInputArray`, `addInOut`, or `addInOutArray` is created, a source to drive the input is required in the API call.  This is fundamental to the way ROHD is able to identify hierarchy and connectivity between Modules (https://intel.github.io/rohd-website/docs/modules/).

Determining the "original" driver of a call to `addInput` is trivial, since you can just call `srcConnection`.  However, for the other three APIs listed, it is not trivial.  An array must use `srcConnections` (since `srcConnection` is null for `LogicStructure`s), and then trace up the stack of `parentStructure` to find the original driver.  For the `inOut` APIs, it's even more complicated (and maybe impossible, depending on connectivity generated afterwards) to determine who the "original" driver was programmatically since inOut wires can have multiple drivers (note that the `build()` analysis already has to do some extra book-keeping on inOuts to properly determine hierarchy).

Knowing the original driver of an input/inOut to a Module is useful for many reasons, some of which are:
- Creating an input/inout port and connecting it to a driver later
- Finding the driver of an input/inout of a sub-module and using it to fan out drivers to other sub-modules
- Automation that benefits from identifying the version of an input/inOut "outside of" the module that owns the port
- Determining easily whether an input is left floating in a programmatic way



### Desired solution

Add an API like `inputDriver` and `inOutDriver` (name subject to change) that provides the "original driver", the external version, of a port with the same name as `input` or `inOut`.

### Alternatives considered

Leave it up to users to figure out and automate per their needs

### Additional details

_No response_

Pull request:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

Adds new APIs to `Module` to access the original "driver" or "source" when an input or inOut port was created.

See #501 for more information on motivation, etc.

Also, removed the `@protected` on various APIs related to accessing inputs and inOuts on Modules since (a) it wasn't really protecting much during hardware generation since modules could access it without lint warning, and (b) because the proper use and connectivity of the APIs is already dependent on user understanding of in/out rules for module building.
 
## Related Issue(s)

Fix #501

## Testing

Add tests for new APIs + existing tests to make sure nothing broke

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible? If yes, how so?

Variable name changes from `x` to `source` on port creation APIs may trigger warnings on overrides of those functions, but not breaking.

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

No

Patch:
diff --git a/lib/src/diagnostics/inspector_service.dart b/lib/src/diagnostics/inspector_service.dart
index 90afc13ed..28a9f60dc 100644
--- a/lib/src/diagnostics/inspector_service.dart
+++ b/lib/src/diagnostics/inspector_service.dart
@@ -34,7 +34,6 @@ extension _ModuleDevToolUtils on Module {
   Map<String, dynamic> toJson({bool skipCustomModules = true}) {
     final json = {
       'name': name,
-      // ignore: invalid_use_of_protected_member
       'inputs': inputs.map((key, value) => MapEntry(key, value.toMap())),
       'outputs': outputs.map((key, value) => MapEntry(key, value.toMap())),
     };
diff --git a/lib/src/interfaces/interface.dart b/lib/src/interfaces/interface.dart
index 4e37bf8c6..4a439a7e7 100644
--- a/lib/src/interfaces/interface.dart
+++ b/lib/src/interfaces/interface.dart
@@ -85,7 +85,6 @@ class Interface<TagType> {
       for (final port in getPorts(inputTags).values) {
         port <=
             (port is LogicArray
-                // ignore: invalid_use_of_protected_member
                 ? module.addInputArray(
                     uniquify(port.name),
                     srcInterface.port(port.name),
@@ -93,7 +92,6 @@ class Interface<TagType> {
                     elementWidth: port.elementWidth,
                     numUnpackedDimensions: port.numUnpackedDimensions,
                   )
-                // ignore: invalid_use_of_protected_member
                 : module.addInput(
                     uniquify(port.name),
                     srcInterface.port(port.name),
@@ -105,14 +103,12 @@ class Interface<TagType> {
     if (outputTags != null) {
       for (final port in getPorts(outputTags).values) {
         final output = (port is LogicArray
-            // ignore: invalid_use_of_protected_member
             ? module.addOutputArray(
                 uniquify(port.name),
                 dimensions: port.dimensions,
                 elementWidth: port.elementWidth,
                 numUnpackedDimensions: port.numUnpackedDimensions,
               )
-            // ignore: invalid_use_of_protected_member
             : module.addOutput(
                 uniquify(port.name),
                 width: port.width,
@@ -136,7 +132,6 @@ class Interface<TagType> {
 
         port <=
             (port is LogicArray
-                // ignore: invalid_use_of_protected_member
                 ? module.addInOutArray(
                     uniquify(port.name),
                     srcInterface.port(port.name),
@@ -144,7 +139,6 @@ class Interface<TagType> {
                     elementWidth: port.elementWidth,
                     numUnpackedDimensions: port.numUnpackedDimensions,
                   )
-                // ignore: invalid_use_of_protected_member
                 : module.addInOut(
                     uniquify(port.name),
                     srcInterface.port(port.name),
diff --git a/lib/src/module.dart b/lib/src/module.dart
index a60b77a0b..27ce248fd 100644
--- a/lib/src/module.dart
+++ b/lib/src/module.dart
@@ -40,14 +40,20 @@ abstract class Module {
   final TraverseableCollection<Logic> _internalSignals =
       TraverseableCollection();
 
-  /// An internal list of inputs to this [Module].
-  final Map<String, Logic> _inputs = {};
+  /// An internal mapping of inputs to this [Module].
+  late final Map<String, Logic> _inputs = {};
 
-  /// An internal list of outputs to this [Module].
-  final Map<String, Logic> _outputs = {};
+  /// An internal mapping of outputs to this [Module].
+  late final Map<String, Logic> _outputs = {};
 
-  /// An internal list of inouts to this [Module].
-  final Map<String, Logic> _inOuts = {};
+  /// An internal mapping of inOuts to this [Module].
+  late final Map<String, Logic> _inOuts = {};
+
+  /// An internal mapping of input names to their sources to this [Module].
+  late final Map<String, Logic> _inputSources = {};
+
+  /// An internal mapping of inOut names to their sources to this [Module].
+  late final Map<String, Logic> _inOutSources = {};
 
   /// The parent [Module] of this [Module].
   ///
@@ -60,7 +66,10 @@ abstract class Module {
 
   /// A map from [input] port names to this [Module] to corresponding [Logic]
   /// signals.
-  @protected
+  ///
+  /// Note that [inputs] should only be used to drive hardware *within* a
+  /// [Module]. To access the signal that drives these inputs, use
+  /// [inputSource].
   Map<String, Logic> get inputs => UnmodifiableMapView<String, Logic>(_inputs);
 
   /// A map from [output] port names to this [Module] to corresponding [Logic]
@@ -70,7 +79,10 @@ abstract class Module {
 
   /// A map from [inOut] port names to this [Module] to corresponding [Logic]
   /// signals.
-  @protected
+  ///
+  /// Note that [inOuts] should only be used for hardware *within* a [Module].
+  /// To access the signal that drives/received these inOuts from outside of
+  /// this [Module], use [inOutSource].
   Map<String, Logic> get inOuts => UnmodifiableMapView<String, Logic>(_inOuts);
 
   /// An [Iterable] of all [Module]s contained within this [Module].
@@ -101,16 +113,21 @@ abstract class Module {
   /// named [name].
   ///
   /// Only logic within this [Module] should consume this signal.
-  @protected
   Logic input(String name) => _inputs.containsKey(name)
       ? _inputs[name]!
       : throw PortDoesNotExistException(
           'Input name "$name" not found as an input to this Module.');
 
+  /// The original `source` provided to the creation of the [input] port [name]
+  /// via [addInput] or [addInputArray].
+  Logic inputSource(String name) =>
+      _inputSources[name] ??
+      (throw PortDoesNotExistException(
+          '$name is not an input of this Module.'));
+
   /// Provides the [input] named [name] if it exists, otherwise `null`.
   ///
   /// Only logic within this [Module] should consume this signal.
-  @protected
   Logic? tryInput(String name) => _inputs[name];
 
   /// Accesses the [Logic] associated with this [Module]s output port
@@ -130,14 +147,19 @@ abstract class Module {
   /// named [name].
   ///
   /// Only logic within this [Module] should consume this signal.
-  @protected
   Logic inOut(String name) => _inOuts.containsKey(name)
       ? _inOuts[name]!
       : throw PortDoesNotExistException(
           'InOut name "$name" not found as an in/out of this Module.');
 
+  /// The original `source` provided to the creation of the [inOut] port [name]
+  /// via [addInOut] or [addInOutArray].
+  Logic inOutSource(String name) =>
+      _inOutSources[name] ??
+      (throw PortDoesNotExistException(
+          '$name is not an inOut of this Module.'));
+
   /// Provides the [inOut] named [name] if it exists, otherwise `null`.
-  @protected
   Logic? tryInOut(String name) => _inOuts[name];
 
   /// Returns true iff [signal] is the same [Logic] as the [input] port of this
@@ -602,26 +624,29 @@ abstract class Module {
   /// Registers a signal as an input to this [Module] and returns an input port
   /// that can be consumed.
   ///
-  /// The return value is the same as what is returned by [input].
-  @protected
-  Logic addInput(String name, Logic x, {int width = 1}) {
+  /// The return value is the same as what is returned by [input] and should
+  /// only be used within this [Module]. The provided [source] is accessible via
+  /// [inputSource].
+  Logic addInput(String name, Logic source, {int width = 1}) {
     _checkForSafePortName(name);
-    if (x.width != width) {
-      throw PortWidthMismatchException(x, width);
+    if (source.width != width) {
+      throw PortWidthMismatchException(source, width);
     }
 
-    if (x is LogicStructure) {
+    if (source is LogicStructure) {
       // ignore: parameter_assignments
-      x = x.packed;
+      source = source.packed;
     }
 
     final inPort = Logic(name: name, width: width, naming: Naming.reserved)
-      ..gets(x)
+      ..gets(source)
       // ignore: invalid_use_of_protected_member
       ..parentModule = this;
 
     _inputs[name] = inPort;
 
+    _inputSources[name] = source;
+
     return inPort;
   }
 
@@ -634,24 +659,27 @@ abstract class Module {
   /// Registers a signal as an inOut to this [Module] and returns an inOut port
   /// that can be consumed.
   ///
-  /// The return value is the same as what is returned by [inOut].
-  @protected
-  LogicNet addInOut(String name, Logic x, {int width = 1}) {
+  /// The return value is the same as what is returned by [inOut] and should
+  /// only be used within this [Module]. The provided [source] is accessible via
+  /// [inOutSource].
+  LogicNet addInOut(String name, Logic source, {int width = 1}) {
     _checkForSafePortName(name);
-    if (x.width != width) {
-      throw PortWidthMismatchException(x, width);
+    if (source.width != width) {
+      throw PortWidthMismatchException(source, width);
     }
 
-    _inOutDrivers.add(x);
+    _inOutDrivers.add(source);
 
     final inOutPort =
         LogicNet(name: name, width: width, naming: Naming.reserved)
           // ignore: invalid_use_of_protected_member
           ..parentModule = this
-          ..gets(x);
+          ..gets(source);
 
     _inOuts[name] = inOutPort;
 
+    _inOutSources[name] = source;
+
     return inOutPort;
   }
 
@@ -661,12 +689,11 @@ abstract class Module {
   ///
   /// This is very similar to [addInput], except for [LogicArray]s.
   ///
-  /// Performs validation on overall width matching for [x], but not on
+  /// Performs validation on overall width matching for [source], but not on
   /// [dimensions], [elementWidth], or [numUnpackedDimensions].
-  @protected
   LogicArray addInputArray(
     String name,
-    Logic x, {
+    Logic source, {
     List<int> dimensions = const [1],
     int elementWidth = 1,
     int numUnpackedDimensions = 0,
@@ -680,12 +707,14 @@ abstract class Module {
       numUnpackedDimensions: numUnpackedDimensions,
       naming: Naming.reserved,
     )
-      ..gets(x)
+      ..gets(source)
       // ignore: invalid_use_of_protected_member
       ..setAllParentModule(this);
 
     _inputs[name] = inArr;
 
+    _inputSources[name] = source;
+
     return inArr;
   }
 
@@ -693,7 +722,6 @@ abstract class Module {
   /// can be driven.
   ///
   /// The return value is the same as what is returned by [output].
-  @protected
   Logic addOutput(String name, {int width = 1}) {
     _checkForSafePortName(name);
 
@@ -711,7 +739,6 @@ abstract class Module {
   /// named [name].
   ///
   /// This is very similar to [addOutput], except for [LogicArray]s.
-  @protected
   LogicArray addOutputArray(
     String name, {
     List<int> dimensions = const [1],
@@ -741,12 +768,11 @@ abstract class Module {
   ///
   /// This is very similar to [addInOut], except for [LogicArray]s.
   ///
-  /// Performs validation on overall width matching for [x], but not on
+  /// Performs validation on overall width matching for [source], but not on
   /// [dimensions], [elementWidth], or [numUnpackedDimensions].
-  @protected
   LogicArray addInOutArray(
     String name,
-    Logic x, {
+    Logic source, {
     List<int> dimensions = const [1],
     int elementWidth = 1,
     int numUnpackedDimensions = 0,
@@ -754,7 +780,7 @@ abstract class Module {
     _checkForSafePortName(name);
 
     // make sure we register all the _inOutDrivers properly
-    final xElems = [x];
+    final xElems = [source];
     for (var i = 0; i < xElems.length; i++) {
       final xi = xElems[i];
       _inOutDrivers.add(xi);
@@ -770,12 +796,14 @@ abstract class Module {
       numUnpackedDimensions: numUnpackedDimensions,
       naming: Naming.reserved,
     )
-      ..gets(x)
+      ..gets(source)
       // ignore: invalid_use_of_protected_member
       ..setAllParentModule(this);
 
     _inOuts[name] = inOutArr;
 
+    _inOutSources[name] = source;
+
     return inOutArr;
   }
 
diff --git a/lib/src/signals/logic_structure.dart b/lib/src/signals/logic_structure.dart
index 6fac9d61e..873c7dab1 100644
--- a/lib/src/signals/logic_structure.dart
+++ b/lib/src/signals/logic_structure.dart
@@ -262,6 +262,9 @@ class LogicStructure implements Logic {
 
   /// Performs a recursive call of setting [parentModule] on all of [elements]
   /// and their [elements] for any sub-[LogicStructure]s.
+  ///
+  /// This should *only* be called by [Module.build].  It is used to optimize
+  /// search.
   @protected
   void setAllParentModule(Module? newParentModule) {
     parentModule = newParentModule;
diff --git a/lib/src/synthesizers/systemverilog.dart b/lib/src/synthesizers/systemverilog.dart
index 7395b5520..cf38297fd 100644
--- a/lib/src/synthesizers/systemverilog.dart
+++ b/lib/src/synthesizers/systemverilog.dart
@@ -74,7 +74,6 @@ class SystemVerilogSynthesizer extends Synthesizer {
           instanceType,
           instanceName,
           Map.fromEntries(ports.entries
-              // ignore: invalid_use_of_protected_member
               .where((element) => module.inputs.containsKey(element.key))),
           Map.fromEntries(ports.entries
               .where((element) => module.outputs.containsKey(element.key))),
@@ -85,7 +84,6 @@ class SystemVerilogSynthesizer extends Synthesizer {
     //non-custom needs more details
     final connections = <String>[];
 
-    // ignore: invalid_use_of_protected_member
     for (final signalName in module.inputs.keys) {
       connections.add('.$signalName(${ports[signalName]!})');
     }
@@ -94,7 +92,6 @@ class SystemVerilogSynthesizer extends Synthesizer {
       connections.add('.$signalName(${ports[signalName]!})');
     }
 
-    // ignore: invalid_use_of_protected_member
     for (final signalName in module.inOuts.keys) {
       connections.add('.$signalName(${ports[signalName]!})');
     }
@@ -548,7 +545,6 @@ class _SynthSubModuleInstantiation {
   /// Adds an input mapping from [name] to [synthLogic].
   void setInputMapping(String name, _SynthLogic synthLogic,
       {bool replace = false}) {
-    // ignore: invalid_use_of_protected_member
     assert(module.inputs.containsKey(name),
         'Input $name not found in module ${module.name}.');
     assert(
@@ -584,7 +580,6 @@ class _SynthSubModuleInstantiation {
 
   void setInOutMapping(String name, _SynthLogic synthLogic,
       {bool replace = false}) {
-    // ignore: invalid_use_of_protected_member
     assert(module.inOuts.containsKey(name),
         'InOut $name not found in module ${module.name}.');
     assert(
@@ -815,17 +810,14 @@ class _SynthModuleDefinition {
   _SynthModuleDefinition(this.module)
       : _synthInstantiationNameUniquifier = Uniquifier(
           reservedNames: {
-            // ignore: invalid_use_of_protected_member
             ...module.inputs.keys,
             ...module.outputs.keys,
-            // ignore: invalid_use_of_protected_member
             ...module.inOuts.keys,
           },
         ) {
     // start by traversing output signals
     final logicsToTraverse = TraverseableCollection<Logic>()
       ..addAll(module.outputs.values)
-      // ignore: invalid_use_of_protected_member
       ..addAll(module.inOuts.values);
 
     for (final output in module.outputs.values) {
@@ -833,13 +825,11 @@ class _SynthModuleDefinition {
     }
 
     // make sure disconnected inputs are included
-    // ignore: invalid_use_of_protected_member
     for (final input in module.inputs.values) {
       inputs.add(_getSynthLogic(input)!);
     }
 
     // make sure disconnected inouts are included, also
-    // ignore: invalid_use_of_protected_member
     for (final inOut in module.inOuts.values) {
       inOuts.add(_getSynthLogic(inOut)!);
     }
@@ -855,10 +845,8 @@ class _SynthModuleDefinition {
     for (final subModule in module.subModules) {
       _getSynthSubModuleInstantiation(subModule);
       logicsToTraverse
-        // ignore: invalid_use_of_protected_member
         ..addAll(subModule.inputs.values)
         ..addAll(subModule.outputs.values)
-        // ignore: invalid_use_of_protected_member
         ..addAll(subModule.inOuts.values);
     }
 
@@ -942,7 +930,6 @@ class _SynthModuleDefinition {
               .setInOutMapping(receiver.name, synthReceiver);
         }
 
-        // ignore: invalid_use_of_protected_member
         logicsToTraverse.addAll(subModule.inOuts.values);
       }
 
@@ -958,9 +945,7 @@ class _SynthModuleDefinition {
         }
 
         logicsToTraverse
-          // ignore: invalid_use_of_protected_member
           ..addAll(subModule.inputs.values)
-          // ignore: invalid_use_of_protected_member
           ..addAll(subModule.inOuts.values);
       } else if (driver != null) {
         if (!module.isInput(receiver) && !module.isInOut(receiver)) {
@@ -1044,7 +1029,6 @@ class _SynthModuleDefinition {
   void _assignSubmodulePortMapping() {
     for (final submoduleInstantiation
         in moduleToSubModuleInstantiationMap.values) {
-      // ignore: invalid_use_of_protected_member
       for (final inputName in submoduleInstantiation.module.inputs.keys) {
         final orig = submoduleInstantiation.inputMapping[inputName]!;
         submoduleInstantiation.setInputMapping(
@@ -1059,7 +1043,6 @@ class _SynthModuleDefinition {
             replace: true);
       }
 
-      // ignore: invalid_use_of_protected_member
       for (final inOutName in submoduleInstantiation.module.inOuts.keys) {
         final orig = submoduleInstantiation.inOutMapping[inOutName]!;
         submoduleInstantiation.setInOutMapping(
diff --git a/lib/src/utilities/simcompare.dart b/lib/src/utilities/simcompare.dart
index 1b7a847a1..4f56099da 100644
--- a/lib/src/utilities/simcompare.dart
+++ b/lib/src/utilities/simcompare.dart
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2023 Intel Corporation
+// Copyright (C) 2021-2024 Intel Corporation
 // SPDX-License-Identifier: BSD-3-Clause
 //
 // simcompare.dart
@@ -77,7 +77,6 @@ class Vector {
   /// Converts this vector into a SystemVerilog check.
   String toTbVerilog(Module module) {
     final assignments = inputValues.keys.map((signalName) {
-      // ignore: invalid_use_of_protected_member
       final signal = module.tryInOut(signalName) ?? module.input(signalName);
 
       if (signal is LogicArray) {
@@ -102,7 +101,6 @@ class Vector {
     for (final expectedOutput in expectedOutputValues.entries) {
       final outputName = expectedOutput.key;
       final outputPort =
-          // ignore: invalid_use_of_protected_member
           module.tryInOut(outputName) ?? module.output(outputName);
       final expected = expectedOutput.value;
       final expectedValue = LogicValue.of(
@@ -152,7 +150,6 @@ abstract class SimCompare {
       Simulator.registerAction(timestamp, () {
         for (final signalName in vector.inputValues.keys) {
           final value = vector.inputValues[signalName];
-          // ignore: invalid_use_of_protected_member
           (module.tryInput(signalName) ?? module.inOut(signalName)).put(value);
         }
 
@@ -161,7 +158,6 @@ abstract class SimCompare {
             for (final signalName in vector.expectedOutputValues.keys) {
               final value = vector.expectedOutputValues[signalName];
               final o =
-                  // ignore: invalid_use_of_protected_member
                   module.tryOutput(signalName) ?? module.inOut(signalName);
 
               final errorReason =
@@ -307,7 +303,6 @@ abstract class SimCompare {
     final logicToWireMapping = Map.fromEntries(vectors
         .map((v) => v.inputValues.keys)
         .flattened
-        // ignore: invalid_use_of_protected_member
         .where((name) => module.tryInOut(name) != null)
         .map((name) => MapEntry(name, toTbWireName(name))));
 


Test patch:
diff --git a/test/interface_test.dart b/test/interface_test.dart
index 31a8401dc..4302b9cf6 100644
--- a/test/interface_test.dart
+++ b/test/interface_test.dart
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2023 Intel Corporation
+// Copyright (C) 2021-2024 Intel Corporation
 // SPDX-License-Identifier: BSD-3-Clause
 //
 // interface_test.dart
@@ -7,9 +7,6 @@
 // 2021 November 30
 // Author: Max Korbel <max.korbel@intel.com>
 
-// ignore_for_file: avoid_multiple_declarations_per_line
-// ignore_for_file: invalid_use_of_protected_member
-
 import 'package:rohd/rohd.dart';
 import 'package:test/test.dart';
 
diff --git a/test/module_test.dart b/test/module_test.dart
index 368ab2bf2..c39e9ea7f 100644
--- a/test/module_test.dart
+++ b/test/module_test.dart
@@ -108,34 +108,62 @@ class SubModWithArray extends Module {
 }
 
 void main() {
-  test('tryInput, exists', () {
-    final mod = ModuleWithMaybePorts(addIn: true);
-    expect(mod.i, isNotNull);
+  group('try ports', () {
+    test('tryInput, exists', () {
+      final mod = ModuleWithMaybePorts(addIn: true);
+      expect(mod.i, isNotNull);
+    });
+
+    test('tryInput, doesnt exist', () {
+      final mod = ModuleWithMaybePorts();
+      expect(mod.i, null);
+    });
+
+    test('tryOutput, exists', () {
+      final mod = ModuleWithMaybePorts(addOut: true);
+      expect(mod.o, isNotNull);
+    });
+
+    test('tryOutput, doesnt exist', () {
+      final mod = ModuleWithMaybePorts();
+      expect(mod.o, null);
+    });
+
+    test('tryInOut, exists', () {
+      final mod = ModuleWithMaybePorts(addIo: true);
+      expect(mod.io, isNotNull);
+    });
+
+    test('tryInOut, doesnt exist', () {
+      final mod = ModuleWithMaybePorts();
+      expect(mod.io, null);
+    });
   });
 
-  test('tryInput, doesnt exist', () {
-    final mod = ModuleWithMaybePorts();
-    expect(mod.i, null);
-  });
-
-  test('tryOutput, exists', () {
-    final mod = ModuleWithMaybePorts(addOut: true);
-    expect(mod.o, isNotNull);
-  });
-
-  test('tryOutput, doesnt exist', () {
-    final mod = ModuleWithMaybePorts();
-    expect(mod.o, null);
-  });
-
-  test('tryInOut, exists', () {
-    final mod = ModuleWithMaybePorts(addIo: true);
-    expect(mod.io, isNotNull);
-  });
-
-  test('tryInOut, doesnt exist', () {
-    final mod = ModuleWithMaybePorts();
-    expect(mod.io, null);
+  group('port sources', () {
+    test('input port source', () {
+      final src = Logic();
+      final mod = FlexibleModule()..addInput('a', src);
+      expect(mod.inputSource('a'), src);
+    });
+
+    test('inout port source', () {
+      final src = LogicNet();
+      final mod = FlexibleModule()..addInOut('a', src);
+      expect(mod.inOutSource('a'), src);
+    });
+
+    test('input array port source', () {
+      final src = LogicArray([1], 1);
+      final mod = FlexibleModule()..addInputArray('a', src);
+      expect(mod.inputSource('a'), src);
+    });
+
+    test('inout array port source', () {
+      final src = LogicArray([1], 1);
+      final mod = FlexibleModule()..addInOutArray('a', src);
+      expect(mod.inOutSource('a'), src);
+    });
   });
 
   test('self-containing hierarchy', () async {