This is a great question! This highlights the importance of data management when integrating large architecture models.
Long story short:
Limit duplicate sources of truth. Interfaces reused in multiple places should only be defined by the sender and then referenced by the receiver
Here are some recommended solutions:
- Avoid the use of storage classes on the root level ports of the child models. Simulink will generate the inter-model variables from the parent model and handle passing those variables to children automatically. Only use storage classes on the root level port of the top application model.
- Leave the storage class in the dictionary as Auto and then set child A's outport to have a storage class of ExportedGlobal (or ExportToFile) and child B's inport to ImportedExtern (or ImportFromFile). The first child model A will own the definition, the second child model B will consume it.
- Set the child A and B storage class to ImportedExtern (or ImportFromFile) and then define the variables in a hand written header/source file.
We recommend the first option as this the easiest and most maintainable. If this is only being used to make the child model I/O to be observable in the generated code then a TestPoint would be better suited.
Long story long:
The example I've created here is kept simple for illustration purposes but is meant to provide better scaleability and limit duplicate sources of truth in the data model.
Data Model Example and Recommended Rules
Let's start with the following architecture model of a transmitter connected to a receiver:
We must first establish some rules for how we intend to manage data to establish modular, shareable, Single Sources of Truth, and also minimize dictionary clutter and collisions in large systems (This applies to the data model of both the Simulink models and the Architecture models):
- Internal (unexposed) parameters and interfaces should be stored in an internal data dictionary.
- External (cross the model/architecture boundary) interfaces should be stored in an external data dictionary.
- The component that generates the data should be the only one responsible for defining it (that is, external data dictionaries should only define system outputs while inputs are driven by references to other dictionaries.
- Interface definition should flow down whenever possible (the system integrator should own the system boundaries).
This results in the following data model:
Some notes on this:
- It isn't practical to assume that the transmitter development will be done before the receiver. In this case, the receiver model would create a stub external inputs dictionary which would be replaced with the real one at the time of system integration. Ideally, the system integrator would define and own all of the boundaries of the systems to be integrated and flow those external dictionaries out to the functional teams.
- A circular dictionary reference is possible. If the receiver sends a command signal to the transmitter, for example, then the TX external dictionary and the RX external dictionary will reference each other. This is allowable in dictionary referencing and is expected in that case.
- Overlap between external interface names is not allowed. That is, two different components can not output different interfaces with the same name in an integrated architecture. This is a design consideration. It can be worked around but best practice would be to maintain the "one interface, one name" paradigm and if necessary, create a shared dictionary for commonly used interfaces that all dictionaries can reference.
- In the special case where the parent architecture also outputs the same interfaces as a child architecture, the shared outputs should be moved to a separate dictionary to avoid unnecessary name collision. Both the receiver and the integrated architecture in this example output "Pressure." If the receiver also had ten other outputs that did not cross the parent boundary then they would just clutter the integrated external dictionary so shared interfaces should be split into a separate dictionary. This will prevent either external dictionary from having parameters that do not cross one boundary or the other. Per rule 4 above, the system integrator should own the shared dictionary.
Now that we've established those rules, we can set up our dictionary structure based on our system boundaries and then begin implementation.
Transmitter Simulink Behavior
Starting with the transmitter, there is one output, raw voltage.
Internal parameters, in this case VoltageGenerator's Amplitude and Bias, would be stored as Simulink Parameters inside the internal data dictionary.
There is one outport in this transmitter, RawVoltage. A Value Type will be created in the external dictionary named VoltageRaw to define the interface information for this port - data type, dimensions, min, max, units, etc. Since the transmitter internal dictionary inherits the transmitter external dictionary, the VoltageRaw Value Type is visible to the model.
Receiver Simulink Behavior
Next, we'll look at the receiver.
The receiver has an input of RawVoltage which needs to be assigned a data type of VoltageRaw so this drives the need for the receiver's external data dictionary to inherit the transmitter's external dictionary. The receiver's internal dictionary then inherits its external dictionary and is linked to the model. Parameters PressureGain and PressureOffset are defined in the internal data dictionary. Finally, a Value Type is created in the external dictionary to define the interface of the Pressure port.
The Simulink behavioral models can now be linked to components in the architecture model and the external dictionaries of each can be referenced by the architecture model's internal dictionary.
Now that we've resolved the issue of collisions, we can transition back to the implementation models and talk about code generation. Open your Simulink model as a top model (outside of System Composer) and launch embedded coder from the apps tray. Next, open the code mappings pane (1)(2), navigate to the outports tab(3) and set the storage class to ExportToFile(4).
You can repeat this same process for the other model (setting the inport RawVoltage as ImportFromFile and outport Pressure as ExportToFile).
I hope this helps. Please feel free to reach out with any other integration questions!