Type de bloc : dataStoreMemory
| Paramètre | Description |
|---|---|
| ports d entree | Aucun port d entree (ce bloc n en a aucun). |
| Paramètre | Description |
|---|---|
| ports de sortie | Aucun port de sortie (ce bloc n en a aucun). |
Declare une memoire scalaire nommee partagee dans tout le modele (valeur initiale).
| Module | nflow_blocks |
| Bibliotheque | Utilitaires |
| Type | dataStoreMemory |
| Libelle | Data Store Memory |
Description
Declare une memoire scalaire nommee (DataStoreName) avec une InitialValue, sans aucun fil. La memoire est ecrite par des blocs dataStoreWrite et lue par des blocs dataStoreRead referencant le meme nom, permettant une communication a l'echelle du modele sans lignes de routage. Le magasin est une map par thread re-initialisee a chaque run par l'INIT de ce bloc.
Natif seulement ; scalaire. Les lectures voient l'ecriture du pas precedent (latence d'un pas, comme un retard unitaire).
Ports
Ce bloc n'a aucun port d'entree.
Ce bloc n'a aucun port de sortie.
Parametres
| Parametre | Valeur par defaut |
|---|---|
DataStoreName |
A |
InitialValue |
0 |
Caracteristiques du bloc
| Type de bloc | dataStoreMemory |
| Famille | Utilitaires |
| Taille rendue | 70 x 60 |
| Phases | INIT |
| Etat interne ou historique | non |
| Type de donnees du signal | valeurs numeriques double |
Algorithmes
Capacites etendues
Execution native seulement (ce bloc n'est pas genere en code).
Sources d implementation
Generation de code : prise en charge pour C et Rust.
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/dataStore.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
//=============================================================================
// Data Store Memory / Read / Write: a named scalar memory shared between blocks
// of a model without a wire (Data Store Memory declares it with an initial
// value; Data Store Write updates it; Data Store Read outputs it). The store is
// a per-thread map keyed by DataStoreName, so it persists across steps within a
// run and is re-seeded each run by the Memory block's INIT. Read observes the
// value as of the previous step's Write (Read runs in OUTPUT, Write in UPDATE),
// i.e. one-step latency, matching a unit delay. Scalar only; native (the
// cross-block store is outside the code-generation model).
//=============================================================================
#include "SimEngineTypes.hpp"
#include "BlockRegistry.hpp"
#include "FieldNames.hpp"
#include "NFlowBlockDescriptor.hpp"
#include "NFlowCodegenLang.hpp"
#include <cctype>
#include <string>
#include <unordered_map>
#include "routing_blocks.hpp"
//=============================================================================
namespace Nelson {
namespace NFlow {
//=============================================================================
// Per-thread named store. A sim run is synchronous on one thread, so this
// persists across that run's steps; concurrent runs on other threads get
// their own map. Each entry is (re)seeded by the owning Memory block's INIT.
static std::unordered_map<std::string, double>&
dataStoreMap()
{
static thread_local std::unordered_map<std::string, double> store;
return store;
}
//=============================================================================
static std::string
dataStoreName(const Block& b, const SimCtx& ctx)
{
nflow::BlockDescriptor bd(b, ctx.variables);
return bd.paramStr("DataStoreName", "A");
}
//=============================================================================
bool
handleDataStoreMemory(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase != Phase::INIT) {
return false;
}
nflow::BlockDescriptor bd(b, ctx.variables);
const std::string name = bd.paramStr("DataStoreName", "A");
dataStoreMap()[name] = bd.paramDouble("InitialValue", 0.0);
return false;
}
//=============================================================================
bool
handleDataStoreWrite(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase == Phase::INIT) {
// Seed the entry if no Memory block declared it, so a lone
// Write/Read pair still starts from a defined value.
const std::string name = dataStoreName(b, ctx);
dataStoreMap().emplace(name, 0.0);
return false;
}
// The write lands in the topologically ordered phase, not at the end
// of the step: a read placed after it then sees the value written this
// step, which is what an execution order means.
if (phase != Phase::ALGEBRAIC) {
return false;
}
if (!hasInput(ctx, b.nid, 0)) {
return false;
}
dataStoreMap()[dataStoreName(b, ctx)] = getInput(ctx, b.nid, 0, 0.0);
return false;
}
//=============================================================================
bool
handleDataStoreRead(SimCtx& ctx, const Block& b, Phase phase)
{
if (phase != Phase::ALGEBRAIC) {
return false;
}
const std::string name = dataStoreName(b, ctx);
const auto& store = dataStoreMap();
auto it = store.find(name);
setOutput(ctx, b.nid, (it != store.end()) ? it->second : 0.0);
return false;
}
//=============================================================================
// ---- Code generation ----
// The named store becomes ONE shared ModelState field ds_<name> per
// DataStoreName. Ordering mirrors the simulator's one-step latency
// (Read in OUTPUT, Write in UPDATE) without synthetic edges: a Read has
// no wired input, so the topological sort always emits it in the first
// wave, before any Write (which needs its producer first). The field is
// declared once (any of the three blocks triggers it, seeded 0.0 like a
// lone Write/Read pair); a Memory block appends its InitialValue init
// AFTER the seed, so it wins whatever the block order.
//=============================================================================
static std::string
dataStoreField(const BlockCodegenStateArgs& a)
{
nflow::BlockDescriptor bd(*a.block, *a.variables);
const std::string name = bd.paramStr("DataStoreName", "A");
std::string field = "ds_";
for (char c : name) {
field += (std::isalnum(static_cast<unsigned char>(c)) != 0) ? c : '_';
}
return field;
}
//=============================================================================
static std::string
dataStoreFieldStep(const BlockCodegenArgs& a)
{
nflow::BlockDescriptor bd(*a.block, *a.variables);
const std::string name = bd.paramStr("DataStoreName", "A");
std::string field = "ds_";
for (char c : name) {
field += (std::isalnum(static_cast<unsigned char>(c)) != 0) ? c : '_';
}
return field;
}
//=============================================================================
static void
ensureDataStoreField(const BlockCodegenStateArgs& a, CodegenLang L)
{
const std::string field = dataStoreField(a);
if (!a.once("dsdecl:" + field)) {
return;
}
if (L.rust) {
a.declState(" pub " + field + ": f64,");
a.addInit(" s." + field + " = 0.0;");
} else {
a.declState("double " + field + ";");
a.addInit(" s->" + field + " = 0.0;");
}
}
//=============================================================================
static BlockCodegenTemplate
makeDataStoreMemoryCodegen(CodegenLang L)
{
BlockCodegenTemplate t;
t.emitState = [L](const BlockCodegenStateArgs& a) {
ensureDataStoreField(a, L);
nflow::BlockDescriptor bd(*a.block, *a.variables);
const std::string field = dataStoreField(a);
const std::string init = a.fmt(bd.paramDouble("InitialValue", 0.0));
if (L.rust) {
a.addInit(" s." + field + " = " + init + ";");
} else {
a.addInit(" s->" + field + " = " + init + ";");
}
};
t.emitStep = [L](const BlockCodegenArgs& a) {
a.line("out_" + a.id + " = " + L.sref(dataStoreFieldStep(a)) + ";");
};
return t;
}
//=============================================================================
static BlockCodegenTemplate
makeDataStoreWriteCodegen(CodegenLang L)
{
BlockCodegenTemplate t;
t.emitState = [L](const BlockCodegenStateArgs& a) { ensureDataStoreField(a, L); };
t.emitStep = [L](const BlockCodegenArgs& a) {
a.line("out_" + a.id + " = " + a.in[0] + ";");
a.line(L.sref(dataStoreFieldStep(a)) + " = out_" + a.id + ";");
};
return t;
}
//=============================================================================
static BlockCodegenTemplate
makeDataStoreReadCodegen(CodegenLang L)
{
BlockCodegenTemplate t;
t.emitState = [L](const BlockCodegenStateArgs& a) { ensureDataStoreField(a, L); };
t.emitStep = [L](const BlockCodegenArgs& a) {
a.line("out_" + a.id + " = " + L.sref(dataStoreFieldStep(a)) + ";");
};
return t;
}
//=============================================================================
BlockCodegenTemplate
getCodeGenCDataStoreMemory()
{
return makeDataStoreMemoryCodegen({ false });
}
BlockCodegenTemplate
getCodeGenRustDataStoreMemory()
{
return makeDataStoreMemoryCodegen({ true });
}
BlockCodegenTemplate
getCodeGenCDataStoreWrite()
{
return makeDataStoreWriteCodegen({ false });
}
BlockCodegenTemplate
getCodeGenRustDataStoreWrite()
{
return makeDataStoreWriteCodegen({ true });
}
BlockCodegenTemplate
getCodeGenCDataStoreRead()
{
return makeDataStoreReadCodegen({ false });
}
BlockCodegenTemplate
getCodeGenRustDataStoreRead()
{
return makeDataStoreReadCodegen({ true });
}
//=============================================================================
} // namespace NFlow
} // namespace Nelson
//=============================================================================
d.blocks={ struct('id','mem','type','dataStoreMemory','inputs',0,'outputs',0,'params',struct('DataStoreName','M','InitialValue',0)), struct('id','r','type','ramp','inputs',0,'outputs',1,'params',struct('slope',1)), struct('id','wr','type','dataStoreWrite','inputs',1,'outputs',0,'params',struct('DataStoreName','M')), struct('id','rd','type','dataStoreRead','inputs',0,'outputs',1,'params',struct('DataStoreName','M')), struct('id','w','type','toWorkspace','inputs',1,'outputs',0,'params',struct('VariableName','y','SaveFormat','Array')) };
d.connections={ struct('from','r','to','wr','fromIndex',0,'toIndex',0), struct('from','rd','to','w','fromIndex',0,'toIndex',0) };
d.sampleTime=0.1; d.duration=1.0; d.solver='discrete'; d.variables=struct();
r=jsondecode(__nflow_simulate__(jsonencode(d)));
| Version | Description |
|---|---|
| 1.0.0 | initial version |