Block type: busCreator
| Parameter | Description |
|---|---|
| input ports | 2 input port(s) declared. |
| Parameter | Description |
|---|---|
| output ports | 1 output port(s) declared. |
Groups heterogeneous signals (or nested buses) into one bus.
| Module |
nflow_blocks
|
| Library | Utility blocks |
| Type |
busCreator
|
| Label | Bus Creator |
Description
Packs its input signals into one bus signal. Member names come from the MemberNames parameter (default signalN); an input that is itself a bus becomes a nested member.
A named BusType (declared in the model-level busTypes array) validates the member layout; NonVirtual marks the bus for struct emission at the generated-code interface. Members keep their full descriptor: numeric type (including exact int64/uint64), complexity and N-D shape.
Bus members are extracted by path with the busSelector block; a bus wired to any other block is a compile-time error.
This page describes the native runtime behavior observed in the module C++ sources. Declared phases indicate when the simulation engine calls the block.
Code generation: supported for C and Rust.
Implementation Sources
modules/nflow_blocks/libraries/utility/library.json{
"id": "builtin.utility",
"title": "Utility",
"version": "1.0.0",
"format": "nflow-2",
"metadata": {
"author": "Allan CORNET",
"created": "2026-03-21",
"tool": "Nelson nflow"
},
"comment": "Utility blocks such as switches, comments, and subsystems",
"license": "LGPL-3.0",
"builtin": true,
"blocks": [
{
"type": "comment",
"label": "Comment",
"icon": "comment.svg",
"phases": [],
"width": 220,
"height": 120,
"inputs": [],
"outputs": [],
"defaultParams": {
"CommentText": "",
"ShowBorder": true
},
"render": {
"type": "comment",
"bodyClass": "block-body"
}
},
{
"type": "switch",
"label": "Switch",
"icon": "switch.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 80,
"inputs": [
{
"x": 0,
"y": 0,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
},
{
"x": 0,
"y": 80,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"Criteria": "ge",
"Threshold": 0
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "switch-math"
}
},
{
"type": "multiportSwitch",
"label": "Multiport Switch",
"icon": "multiportSwitch.svg",
"phases": [
"ALGEBRAIC"
],
"width": 40,
"height": 80,
"inputs": [
{
"x": 20,
"y": 0,
"side": "top"
},
{
"x": 0,
"y": 20,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
},
{
"x": 0,
"y": 60,
"side": "left"
}
],
"outputs": [
{
"x": 40,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"DataPortCount": 3
},
"render": {
"type": "image",
"src": "exports/multiportSwitch.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 40,
"height": 90
}
},
{
"type": "toggleSwitch",
"label": "Toggle Switch",
"icon": "toggleSwitch.svg",
"phases": [
"OUTPUT"
],
"width": 80,
"height": 50,
"inputs": [],
"outputs": [
{
"x": 80,
"y": 25,
"side": "right"
}
],
"defaultParams": {
"State": 0,
"OnLabel": "ON",
"OffLabel": "OFF",
"OnValue": 1,
"OffValue": 0
},
"render": {
"type": "toggle"
}
},
{
"type": "subsystem",
"icon": "subsystem.svg",
"label": "Subsystem",
"phases": [
"INIT",
"OUTPUT",
"ALGEBRAIC",
"UPDATE"
],
"width": 120,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 120,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"name": "Subsystem",
"externalInputs": [],
"externalOutputs": [],
"subsystem": null
},
"render": {
"type": "math",
"bodyClass": "block-body",
"mathGroupClass": "subsystem-math",
"formula": "\\mathsf{Sub}"
}
},
{
"type": "mux",
"label": "Mux",
"icon": "mux.svg",
"phases": [
"OUTPUT"
],
"width": 8,
"height": 40,
"inputs": [
{
"x": 0,
"y": 10,
"side": "left"
},
{
"x": 0,
"y": 30,
"side": "left"
}
],
"outputs": [
{
"x": 8,
"y": 20,
"side": "right"
}
],
"defaultParams": {
"Inputs": 2
}
},
{
"type": "demux",
"label": "Demux",
"icon": "demux.svg",
"phases": [
"OUTPUT"
],
"width": 8,
"height": 40,
"inputs": [
{
"x": 0,
"y": 20,
"side": "left"
}
],
"outputs": [
{
"x": 8,
"y": 10,
"side": "right"
},
{
"x": 8,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"Outputs": 2
}
},
{
"type": "convert",
"label": "Convert",
"icon": "convert.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 25,
"side": "right"
}
],
"defaultParams": {
"OutDataType": "double",
"SaturateOnOverflow": true,
"Rounding": "nearest"
}
},
{
"type": "initialCondition",
"label": "IC",
"icon": "initialCondition.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 25,
"side": "right"
}
],
"defaultParams": {
"InitialValue": 0
}
},
{
"type": "dataStoreMemory",
"label": "Data Store Memory",
"icon": "dataStoreMemory.svg",
"phases": [
"INIT"
],
"width": 70,
"height": 60,
"inputs": [],
"outputs": [],
"defaultParams": {
"DataStoreName": "A",
"InitialValue": 0
},
"render": {
"type": "image",
"src": "exports/dataStoreMemory.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 70,
"height": 60
}
},
{
"type": "dataStoreWrite",
"label": "Data Store Write",
"icon": "dataStoreWrite.svg",
"phases": [
"INIT",
"UPDATE"
],
"width": 70,
"height": 60,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
}
],
"outputs": [],
"defaultParams": {
"DataStoreName": "A"
},
"render": {
"type": "image",
"src": "exports/dataStoreWrite.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 70,
"height": 60
}
},
{
"type": "dataStoreRead",
"label": "Data Store Read",
"icon": "dataStoreRead.svg",
"phases": [
"OUTPUT"
],
"width": 70,
"height": 60,
"inputs": [],
"outputs": [
{
"x": 70,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"DataStoreName": "A"
},
"render": {
"type": "image",
"src": "exports/dataStoreRead.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 70,
"height": 60
}
},
{
"type": "selector",
"label": "Selector",
"icon": "selector.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 25,
"side": "right"
}
],
"defaultParams": {
"Indices": "1"
}
},
{
"type": "reshape",
"label": "Reshape",
"icon": "reshape.svg",
"phases": [
"INIT",
"ALGEBRAIC"
],
"width": 80,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 25,
"side": "right"
}
],
"defaultParams": {
"OutputDimensions": ""
}
},
{
"type": "concatenate",
"label": "Concatenate",
"icon": "concatenate.svg",
"phases": [
"ALGEBRAIC"
],
"width": 60,
"height": 60,
"inputs": [
{
"x": 0,
"y": 20,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 60,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"ConcatenateDimension": 1
}
},
{
"type": "busCreator",
"label": "Bus Creator",
"icon": "busCreator.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 70,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
},
{
"x": 0,
"y": 45,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 35,
"side": "right"
}
],
"defaultParams": {
"BusType": "",
"NonVirtual": false,
"MemberNames": []
}
},
{
"type": "busSelector",
"label": "Bus Selector",
"icon": "busSelector.svg",
"phases": [
"ALGEBRAIC"
],
"width": 85,
"height": 70,
"inputs": [
{
"x": 0,
"y": 35,
"side": "left"
}
],
"outputs": [
{
"x": 85,
"y": 25,
"side": "right"
},
{
"x": 85,
"y": 45,
"side": "right"
}
],
"defaultParams": {
"SelectedSignals": [],
"OutputAsBus": false
}
},
{
"type": "merge",
"label": "Merge",
"icon": "merge.svg",
"phases": [
"INIT",
"ALGEBRAIC"
],
"width": 40,
"height": 80,
"inputs": [
{
"x": 0,
"y": 30,
"side": "left"
},
{
"x": 0,
"y": 50,
"side": "left"
}
],
"outputs": [
{
"x": 40,
"y": 40,
"side": "right"
}
],
"defaultParams": {
"InitialOutput": 0
},
"render": {
"type": "image",
"src": "exports/merge.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 40,
"height": 80
}
},
{
"type": "functionCallGenerator",
"label": "Function-Call Generator",
"icon": "functionCallGenerator.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 60,
"inputs": [],
"outputs": [
{
"x": 90,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"NumberOfIterations": 1
},
"render": {
"type": "image",
"src": "exports/functionCallGenerator.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 90,
"height": 60
}
},
{
"type": "functionCallSplit",
"label": "Function-Call Split",
"icon": "functionCallSplit.svg",
"phases": [],
"width": 60,
"height": 80,
"inputs": [
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 60,
"y": 30,
"side": "right"
},
{
"x": 60,
"y": 50,
"side": "right"
}
],
"defaultParams": {},
"render": {
"type": "image",
"src": "exports/functionCallSplit.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 60,
"height": 80
}
},
{
"type": "iteratorNumber",
"label": "Iterator Number",
"icon": "iteratorNumber.svg",
"phases": [
"OUTPUT"
],
"width": 70,
"height": 50,
"inputs": [],
"outputs": [
{
"x": 70,
"y": 25,
"side": "right"
}
],
"defaultParams": {},
"render": {
"type": "image",
"src": "exports/iteratorNumber.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 70,
"height": 50
}
},
{
"type": "iteratorCondition",
"label": "Iterator Condition",
"icon": "iteratorCondition.svg",
"phases": [
"ALGEBRAIC"
],
"width": 70,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 70,
"y": 25,
"side": "right"
}
],
"defaultParams": {},
"render": {
"type": "image",
"src": "exports/iteratorCondition.svg",
"svgMode": "element",
"preserveAspectRatio": "none",
"x": 0,
"y": 0,
"width": 70,
"height": 50
}
},
{
"type": "width",
"label": "Width",
"icon": "width.svg",
"phases": [
"ALGEBRAIC"
],
"width": 80,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 80,
"y": 25,
"side": "right"
}
],
"defaultParams": {}
},
{
"type": "signalConversion",
"label": "Signal Conversion",
"icon": "signalConversion.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 50,
"inputs": [
{
"x": 0,
"y": 25,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 25,
"side": "right"
}
],
"defaultParams": {}
},
{
"type": "assignment",
"label": "Assignment",
"icon": "assignment.svg",
"phases": [
"ALGEBRAIC"
],
"width": 90,
"height": 60,
"inputs": [
{
"x": 0,
"y": 20,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 90,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"Indices": [
1
]
}
},
{
"type": "busAssignment",
"label": "Bus Assignment",
"icon": "busAssignment.svg",
"phases": [
"ALGEBRAIC"
],
"width": 100,
"height": 60,
"inputs": [
{
"x": 0,
"y": 20,
"side": "left"
},
{
"x": 0,
"y": 40,
"side": "left"
}
],
"outputs": [
{
"x": 100,
"y": 30,
"side": "right"
}
],
"defaultParams": {
"AssignedSignals": []
}
}
]
}
modules/nflow_blocks/src/cpp/routing/busCreator.cpp//=============================================================================
// Copyright (c) 2016-present Allan CORNET (Nelson)
//=============================================================================
// This file is part of Nelson.
//=============================================================================
// LICENCE_BLOCK_BEGIN
// SPDX-License-Identifier: LGPL-3.0-or-later
// LICENCE_BLOCK_END
//=============================================================================
// busCreator: group input signals (or nested buses) into one bus. The output
// port owns a packed contiguous region; member p starts at the sum of the
// widths of members 0..p-1 (the registry layout mirrors the port order), so
// the runtime copy needs no registry lookup. All storage lanes are
// preserved.
//=============================================================================
#include "routing_blocks.hpp"
#include "SimEngineBusTypes.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include <algorithm>
//=============================================================================
bool
Nelson::NFlow::handleBusCreator(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase != Phase::ALGEBRAIC) {
return false;
}
const int nIn = numInputs(ctx, b.nid);
const int wOut = outputWidth(ctx, b.nid, 0);
double* y = outputSlice(ctx, b.nid, 0);
double* yim = outputSliceImag(ctx, b.nid, 0);
long long* yi = outputSliceI64(ctx, b.nid, 0);
bool changed = false;
int at = 0;
for (int p = 0; p < nIn && at < wOut; ++p) {
SigView v = getInputSig(ctx, b.nid, p);
const int w = std::max(1, v.width);
for (int k = 0; k < w && at < wOut; ++k, ++at) {
const double val = sigAt(v, k);
if (y[at] != val) {
y[at] = val;
changed = true;
}
if (yim) {
const double iv
= (v.imag && v.width > 0) ? ((v.width == 1) ? v.imag[0] : v.imag[k]) : 0.0;
if (yim[at] != iv) {
yim[at] = iv;
changed = true;
}
}
if (yi) {
const long long ev
= v.idata ? ((v.width == 1) ? v.idata[0] : v.idata[k]) : (long long)val;
if (yi[at] != ev) {
yi[at] = ev;
changed = true;
}
}
}
}
return changed;
}
//=============================================================================
// Dimension/type rule: build (or reuse) the bus type from the input
// descriptors. Deduplicated by structural equality so the fixed-point loop
// converges to a stable id.
bool
Nelson::NFlow::resolveBusCreatorDims(const Block& b, const ValMap& vars,
const std::vector<PortSig>& inSigs, std::vector<PortSig>& outSigs, BusRegistry& busReg,
std::string& err)
{
nflow::BlockDescriptor bd(b, vars);
const int nIn = std::max(1, b.inputs);
// Member names: MemberNames[p] or "signal<p+1>".
std::vector<std::string> names;
if (b.params.contains(nflow::kMemberNames) && b.params[nflow::kMemberNames].is_array()) {
for (const auto& n : b.params[nflow::kMemberNames]) {
if (n.is_string()) {
names.push_back(n.get<std::string>());
}
}
}
BusType t;
t.nonvirtual = false;
if (b.params.contains(nflow::kNonVirtual) && b.params[nflow::kNonVirtual].is_boolean()) {
t.nonvirtual = b.params[nflow::kNonVirtual].get<bool>();
}
for (int p = 0; p < nIn; ++p) {
BusMember m;
m.name = (p < (int)names.size() && !names[(size_t)p].empty())
? names[(size_t)p]
: ("signal" + std::to_string(p + 1));
if (p < (int)inSigs.size()) {
m.sig = inSigs[(size_t)p];
m.sig.offset = -1;
}
t.members.push_back(std::move(m));
}
// Named bus type: validate structural agreement with the declaration.
const std::string declared = bd.paramStr(nflow::kBusType, "");
if (!declared.empty()) {
const int want = busReg.idOf(declared);
if (want < 0) {
err = "unknown bus type '" + declared + "'";
return false;
}
// Adopt the declared layout (it is the contract); the runtime packs
// members in port order, so member count must match.
if ((int)busReg.types[(size_t)want].members.size() != nIn) {
err = "bus type '" + declared + "' declares "
+ std::to_string(busReg.types[(size_t)want].members.size())
+ " members but the block has " + std::to_string(nIn) + " inputs";
return false;
}
outSigs[0].busTypeId = want;
outSigs[0].setVector(std::max(1, busReg.packedWidth(want)));
outSigs[0].type = SigType::Double;
outSigs[0].isComplex = false;
return true;
}
// Anonymous (inferred) bus: reuse a structurally equal registered type
// so the fixed-point iteration converges to a stable id.
int id = -1;
const int probe = busReg.add(t); // candidate at the end
for (int k = 0; k < probe; ++k) {
if (busReg.structurallyEqual(k, probe)) {
id = k;
break;
}
}
if (id >= 0) {
busReg.types.pop_back(); // discard the duplicate candidate
} else {
id = probe;
}
outSigs[0].busTypeId = id;
outSigs[0].setVector(std::max(1, busReg.packedWidth(id)));
outSigs[0].type = SigType::Double;
outSigs[0].isComplex = false;
return true;
}
//=============================================================================
| Version | Description |
|---|---|
| 1.0.0 | initial version |