Building a Twin
Writing behaviour into a component
vsiBuild generates a complete, runnable component. Behaviour goes into marked user regions
and nowhere else, because the next generate -overwrite rewrites everything outside them.
The regions are the same five in C++ and Python, and the glue scripts in this workspace
write into them by name, so a person can too.
The five regions and what runs when
| region | where it sits | use it for |
|---|---|---|
| Global Variables and Definitions | file scope, after the includes or imports | state that outlives a step: counters, a trace writer, helper classes |
| Constructor | end of the component's constructor | one-time set-up that needs the object |
| Before sending the packet | inside the per-step method, right before the generated send calls | compute this step's OUTPUT values into mySignals |
| After sending the packet | same method, after the sends and the generated print block | read this step's INPUT values from mySignals, log, trace, advance your own step counter |
| Protocol's callback function | end of the receive handler | react at arrival time; note the generated unpack into mySignals has already happened when this runs |
The per-step method is called once per simulation step by the fabric. A value another component sent during step N is unpacked by your callback when it arrives and is first visible in your "Before sending" region at step N plus 1. Reactions therefore land one step after their cause. Do not return early from the per-step method: the end-of-step handshake with the main thread sits after the "After" region, and skipping it deadlocks the component.
The signal struct
Every signal declared for the component is a member of mySignals under the name from
define componentSignals. Outputs you assign before the send are packed and sent by the
generated code; inputs are unpacked into it by the generated receive code. For a CAN port
the generated code packs each signal into its frame slot (8 bits per signal in the
generated twins here) and sends the frame every step whether or not you changed anything.
A C++ example: a counter that echoes a received value
Global region:
static long g_step = 0;
static int g_last_seen = -1;
Before sending the packet (an output signal named ctl_command):
mySignals.ctl_command = (int)(g_step % 256);
After sending the packet (an input signal named ctl_status):
if (mySignals.ctl_status != g_last_seen) {
g_last_seen = mySignals.ctl_status;
std::printf("step %ld status %d\n", g_step, g_last_seen);
}
g_step++;
The generated code prints every signal each step to the component's console log
(vsi.sim\_logs\check.COMPONENT.log), so a first version needs no printing at all.
The Python equivalent
Global region (module level, before the class):
class Glue:
def __init__(self):
self.step = 0
self.last_seen = -1
g_glue = Glue()
Before sending the packet:
self.mySignals.ctl_command = g_glue.step % 256
After sending the packet:
if self.mySignals.ctl_status != g_glue.last_seen:
g_glue.last_seen = self.mySignals.ctl_status
print("step", g_glue.step, "status", g_glue.last_seen)
g_glue.step += 1
Keep the state in one module-level object rather than module-level integers: a global
statement after the same name has already been read in that method is a syntax error, and
both regions share one method. Indentation inside the two step regions is two tabs, as the
markers show; the module region is unindented.
A scenario instead of counters
innexis-vsi\msp2_glue\msp2_stim.h shows behaviour worth copying: a scenario with named
steps read from environment variables (MSP2_INSERT_AT, MSP2_FAULT_AT, and so on), small
state machines (card discretes, a heartbeat that stalls, a watchdog, a time lock), a CSV
trace per component, and a host-side test program that proves the scenario with two
negative controls before the twin is even built. apply_glue.py there writes the calls into
the seven skeletons by region name. Its run on Candidate A matched the scenario exactly.
Time inside a component
convert.timeInNs() in C++ and vsiCommonPythonApi.getSimulationTimeInNs() in Python give
the fabric's current time. A step is 100 microseconds in the twins here (set simulationStep 100000), total time 10 ms (set totalSimTime 10000000); change both in the command file,
or at run time with set step and config at the control prompt.
Vendor note on custom code (VENDOR, chapter 6)
The Builder and Simulator Guide's Custom Code Enablement chapter describes the same regions and adds that a component can be replaced wholesale by a user program as long as it uses the gateway APIs of chapter 5. Nothing in this workspace has needed that; the regions were enough for counters, scenarios and traces.
Source: generated skeletons under D:\wfpy\WildfirePyDT\src and innexis-vsi\workspace_A\Msp2DT_A\src; innexis-vsi/msp2_glue (EXERCISED 2026-09-06 to 2026-09-08); InnexisVsiBuilderSimulatorGuide.pdf chapter 6 (VENDOR) · retrieved Tue Sep 08 2026 00:00:00 GMT+0000 (Coordinated Universal Time)