API — ICoreBlocks/ICoreCodegen
The public contract of 18 header(s) under src/ICoreBlocks/ICoreCodegen — 12 class/struct definition(s), 175 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreCodeExportTarget.h#
src/ICoreBlocks/ICoreCodegen/ICoreCodeExportTarget.h
Opt-in reproducible verification stimulus. Unset (the default), each verification run draws a fresh random pulse train; set, the same seed reproduces the same stimulus, so a failing combination can be re-run and debugged on the numbers that failed.
ICoreCodeExportTarget#
ICoreCodeExportTarget.h:8 · class · pImpl · 29 declaration(s)
class ICoreCodeExportTarget {
public:
explicit ICoreCodeExportTarget();
void setTargetName(const std::string &newTargetName);
void setCodeType(const std::string& newCodeType);
void setSourceSubsystemPath(const std::string& newSourceSubsystemPath);
void setTargetPath(const std::string& newExportTargetPath);
void setVerificationLevel(const std::string& newVerificationLevel);
void setResidualRelativeTolerancePercent(double newResidualRelativeTolerancePercent);
void setTestingPulsesWidth(double newTestingPulsesWidth);
void setTestingAmplitudeMin(double newTestingAmplitudeMin);
void setTestingAmplitudeMax(double newTestingAmplitudeMax);
void setCompilerScanEnabled(bool enabled);
void setCustomCompilerPath(const std::string& path);
// Opt-in reproducible verification stimulus. Unset (the default), each
// verification run draws a fresh random pulse train; set, the same seed
// reproduces the same stimulus, so a failing combination can be re-run
// and debugged on the numbers that failed.
void setTestingSeed(unsigned int seed);
void clearTestingSeed();
std::string getTargetName() const;
std::string getCodeExportType() const;
std::string getSourceSubsystemPath() const;
std::filesystem::path getTargetExportPath() const;
std::string getVerificationLevel() const;
double getResidualRelativeTolerancePercent() const;
double getTestingPulsesWidth() const;
double getTestingAmplitudeMin() const;
double getTestingAmplitudeMax() const;
bool getCompilerScanEnabled() const;
std::string getCustomCompilerPath() const;
bool hasTestingSeed() const;
unsigned int getTestingSeed() const;
// Why this target's LANGUAGE cannot carry one of the signal types inside its
// source subsystem, as "<port path>: <reason>", or "" when it can carry them
// all.
//
// A refusal by name, never a silent wrong answer: HDL has no variable-length
// string, Java has no unsigned 64-bit primitive, the Structured Text exporter
// declares no types so it cannot declare a bus. The export dialog shows this,
// fireTarget stops on it, and export verification records the cell as
// notApplicable with this text rather than as a FAIL -- the same shape as
// ICoreParityRigLibrary::hdlIncomparableReason.
//
// Computed, not stored: it depends on the model, which changes under the
// target. Answers without building the model, so a UI may call it freely.
std::string typeUnsupportedReason() const;
~ICoreCodeExportTarget();
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreCodeWatermark.h#
src/ICoreBlocks/ICoreCodegen/ICoreCodeWatermark.h
ICoreCodeWatermark#
ICoreCodeWatermark.h:39 · class · 12 declaration(s)
ICoreCodeWatermark — the two comment lines a watermarking licence adds to every generated file (A4.2 on the account board).
class ICoreCodeWatermark {
public:
ICoreCodeWatermark() = delete;
// How the target language opens a comment line. The banner the watermark
// joins is already open, so these are LINE prefixes inside it, never the
// opening or closing delimiter.
enum class Style {
CBlockStar, // " * " inside a /* ... */ banner (C, C++, Java, Rust)
DoubleSlash, // "// " Verilog, SystemVerilog
DoubleDash, // "-- " VHDL
StructuredText, // " " inside a (* ... *) banner (IEC 61131-3 ST)
Hash, // "# " shell-style
Percent, // "% " MATLAB
PlainIndent // " " inside a Python \"\"\" docstring
};
// Whether the generated file states its commercial-use entitlement, and
// which way (A4.5 on the account board).
//
// ⚠ READ, NEVER INFERRED. `NotStated` is not "probably fine" and not
// "probably not": it means no licence was resolved in this process, so
// nothing is said at all. Guessing either way would put a sentence about
// somebody's licence into a file they ship, on no evidence — and today
// every export in this tree runs with no licence bound, so a default of
// `NotAllowed` would stamp "commercial use not included" on every file
// this tree has ever generated.
//
// ⚠ AND IT IS NEVER DERIVED FROM THE TIER. The entitlement rests on the
// attestation signed at purchase, held server-side, and arrives in the
// `lic.commercial_use` claim. A tier name does not determine it — that is
// the same mistake ICoreLicenseState.h warns about for features, and it
// passes its author's own test for the same reason.
enum class CommercialUse {
NotStated, // no licence resolved — say nothing
Allowed,
NotAllowed
};
// The licence's answer, read through icore::License. False in any process
// that has not bound a licence gate.
[[nodiscard]] static bool isRequired();
// icore::License's answer, or NotStated when nothing is resolved.
[[nodiscard]] static CommercialUse commercialUse();
// The prefix for one style, exactly as it appears at the start of a line.
[[nodiscard]] static std::string prefix(Style style);
// The unprefixed text, one entry per line, no trailing newline. This is
// the string the trap guards above are asserted against.
[[nodiscard]] static std::vector<std::string> textLines();
// The commercial-use sentence for one answer, unprefixed. Empty for
// NotStated. Held to exactly the same trap table as textLines().
[[nodiscard]] static std::string commercialUseLine(CommercialUse use);
// The block to splice into a banner: the commercial-use line first when
// there is one, then the watermark when it is required, every line
// prefixed for `style` and newline-terminated. "" when neither applies,
// which is the ordinary case in a process with no licence bound. PURE — a
// test drives every combination with no licence anywhere.
[[nodiscard]] static std::string render(Style style, bool required, CommercialUse use);
// The two halves separately, for a test that needs to see which is which.
[[nodiscard]] static std::string renderWatermark(Style style, bool required);
[[nodiscard]] static std::string renderCommercialUse(Style style, CommercialUse use);
// render(style, isRequired(), commercialUse()). What an exporter calls, and
// the ONLY thing an exporter calls — the twenty splice sites do not change
// when a line is added here, and the source census that proves every
// exporter still stamps its banners counts calls to this one. (Its path is
// on the guards page rather than here: this header is rendered into a
// PUBLIC api page, and a published page may not cite the build machinery.)
[[nodiscard]] static std::string block(Style style);
// Every sequence that would close a comment early in some language this
// tree emits. Public so the suite asserts against this list rather than
// against a second copy of it.
[[nodiscard]] static std::vector<std::string> forbiddenSequences();
// 0 is a pass; a line is appended per failure.
static int selfTest(std::vector<std::string>* failures);
};
};
ICoreCodegen.h#
src/ICoreBlocks/ICoreCodegen/ICoreCodegen.h
ICoreCodeEngine -- the code-export coordinator.
THIS CLASS IS Qt-FREE, AND THAT IS A PROPERTY TO PRESERVE, NOT A COINCIDENCE. It names no Qt type, includes no Qt header and includes no ICoreStudio header (DEVELOPER_GUIDELINES.md Rule 2). It coordinates parsing, export and verification, all of which is model + filesystem work that has no reason to know a GUI exists.
The three things it genuinely cannot do alone -- run a callback on the application's main thread, ask the user a question, and put a notification on screen -- arrive as plain std::function HOST SERVICES, installed once at start-up by ICoreCodeEngineHost (ICoreStudio/Panels/CodeExportPanels/). That is the whole seam: everything Qt-shaped sits on the far side of it.
ICoreSignalBitSize#
ICoreCodegen.h:40 · struct · 0 declaration(s)
struct ICoreSignalBitSize {
public:
int intBits; // integer bits including sign
int fracBits; // fractional bits
};
};
ICoreCodeEngine#
ICoreCodegen.h:44 · class · nested SignalColumn, BusRecordElement, BusRecord, DataStore, ExecutionRun, InstanceName, InstanceScope, ReusableFunction, SignalQuantize · 70 declaration(s)
class ICoreCodeEngine {
public:
// ---- Structs ----
// ====================[Host Services]======================
// Installed once, from the main thread, before any export runs. Each is
// optional: the fallback in brackets is what an installer-less host gets.
// Runs `callback` on the application's main thread and returns immediately.
// [fallback: the callback runs inline, on the calling thread]
//
// Installing this also records the calling thread as the main thread, which
// is what lets askContinue-style prompts know whether they must marshal.
using MainThreadInvoker = std::function<void(std::function<void()> callback)>;
static void setMainThreadInvoker(MainThreadInvoker invoker);
// Asks the user a yes/no question and blocks until they answer.
// [fallback: answers NO and logs it -- a headless run must not hang waiting
// for a person, and "no" is the safe answer for every question the engine
// asks, all of which are "carry on despite a failure?"]
using QuestionHandler = std::function<bool(const std::string& title,
const std::string& text)>;
static void setQuestionHandler(QuestionHandler handler);
enum class NotificationKind { Friendly, Warning };
// Shows a notification to the user. [fallback: ICoreLogger]
using NotificationSink = std::function<void(NotificationKind kind,
const std::string& title,
const std::string& message)>;
static void setNotificationSink(NotificationSink sink);
// The handler and sink installed now, so a caller that has to answer the
// engine's questions itself for one run -- a script's target.fire(), which
// must never raise a dialog nobody is looking at (the agent-bridge board,
// AB.23) -- can put the host's own back afterwards. Empty when none is set.
static QuestionHandler questionHandler();
static NotificationSink notificationSink();
// ====================[Listeners]======================
// Single-listener, not lists: each has exactly one consumer today, and the
// infrastructure rule is to add API when a call site needs it.
// Fired when the SET of export targets changes -- created, deleted, cleared.
// A UI listening to this rebuilds its list from getAllExportTargets(), which
// destroys and recreates widgets, so it is not safe to fire from inside a
// handler owned by one of them.
using TargetsChangedListener = std::function<void()>;
static void setTargetsChangedListener(TargetsChangedListener listener);
static void notifyTargetsChanged();
// Coalesce that notification across a BULK edit, because the listener above
// rebuilds its whole widget list on every fire and firing per item is
// quadratic. Measured: loading a project with 20 targets called the panel
// rebuild 20 times and constructed 1+2+...+20 = 210 sections to end up
// showing 20 (W8.1, section 123 of the Windows backend plan).
//
// Nestable, and hold() does NOT swallow the notification -- it records that
// one was asked for, and the OUTERMOST release fires it exactly once. A bulk
// edit that changed nothing still fires nothing.
//
// Main thread only, like every other call in this block: the target vector
// has never been anything else.
static void holdTargetsChanged();
static void releaseTargetsChanged();
// Fired when a background job claims (true) or releases (false) the parser
// thread, on the thread that claimed it -- the main thread in practice.
using JobStateListener = std::function<void(bool jobRunning)>;
static void setJobStateListener(JobStateListener listener);
// ====================[Code Types]======================
static std::vector<std::string> getAvailableCodeTypes();
// The icon for a code type is presentation and lives in ICoreStudio --
// see ICoreCodeTypeIcons::forCodeType().
// ====================[Export Targets]======================
static ICoreCodeExportTarget* createNewCodeExportTarget();
static void deleteCodeExportTarget(const ICoreCodeExportTarget* targetToDelete);
static std::vector<ICoreCodeExportTarget *> getAllExportTargets();
// Drops every target. Used when a project is closed or replaced: targets are
// project data (they name a source subsystem inside THIS diagram), so
// carrying them into the next project would point them at subsystems that
// no longer exist. Refuses while a job owns the parser thread, like the
// create/delete calls above, and returns false when it does.
static bool clearAllExportTargets();
static void generateSimpleTestingTargetsSet();
// ====================[Fire Targets]======================
static bool fireAllTargets();
static bool fireTarget(const ICoreCodeExportTarget *targetToFire);
// ====================[Export Verification]======================
// The engine's single verifier. Everything the verifier computes is static; the
// instance exists only to own the verification window, which is why it is created
// on first request rather than at start-up -- a console run never builds a widget
// it will not show. Call it from the main thread only: the first call constructs
// the verifier's UI.
static ICoreCodeExportVerifier* getCodeExportVerifier();
// ====================[Parser Worker Thread]======================
// The engine owns ONE background thread, and every code parser / export / verification
// run happens on it -- callers never spawn their own. Because that work shares the
// global model + codegen state, a single thread is what makes it safe: jobs are
// serialised by construction instead of by luck.
//
// It is a plain std::thread draining a FIFO queue, NOT a Qt event loop: that is what
// took QThread, QObject, QMetaObject and QPointer out of this class. `work` runs on
// that thread; `onFinished` is handed to the MainThreadInvoker once `work` returns.
//
// `requesterLifetime` replaces the QPointer the Qt version used. The caller keeps a
// std::shared_ptr<void> member alive for as long as it can receive the callback, and
// passes it here; if that owner is destroyed mid-job the completion callback is
// dropped instead of reaching freed memory, and the job slot is released so the
// parser does not stay "busy" forever. Pass an empty weak_ptr for a caller that
// outlives every job.
static void postToParserThread(std::weak_ptr<void> requesterLifetime,
std::function<void()> work,
std::function<void()> onFinished);
static bool isOnParserThread();
static void shutdownParserThread(); // called once at application exit
// ====================[Background-job guard]======================
// Only one job may own the parser thread at a time. tryBeginJob() atomically claims
// the slot; tryBeginParserJob() is the UI-facing form that also pops the
// "Parser is busy!" notification when the claim fails.
static bool tryBeginJob(); // false if a job is already running
static bool tryBeginParserJob(const std::string& requestedAction);
static void notifyParserBusy(const std::string& requestedAction);
static void endJob();
static bool isJobRunning();
// Cooperative cancellation: requestCancel() asks the running activity to stop. Chains
// (verify-all / deploy) stop before the next target; a single job aborts at the next
// step boundary and discards its result. A call already inside an external tool runs
// to completion. clearCancel() is called when a fresh user action starts.
static void requestCancel();
static bool isCancelRequested();
static void clearCancel();
// ====================[Signal Name Generator (Linker)]======================
static void resetSignalNameSpace();
static void resetBlockNameSpace();
static bool generateUniqueSignalName(ICorePort* port);
static std::string getGeneratedUniqueSignalName(ICorePort* port);
static bool isSignalNameGenerated(ICorePort* port); // membership check, no logging
// Read a verification <name>_input.csv into numeric rows (header skipped, all columns
// incl. the leading time column). Empty if the file is missing/unreadable. Used by the
// *ForVerification testbench builders to bake the test-input stimulus.
//
// ⚠ A CELL THAT IS NOT A NUMBER IS A ZERO HERE, NOT A DROPPED ROW, and the
// difference is the whole reason a String column can exist at all. This used
// to abandon the entire row on the first unparseable cell, so the moment
// anything wrote a quoted string every row went and every testbench was
// silently handed an EMPTY stimulus -- a failure that looks like a
// generator defect and is not one. The text of such a cell is not lost: it
// is what readVerificationInputText() returns, at the same [row][column].
static std::vector<std::vector<double>> readVerificationInputRows(const std::string& csvPath);
// The same file, same rows and same columns, as TEXT -- CSV-unquoted, so a
// string containing a comma comes back whole. A testbench emitter that has
// to bake a String stimulus reads this beside the numeric view and uses
// whichever its column is, which is why they are two views of one file
// rather than one view of two.
static std::vector<std::vector<std::string>> readVerificationInputText(const std::string& csvPath);
// ====================[Flattening a port list into CSV columns]==============
//
// The verification CSVs are a table: a time column, then one column per
// SCALAR value. Which columns a given port list produces is asked in five
// places -- the stimulus writer, the live-side collector, the signal-name
// list the UI labels its plot with, the emulated-side CSV reader, and every
// *ForVerification testbench emitter. They have to agree column for column
// or each side is scoring the other side's signals under its own names, so
// they all ask HERE and there is one answer.
//
// The three rules, all of them D2 restated as a table shape:
//
// * a NUMERIC port contributes rows x columns cells, row-major, named
// `sig` when it is 1x1 and `sig[r,c]` otherwise -- exactly what this
// path produced before types existed, so an all-double port list gives
// byte-identical column names and no committed golden moves;
// * a STRING port contributes ONE column whose cell is TEXT. Its matrix
// is a 1x1 placeholder (T3.2), so a numeric column here would compare a
// zero against a zero and pass while proving nothing;
// * a BUS port contributes one column per flattened element value, in
// spec order, named `sig.element` (plus `[r,c]` when the element is not
// 1x1). A bus has no single numeric column, and expanding it is what
// makes its residual, its plot and its per-signal error ordinary.
struct SignalColumn {
ICorePort* port = nullptr;
std::string name;
// The cell is text rather than a number: a String port, or a String
// element of a bus. Text columns are compared for EQUALITY; they carry
// the placeholder zero in the numeric view so column arithmetic on
// either side is unchanged.
bool isText = false;
// Where the value sits inside the port. For a numeric or String port
// this is the cell of its own matrix; for a bus element it is the
// element's index in the spec, plus the cell inside THAT element.
int busElementIndex = -1; // -1 unless the port carries a bus
std::size_t row = 0;
std::size_t col = 0;
// The bus element's own name, empty unless busElementIndex >= 0.
//
// ⚠ CARRIED, NOT RE-PARSED OUT OF `name`. Two emitters recovered it by
// splitting `name` on '.' and '[', which is wrong the moment an element
// is called "x.y" or "a[1]" -- and the failure would be a generated file
// that selects the wrong field, or does not parse. It is also what the
// generated code SELECTS BY (a dict key, a struct field), so it has to
// be the name and not a rendering of it.
std::string elementName;
};
// Requires a BUILT model: a bus port's element list comes from the spec,
// which Bus_Creator fills in the sizing loop, and a port's size is only
// final after the same loop.
static std::vector<SignalColumn> flattenSignalColumns(const std::vector<ICorePort*>& ports);
// Whether any column of that list is text -- i.e. whether this model needs
// the text half of the verification path at all. Every all-numeric model
// answers false and takes exactly the path it took before T5.8.
static bool anyTextColumn(const std::vector<SignalColumn>& columns);
// The accessor that reaches one column's cell from its signal, in the
// `[r][c]` spelling C, C++, Java and Rust share: `[r][c]` for a numeric or
// String port, `.element[r][c]` for a bus element (FEATURES_TO_ADD.md BF1.3).
static std::string columnCellAccessor(const SignalColumn& column);
// ====================[Bus record types (FEATURES_TO_ADD.md BF1.3)]======================
//
// C, C++, Java and Rust hold a bus in a RECORD TYPE, and each needs that type
// DECLARED before a signal can be one. Python's dict and MATLAB's struct are
// built by an expression and need none, which is why those two carried a bus
// first.
//
// ⚠ ONE RECORD PER DISTINCT SPEC, NOT ONE PER SIGNAL. A bus is copied from
// signal to signal -- through every subsystem gate it crosses -- and C only
// assigns a struct to a struct of the SAME type. Two signals whose specs are
// equal (ICoreBusSpec::operator==: names, order, types and sizes) share a
// record; any difference is a second one.
//
// ⚠ AN ELEMENT IS STORED EXACTLY AS A SIGNAL OF ITS TYPE AND SIZE WOULD BE:
// a double matrix for every numeric kind, the language's string matrix for a
// String. That is what keeps Bus_Creator and Bus_Selector plain signal copies
// with no conversion in either direction, and it is the same storage rule
// PORT_TYPES.md D8 (option 3) set for every other signal.
struct BusRecordElement {
std::string field; // the element's name, used verbatim as the field
std::string typeId; // the element's signal type
std::size_t rows = 1;
std::size_t cols = 1;
};
struct BusRecord {
std::string typeName; // "Bus0", "Bus1", ... in first-use order
std::vector<BusRecordElement> elements; // in spec order
std::vector<std::string> carriers; // paths of the ports that carry it
};
// Requires a BUILT model. Every distinct bus spec among the ordered blocks'
// ports and the boundary inputs, in the order a signal first carries it.
static std::vector<BusRecord> busRecords();
// The record type a port's bus is declared as, or "" when the port carries
// no bus. An input port answers for the bus its source drives.
static std::string busRecordName(const ICorePort* port);
// Why the bus records cannot be declared in the four record languages, or
// "" when they can. An element name becomes a FIELD verbatim, so it has to be
// a plain identifier and no reserved word of C, C++, Java or Rust -- the rule
// Simulink sets for a bus element name too. Renaming it silently would make
// the generated field disagree with the name a Bus_Selector picks by.
static std::string busRecordRefusal();
// ====================[Data stores (FEATURES_TO_ADD.md BF10.4)]======================
//
// A data store is a NAMED GLOBAL in every target: one fixed-size matrix per
// store, `ds0`, `ds1`, ... in the order the export places its owners, held
// BESIDE the signals -- in the same record, struct, class or process
// variable a block body already reaches every signal through -- and seeded
// with the store's initial value where the target seeds its state. So a
// Data Store Read is a copy out of it and a Data Store Write a copy into it,
// spelled exactly as a signal copy is, through each parser's
// dataStoreRef(owner); no block signature changes, and in the HDLs the
// store takes the working-copy-then-commit path every signal takes, which
// is what lets a Write be seen by a Read later in the same pass.
//
// Storage is the language's double (Q16.16 in the HDLs, LREAL in ST), the
// rule PORT_TYPES.md D8 (option 3) sets for every signal.
struct DataStore {
const ICoreBlock* owner = nullptr; // the block that declared it
std::string name; // the store's own name, as the model spells it
std::string codeName; // "ds0", "ds1", ...
std::size_t rows = 1; // the store's size: its initial value's
std::size_t cols = 1;
std::vector<double> initialValue; // rows * cols, row-major
};
// Requires a BUILT model whose config load has run (a store is declared
// there). Every store whose owner is among the ordered blocks, in that order.
static std::vector<DataStore> dataStores();
// The named global of the store `owner` declared, or "" when it is not one
// of dataStores().
static std::string dataStoreCodeName(const ICoreBlock* owner);
// A store's initial value as a matrix, for a parser's array-literal helper
// (toCppFixedArray, toPythonNPArray, toVHDLMatrix, ...).
static ICoreMatrix dataStoreInitialValue(const DataStore& store);
// Why this export cannot carry its data stores, or "" when it can: an
// accessor (a config marked ICoreBlockConfigVariable::setDataStoreAccess)
// whose name resolves to no store, in Simulink's words, or to a store
// declared OUTSIDE the exported subsystem -- the exported core would have
// nothing to hold it in. Checked by fireTarget and by the verifier before
// any parsing.
static std::string dataStoreRefusal();
// ====================[CSV, spelled once]======================
//
// Both verification CSVs are written by this tree and read back by it, and
// since T5.8 a cell can be a quoted string containing a comma. Splitting on
// ',' is then wrong in a way that shows up as a column-count mismatch three
// layers away, so the split and the quote live here and every reader and
// writer on both sides of the path uses them.
static std::vector<std::string> splitCsvLine(const std::string& line);
static std::string quoteCsvField(const std::string& field);
// Boundary input-gate output ports (drivers outside the export scope -> fed externally).
// These form the core's external-input interface; CSV input columns map onto them in
// row-major order. Operates on the current ICoreModelBuild::getOrderedBlocks() state.
static std::vector<ICorePort*> collectBoundaryInputPorts();
static bool generateUniqueBlockName(ICoreBlock* block);
static std::string getUniqueBlockFunctionName(ICoreBlock* block);
// AN ATOMIC SUBSYSTEM AS ITS OWN FUNCTION (FEATURES_TO_ADD.md BF12.4). The step body
// of the software targets, split at every atomic subsystem (ICoreBlock::
// isAtomicSubsystem): runs[0] is the step itself, and every other run is one atomic
// subsystem's contiguous run of getOrderedBlocks(), which the target emits as a
// function of its own -- `functionName`, "atomic1", "atomic2", ... in solve order, a
// namespace that cannot meet a block's "blkN" or a signal's "sigN". A run's `slots`
// are, in order, the blocks it calls and the atomic subsystems it calls: a slot that
// isSubsystemBlock() is a call to that subsystem's run (runFor), and an atomic
// subsystem nested in another is called from its parent's function. A plain
// subsystem's blocks stay in whichever run holds it, as they always have. A
// conditionally executed subsystem's face is a slot too, calling no run (runFor gives
// nullptr): its decision and guard are emitted at its place (ICoreConditionalExport,
// BF2.5).
//
// It changes no number: with one instance per subsystem the calls are the same calls
// in the same order. HDL keeps its one clocked process (subsystem-execution.md,
// note 1). Operates on the current build's ordered blocks.
//
// An atomic subsystem's Function Packaging (BF12.5, below) decides whether it gets a
// run: Inline gives it none, so its blocks stay in the run that holds it; Reusable
// function marks its run `reusable`, and an atomic subsystem inside a reusable one is
// inlined into it, so a reusable function calls only blocks.
struct ExecutionRun {
ICoreBlock* subsystem = nullptr; // nullptr for the step itself
std::string functionName; // "" for the step itself
std::vector<ICoreBlock*> slots;
bool reusable = false; // packaged as a Reusable function (BF12.5)
};
static std::vector<ExecutionRun> executionRuns();
// A REUSABLE FUNCTION (BF12.5; subsystem-execution.md has the design). Simulink's RTWSystemCode, a
// Subsystem block's "Function Packaging" config, read only on an ATOMIC subsystem
// (Simulink accepts and ignores it on a virtual one, measured -- §F.BF12 (d)):
// Auto, Nonreusable function -- a function of its own (BF12.4);
// Inline -- no function: its blocks run where it sits;
// Reusable function -- instances whose generated code is identical share
// ONE function, each with a frame of its own.
// Any other block, and a plain subsystem, answers Auto.
static const std::string CONFIG_FUNCTION_PACKAGING;
enum class FunctionPackaging { Auto, Inline, NonreusableFunction, ReusableFunction };
static FunctionPackaging functionPackaging(const ICoreBlock* subsystem);
// THE INSTANCE SCOPE. No block generator changes for a reusable function: while a
// scope is open, the two names every generator already asks for are the instance's
// own. For the subsystem `subsystem` itself and every block inside it,
// getUniqueBlockFunctionName answers `prefix + "blkN"` and getGeneratedUniqueSignalName
// `prefix + "sigN"`, numbered in solve order from 0 -- so two identical instances
// generate identical text. A signal from OUTSIDE that a body reads becomes a boundary
// input, `prefix + "uN"` in the order it is first asked for, which the caller copies
// into the frame before each call. Anything else outside that a body names -- another
// block, a data store -- cannot live in a frame, and makes `escape` say so. Scopes do
// not nest; ending one restores the global names untouched.
struct InstanceName {
ICorePort* port = nullptr; // the port the global name belongs to
std::string name; // its name inside the instance
};
struct InstanceScope {
std::string prefix; // the prefix the scope was opened with
std::vector<InstanceName> inputs; // boundary inputs: the outside port, its frame field
std::vector<InstanceName> signals; // every signal inside, the face's outputs included
std::string escape; // why the instance cannot be framed, or ""
};
static void beginInstanceScope(const ICoreBlock* subsystem, const std::string& prefix);
// What the open scope has named so far -- the boundary inputs are known only once
// the bodies that read them have been generated -- without closing it.
static InstanceScope instanceScopeSoFar();
static InstanceScope endInstanceScope();
static bool isInInstanceScope();
// True when `block` lies inside an instance that reusableFunctions() placed in a
// group, so its params and state live in the instance's frame and not in the core's.
// Set by the last reusableFunctions() call, cleared when names are generated.
static bool isFramed(const ICoreBlock* block);
// The instances that share each reusable function. `instanceText` generates, inside an
// open scope, the text an instance would be emitted as (its block bodies, frame
// declarations and run); instances whose text is identical share one function, named
// "r1", "r2", ... in solve order of the first instance, which is also the prefix of
// every name in its scope ("r1_"). A reusable run left out of every group -- it escaped
// the scope or matched no other instance's text is NOT a reason: a lone instance is a
// group of one -- is emitted as a function of its own, and the reason is logged.
struct ReusableFunction {
std::string name; // "r1"
std::vector<const ExecutionRun*> instances; // in solve order
};
static std::vector<ReusableFunction> reusableFunctions(
const std::vector<ExecutionRun>& runs, const std::string& language,
const std::function<std::string(const ExecutionRun&)>& instanceText);
// The group an instance's run belongs to, or nullptr; and the instance's index in it.
static const ReusableFunction* reusableFunctionFor(const std::vector<ReusableFunction>& groups,
const ExecutionRun* run, std::size_t* index = nullptr);
// The ports a verification level compares (FEATURES_TO_ADD.md BF12.3), for the
// live collector and every exported testbench alike: every block's outputs for
// Strict; for Output Gates Only (and Output Gates and Sink Blocks) what feeds each
// gate's (and sink's) inputs. In DIAGRAM order -- the blocks of getOrderedBlocks()
// in a depth-first walk of the tree -- never in solve order, which the build's
// first pass and the final one may lay out differently: the testbench is written
// from the final order and the live selection was made after the first, and since
// plain subsystems went virtual the two differ whenever one is nested.
static std::vector<ICorePort*> portsToVerify(const std::string& verificationLevel);
// The run an atomic subsystem slot calls, or nullptr.
static const ExecutionRun* runFor(const std::vector<ExecutionRun>& runs, const ICoreBlock* subsystem);
// A double as a literal every target reads back EXACTLY (FEATURES_TO_ADD.md BF11.8):
// 17 significant digits, and always a decimal point -- "2.0", "0.10000000000000001",
// "1.0e+20" -- because VHDL reads "2" as an INTEGER that to_fx(real) rejects, and C++'s
// std::min/max will not mix it with a double. The same text is a valid Python, MATLAB,
// Java, C, C++, VHDL, Verilog and SystemVerilog real; Rust appends "_f64". Not for
// inf or NaN, which have no literal in most of these.
//
// ⚠ A BARE `ostream << double` OR `std::to_string(double)` IS NOT THIS. The first keeps
// 6 significant digits and the second 6 decimals, so a parameter of 0.1234567 exported
// as 0.123457 in every array helper and every scalar parameter until 2026-09-30.
static std::string formatFullPrecision(double value);
static ICoreSignalBitSize getSignalBitSize(ICorePort* outputPort);
// ====================[Signal types in a language]======================
// The three questions every generator asks about a port, answered once so
// that ten parsers cannot answer them ten different ways. Each forwards to
// ICoreCodegenTypeMap with the port's own type id; a null port or a port
// whose type the language refuses comes back as the empty string, never as
// a plausible double.
//
// These are the ONLY place a generator learns what a signal's element type
// is. A parser that writes "double" into generated code is a parser that
// will keep writing "double" the day a port is an int32.
static std::string signalElementType(const std::string& language, const ICorePort* port);
static std::string signalZeroLiteral(const std::string& language, const ICorePort* port);
// The reason `language` cannot carry this port's type, or "" when it can.
static std::string signalUnsupportedReason(const std::string& language, const ICorePort* port);
// ====================[The write-time quantize, as data]======================
// What a generated core must do to a signal AFTER the block that produces it
// has run, so that the export and the live simulation hold the same number.
//
// A generated model's INTERFACE carries the port's real type; its internal
// signal storage stays the language's double. Storage being double is what
// makes this possible at all -- the quantize is then arithmetic on a double,
// so a generator emits the SAME EXPRESSION the simulator runs rather than an
// argument about why its language's cast is equivalent.
//
// Deliberately DATA and not text: the semantics belong in one place, while
// ten languages spell a for-loop ten ways. The authority is
// ICorePortSolverEnvironment::quantizeSignalToType(); any change there is a
// change here, and the two disagreeing is a parity failure no guard catches.
//
// Rule::None for ICoreDouble -- which is every port in the tree today -- so
// a generator emits nothing and an all-double core stays byte-identical.
struct SignalQuantize {
enum class Rule {
None, // ICoreDouble, String, Bus: the identity, emit nothing
ToSingle, // (double)(float)v
ToBool, // non-finite -> 0; then v != 0 ? 1 : 0
WrapInt, // non-finite -> 0; then wrap(trunc(v)) to `bits`
// A fixed-point port (FEATURES_TO_ADD.md BF14.2): WrapInt on the
// 2^-fraction grid -- wrap(trunc(v * 2^f)) to `bits`, times 2^-f.
WrapFixed
};
Rule rule = Rule::None;
int bits = 0;
bool isSigned = false;
int fraction = 0; // WrapFixed only: the fixdt's fraction length
// Declared, not defined: a header carries the contract and no bodies
// (DEVELOPER_GUIDELINES.md, the header surface rule).
[[nodiscard]] bool needed() const;
};
static SignalQuantize signalQuantize(const ICorePort* port);
// Whether ANY output port under the ordered build needs one. A generator
// asks before emitting its quantize helpers, so a core with nothing to
// quantize carries no helper text at all.
static bool anySignalNeedsQuantize();
// Whether any of them is a fixed-point one (BF14.2): the HDL generators
// emit their q_fix helper only then, so every other core is unchanged.
static bool anySignalNeedsFixedQuantize();
// 2^exponent as a decimal literal every target reads back as that exact
// double (17 significant digits; always with a point or an exponent).
static std::string powerOfTwoLiteral(int exponent);
// Whether every signal under `sourceTreeNode` is one an exported target must
// reproduce EXACTLY rather than within a tolerance band. True when no output
// port carries a Floating kind: an integer, a boolean or a string that comes
// back off a generated target with a residual is not "close", it is wrong,
// and a 0.1 % band would hide an off-by-one in an int32 counter for the
// whole range where 0.1 % is more than one.
//
// False for an empty or unresolvable scope, and false for the all-double
// tree of today -- so no rig changes band until a typed one exists.
static bool allSignalsCompareExactly(const ICoreSubsystemTreeNode* sourceTreeNode);
// The first port under `sourceTreeNode` whose type `language` refuses, as
// "<port path>: <reason>", or "" when every port is exportable. This is
// what an export target surfaces and what export verification records as a
// notApplicable cell instead of a failure. Walks the subsystem tree rather
// than the build ordering, so it answers before a model has been built.
static std::string firstUnsupportedSignal(const std::string& language,
const ICoreSubsystemTreeNode* sourceTreeNode);
// Sanitize a subsystem name into an identifier that is legal as a file name AND as a
// module/class/entity name across every export language (C/C++/Rust/Java/Python/MATLAB/
// VHDL/Verilog/SystemVerilog/PLC-ST). Keeps [A-Za-z0-9], collapses every run of other
// characters - INCLUDING literal underscores - to a single '_', drops a leading or
// trailing '_', prefixes a leading digit with "ICore_", and falls back to
// "ExportedModel" if nothing usable remains. The underscore rules come from VHDL and
// Structured Text, the strictest of the ten: both reject "A__B", "_A" and "A_". The
// file name and the in-code identifiers are derived from the same result, so they
// always match.
static std::string sanitizeCodeIdentifier(const std::string& rawName);
// ====================[Code Types]======================
static const std::string CODE_TYPE_PYTHON;
static const std::string CODE_TYPE_MATLAB;
static const std::string CODE_TYPE_JAVA;
static const std::string CODE_TYPE_RUST;
static const std::string CODE_TYPE_CPP;
static const std::string CODE_TYPE_C;
// static const std::string CODE_TYPE_ARM;
// static const std::string CODE_TYPE_x86;
// static const std::string CODE_TYPE_DSP;
static const std::string CODE_TYPE_SYSTEM_VERILOG;
static const std::string CODE_TYPE_VERILOG;
static const std::string CODE_TYPE_VHDL;
// ST is the only IEC 61131-3 language the engine exports. LD / FBD / SFC are
// graphical languages with no textual exporter behind them -- they used to be
// offered as target types and could only ever fail at export, so they are gone.
static const std::string CODE_TYPE_PLC_ST;
static std::vector<std::string> AVAILABLE_CODE_TYPES;
// Code types that support export verification (emulation + residual comparison).
static const std::vector<std::string> VERIFIABLE_CODE_TYPES;
// ====================[Code Templates Delimiters]======================
static const std::string DELIMITER_IMPORTS;
static const std::string DELIMITER_CONFIG;
static const std::string DELIMITER_GLOBAL_TIME;
static const std::string DELIMITER_SIGNALS;
static const std::string DELIMITER_BLOCK_PARAMS;
static const std::string DELIMITER_EXECUTE;
static const std::string DELIMITER_BLOCK_FUNC_NAME;
static const std::string DELIMITER_SOLVE_METHOD_NAME;
static const std::string DELIMITER_COMPUTE_METHOD_NAME;
static const std::string DELIMITER_BLOCK_FIELDS;
static const std::string DELIMITER_BLOCK_INSTANCES;
};
};
ICoreCodegenTypeMap.h#
src/ICoreBlocks/ICoreCodegen/ICoreCodegenTypeMap.h
ICoreCodegenTypeMap#
ICoreCodegenTypeMap.h:29 · class · nested Mapping · 4 declaration(s)
The one place a language name meets a signal type id.
class ICoreCodegenTypeMap {
public:
enum class Support {
Yes,
GeneratedPerDiagram,
No
};
struct Mapping {
Support support = Support::No;
// The type as the language declares it. Empty when support is not Yes.
std::string declaration;
// What the language writes for "nothing" of this type -- `0`, `false`,
// `0.0`, `(others => '0')`. This is the language's answer and overrides
// the registry's language-neutral one.
std::string zeroLiteral;
// A cast of an expression to this type, with `{}` standing for the
// expression: `(int32_t)({})`, `({}) as i32`, `TO_DINT({})`. Empty
// where the language needs no cast (Python and MATLAB mostly do, VHDL
// always does).
std::string castPattern;
// Why the language refuses, in words an export dialog shows a user.
// Non-empty exactly when support is No.
std::string reason;
};
// The ten language names, spelled exactly as ICoreCodeEngine spells them.
static const std::vector<std::string>& languages();
// The mapping for one pair. An unknown language or an unknown type id comes
// back as a refusal naming what was not recognised, rather than as a
// plausible default -- a wrong type in generated code is silent until the
// numbers disagree.
static const Mapping& lookup(const std::string& language, const std::string& typeId);
static bool isSupported(const std::string& language, const std::string& typeId);
// The reason a language refuses a type, or an empty string when it does
// not refuse it. This is what an export target surfaces and what export
// verification records instead of a failure.
static std::string unsupportedReason(const std::string& language, const std::string& typeId);
// `expression` cast to the type, or the expression unchanged where the
// language needs no cast.
static std::string castTo(const std::string& language, const std::string& typeId,
const std::string& expression);
// A numeric value written as a literal of this type in this language:
// `42`, `42.0f`, `true`, `to_signed(42, 32)`. Values are quantized to the
// type first, so what is written matches what a port would hold. String
// and Bus have no numeric literal and come back as the zero literal.
static std::string formatLiteral(const std::string& language, const std::string& typeId,
double value);
};
};
ICoreConditionalExport.h#
src/ICoreBlocks/ICoreCodegen/ICoreConditionalExport.h
ICoreConditionalExport#
ICoreConditionalExport.h:70 · class · nested Guards · 13 declaration(s)
AN ENABLED, TRIGGERED, RESETTABLE OR ACTION SUBSYSTEM, AND A FOR OR WHILE LOOP, IN ALL TEN EXPORT TARGETS (the subsystem-execution design page has the design and the per-target table).
class ICoreConditionalExport {
public:
// Why `language` (an ICoreCodeEngine::CODE_TYPE_*) cannot carry the
// conditional subsystems among `blocks` (the build's ordered blocks), or a
// block among them that schedules its own hits (a variable-step construct,
// FEATURES_TO_ADD.md BF21.4), or "". Needs no generated names; fireTarget and
// the verifier ask before parsing.
static std::string refusal(const std::vector<ICoreBlock*>& blocks, const std::string& language);
// Finds the conditional subsystems among the build's ordered blocks, for
// `language`. Every parser calls it once its block and signal names exist and
// before it emits anything; "" on success, or why it cannot -- a check that
// needs the generated block bodies.
static std::string prepare(const std::string& language);
// ---- Declarations, one call per section of the generated file ----
// The decision's persistent state (edge memories, ever-ran, was-enabled, the
// pending re-seed): fields in C's State and Rust's core struct, members in C++
// and Java, a module object in Python, `obj.state` fields in MATLAB (set in the
// constructor), architecture signals in VHDL, module variables in Verilog and
// SystemVerilog, VAR lines in PLC-ST. "" when the model has none.
static std::string persistentDeclarations(const std::string& indent);
// Their initial values where a declaration cannot carry one: C's initialiser
// after its memset, Rust's new() fields, MATLAB's constructor, and the reset
// branch of the three HDL processes.
static std::string persistentInitialisation(const std::string& indent);
// The decision's scratch variables, where a target declares them apart from
// their use: VHDL process variables, Verilog block registers, SystemVerilog
// module variables, PLC-ST VAR lines. "" for the software targets, which
// declare them where the decision is.
static std::string scratchDeclarations(const std::string& indent);
// C only: a marker field before (`before`) or after a block's state fields,
// bracketing each conditional run's slice of State so a reset zeroes it.
static std::string cStateMarkers(const ICoreBlock* block, bool before);
// ---- The step body, slot by slot ----
// The guards open at this point of a slot walk, outermost first.
struct Guards {
std::vector<const ICoreBlock*> open;
};
// Before emitting `slot`: closes every guard `slot` is not inside, then, when
// `slot` is a conditional subsystem's Subsystem block, emits its decision and
// opens its guard; when it is a Merge, emits the Merge's output: its initial
// output until a source has run, then the input whose subsystem ran on this
// step (the higher one when two did). `indent` is the walk's base indentation.
static std::string enter(Guards& guards, const ICoreBlock* slot, const std::string& indent);
// Closes every open guard: after the walk's last slot.
static std::string closeAll(Guards& guards, const std::string& indent);
// `text` (a slot's emitted lines) indented one level further per open guard.
static std::string nest(const Guards& guards, const std::string& text);
// HDL: `text` (the emitted body of `block`) with every read of a register a
// re-seed seeds turned into a read of its read view; `text` unchanged for any
// other block or target.
static std::string seedReads(const ICoreBlock* block, const std::string& text);
// True for a block the walk must not call: a Trigger port block, whose output
// the decision writes, a gate isSlicingGate names, and a Merge, whose output
// enter() writes.
static bool skipsCall(const ICoreBlock* block);
// ---- A subsystem port that converts a unit ----
// After a gate has copied its value into `destination`, the lines that make it
// y = a*u + b in place, with the a and b ICoreUnits::conversionAt decided for
// `gatePort` in the build (an input gate's output port, or an output gate's
// input port: FEATURES_TO_ADD.md BF20.3, BF20.6), in `language`'s syntax; ""
// when the port converts nothing. `destination` is the stored matrix as the
// gate names it -- "signals.X", "obj.signals.X", "core->signals.X", "s.X",
// "w_X" -- and for PLC-ST the token head ("OUT:0", "SIG:X") each element's
// ":i:j" completes; `rows` x `cols` is its shape. HDL computes in its Q16.16
// datapath, as the Unit Conversion block does, so a factor or a result past
// its range does not fit there either.
static std::string unitConversion(const ICorePort* gatePort, const std::string& language,
const std::string& destination, int rows, int cols,
const std::string& indent);
// Whether `gate` is an input gate inside a For Each that partitions one of its
// inputs, or an output gate inside one: the walk writes its slice or its join
// instead of calling it, so its own body must be one that does nothing (it
// is still declared, and a whole-signal copy between a slice and the face
// does not compile where arrays are sized). From the model alone.
static bool isSlicingGate(const ICoreBlock* gate);
// The scratch variable that reads 1 when `face`'s subsystem ran on this step
// and 0 when it did not -- what a Merge exported after it reads as "written
// this step" (its one-writer exemption, BF2.4). Valid in the step body after
// the decision; "" when `face` is not a prepared conditional subsystem.
static std::string ranThisStep(const ICoreBlock* face);
};
};
ICoreMessageExport.h#
src/ICoreBlocks/ICoreCodegen/ICoreMessageExport.h
ICoreMessageExport#
ICoreMessageExport.h:39 · class · nested Queue · 8 declaration(s)
MESSAGES IN THE SOFTWARE EXPORT TARGETS (FEATURES_TO_ADD.md BF4.4, decision D4).
class ICoreMessageExport {
public:
struct Queue {
std::string codeName; // mq0, mq1, ...
const ICorePort* sender = nullptr; // a block's message output (not a gate's or a face's)
const ICorePort* reader = nullptr; // a Receive's input, or a message Trigger's face
std::size_t capacity = 1;
bool overwrite = true; // a full queue drops its oldest; false: the newest is dropped
bool lifo = true; // which message is taken first
std::size_t rows = 1; // the payload's shape
std::size_t cols = 1;
};
// Every queue of the export, from the build's ordered blocks.
static std::vector<Queue> queues();
// The queue `port` sends into or reads from; false when it has none (a
// Send whose line goes nowhere).
static bool bySender(const ICorePort* port, Queue& out);
static bool byReader(const ICorePort* port, Queue& out);
// Why `language` cannot carry the messages among `blocks`, or "": a message
// line whose reader the export does not know (a block that is neither a
// Receive nor a message Trigger's face). HDL and PLC-ST are refused earlier,
// by the type map.
static std::string refusal(const std::vector<ICoreBlock*>& blocks, const std::string& language);
// ---- Per target, at the places the data stores take ----
// The queue type and its functions, once per core; "" with no queue.
// `logSends` (MATLAB only): every push also appends its value to the queue's
// `sent` list, which the Simulink diagram parity core reads to compare the
// message lines as event traces (FEATURES_TO_ADD.md BF4.5); never in a
// public export, where the list would only grow.
static std::string runtime(const std::string& language, bool logSends = false);
// Each queue's field(s) in the signals record, at `indent`, each line ending
// as that record's fields do; "" with no queue. C: `double mqN_items[CAP][R*C];`
// and `int mqN_count;`, zeroed with the record.
static std::string fields(const std::string& language, const std::string& indent);
// Where a record's field cannot carry its initial value (Rust's new(),
// MATLAB's constructor): the initialisers, at `indent`; "" otherwise.
static std::string initialisation(const std::string& language, const std::string& indent, bool logSends = false);
// ---- Statements, `record` the signals record as the context spells it ----
// ("signals", "self.signals", "obj.signals", "core->signals").
// Pushes `value` (an expression of the payload's matrix type) under the
// queue's policy.
static std::string push(const std::string& language, const std::string& record, const Queue& queue,
const std::string& value, const std::string& indent);
// How many messages wait, as an integer expression.
static std::string count(const std::string& language, const std::string& record, const Queue& queue);
// Takes the next message: when one waits, `target` (an lvalue of the
// payload's matrix type) receives it and `ok` (an existing integer or bool
// variable) is set to 1; otherwise `ok` is set to 0 and `target` is left
// alone.
static std::string pop(const std::string& language, const std::string& record, const Queue& queue,
const std::string& target, const std::string& ok, const std::string& indent);
};
};
ICoreParameterWriteExport.h#
src/ICoreBlocks/ICoreCodegen/ICoreParameterWriteExport.h
ICoreParameterWriteExport#
ICoreParameterWriteExport.h:28 · class · nested Target · 5 declaration(s)
Simulink's Parameter Writer in a live run and in an exported core (FEATURES_TO_ADD.md BF11.4-BF11.6).
class ICoreParameterWriteExport {
public:
struct Target {
const char* ownerType; // "Control_Systems/Base_Blocks/Gain"
const char* simulinkName; // "Gain"
const char* config; // "Gain Value"
const char* field; // "gain": <block>_gain, and gain_<block> in ST
bool scalar; // a one-value parameter (Saturation's limits)
};
static const std::vector<Target>& targets();
static const Target* find(const std::string& ownerType, const std::string& simulinkName);
// The writer's two configs, spelled once.
static const std::string CONFIG_OWNER; // "Parameter owner block"
static const std::string CONFIG_NAME; // "Parameter name"
// True for a Parameter Writer block.
static bool isWriter(const ICoreBlock* block);
// The writer's block body for `language` (an ICoreCodeEngine::CODE_TYPE_*
// name): the assignment where the body reaches the parameters; for Rust and C++
// an empty struct, since slot() does it. Empty, with the reason logged, when the
// owner or its parameter cannot be resolved.
static std::string body(ICoreBlock* writer, const std::string& language);
// What a Rust or C++ core's step runs at the writer's slot, in place of a call,
// each line prefixed with `indent`.
static std::string slot(ICoreBlock* writer, const std::string& language, const std::string& indent);
};
};
ICoreStateAccessExport.h#
src/ICoreBlocks/ICoreCodegen/ICoreStateAccessExport.h
ICoreStateAccessExport#
ICoreStateAccessExport.h:26 · class · 4 declaration(s)
Simulink's State Reader and State Writer in an exported core (FEATURES_TO_ADD.md BF15.4).
class ICoreStateAccessExport {
public:
// rows x cols element names, row-major, from `pattern`: "{i}" "{j}" and "{e}"
// (e = i*cols + j) counted from 0, "{I}" "{J}" and "{E}" from 1.
static std::vector<std::string> grid(std::size_t rows, std::size_t cols, const std::string& pattern);
// True for a block with a config marked as state access: a State Reader or
// State Writer.
static bool isAccessor(const ICoreBlock* block);
// The accessor's block body for `language` (an ICoreCodeEngine::CODE_TYPE_*
// name), in the shape that language's parser takes a body: where the body
// reaches the owner, the copy itself; for Rust and C++, an empty struct, since
// slot() does the copy. Empty, with the reason logged naming both blocks,
// when the owner's state cannot be reached in that core: an owner that does
// not say where it keeps it, an owner or accessor inside a reusable subsystem
// function (its state lives in a frame of its own), or a Writer's input that
// fits the state neither element for element nor as one value.
static std::string body(ICoreBlock* accessor, const std::string& language);
// What a Rust or C++ core's step runs at the accessor's slot, in place of a
// call, each line prefixed with `indent`. body() has already refused what
// cannot be reached.
static std::string slot(ICoreBlock* accessor, const std::string& language, const std::string& indent);
};
};
ICoreSystemVerilogParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/HDL/ICoreSystemVerilogParser.h
Declares no class of its own — see the file.
ICoreVHDLParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/HDL/ICoreVHDLParser.h
Declares no class of its own — see the file.
ICoreVerilogParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/HDL/ICoreVerilogParser.h
Declares no class of its own — see the file.
ICoreCParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICoreCParser.h
Declares no class of its own — see the file.
ICoreCppParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICoreCppParser.h
ICoreCppParser#
ICoreCppParser.h:28 · class · 8 declaration(s)
ICoreCppParser Exports a block diagram to a fixed-size, heap-free C++ project: <name>_deployableCore.hpp (header-only DeployableCore) <name>_testbench.cpp (runnable driver) Mirrors the Rust fixed-s...
class ICoreCppParser {
public:
// ---- Entry Point ----
static bool exportToCpp(const std::filesystem::path& pathToWriteCodeTo,
const ICoreSubsystemTreeNode* sourceTreeNode);
// ---- Verification entry: emits the core + a RECORDING testbench that steps the
// horizon, records `portsToRecord` each step (time + signal columns, same order
// as the native sim), and writes <name>_output.csv. ----
static bool exportToCppForVerification(const std::filesystem::path& pathToWriteCodeTo,
const ICoreSubsystemTreeNode* sourceTreeNode,
const std::vector<ICorePort*>& portsToRecord);
// ---- Public Helpers (used by block generateBodyCode_Cpp / generateParamsCode_Cpp) ----
// C++ aggregate initializer for a std::array-of-std::array literal: {{ {{a, b}}, {{c, d}} }}
static std::string toCppFixedArray(const ICoreMatrix& matrix);
// ⚠ TWO TYPES PER PORT, AND THE DIFFERENCE IS THE WHOLE DESIGN.
//
// fixedType() is what a signal is STORED in and is what every block body
// gets: `Mat<R, C>`, a matrix of DOUBLE, for every kind that rides in the
// matrix. It must stay double. A C++ block body binds its scratch with
// `auto` off zerosFor()/readInputExpr(), so typing this would type ~296
// block bodies' LOCALS at a stroke -- a change to the arithmetic every block
// performs, not to how a signal is declared.
//
// interfaceType() is what the port CARRIES, and reaches generated text only
// in the `Inputs` struct a caller of the core writes to.
static std::string fixedType(const ICorePort* port);
static std::string interfaceType(const ICorePort* port);
// Value-initialised (zero) Mat<R, C>{} for a port's signal.
static std::string zerosFor(const ICorePort* port);
// The write-time quantize, emitted after the block that wrote `name`, and
// the inline helpers it calls. Both empty when no port needs one, which is
// every model today.
static std::string quantizeStatement(const ICorePort* port, const std::string& name,
const std::string& indent);
static std::string quantizeHelpers();
// signals.<src> if connected & in-scope, else a fixed-size zero matrix.
static std::string readInputExpr(const ICorePort* inputPort);
// A C++ string literal, for a baked text stimulus (T5.8). The stimulus
// alphabet is ASCII letters, so the escapes should never fire; a String
// boundary input can also be fed from a rig file, and one quote in such a
// word would turn a generated .cpp file into a syntax error that reads as a
// compiler problem.
static std::string cppStringLiteral(const std::string& text);
// ---- Name Constants (used by block generateBodyCode_Cpp) ----
static const std::string SIGNALS_STRUCT_NAME; // "signals"
static const std::string PARAMS_STRUCT_NAME; // "params"
static const std::string TIME_ARG_NAME; // "t"
static const std::string SOLVE_METHOD_NAME; // "solve"
// The data store `owner` declared, as a C++ block body reaches it: signals.<ds>, a
// Mat<R, C>. "" when it is not a store of this export (FEATURES_TO_ADD.md BF10.4).
// A Data Store Read copies out of it and a Data Store Write into it, as a body
// copies a signal.
static std::string dataStoreRef(const ICoreBlock* owner);
};
};
ICoreJavaParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICoreJavaParser.h
Declares no class of its own — see the file.
ICoreMatlabParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICoreMatlabParser.h
Entry Point Exports two files, mirroring the Python separation: <name>_deployableCore.m -> classdef handle: params + signals + state + execute_blocks <name>_testbench.m -> runnable script that instantiates and drives the core
ICoreMatlabParser#
ICoreMatlabParser.h:13 · class · 12 declaration(s)
class ICoreMatlabParser {
public:
// -------------------------------------------------------
// Entry Point
// -------------------------------------------------------
// Exports two files, mirroring the Python separation:
// <name>_deployableCore.m -> classdef handle: params + signals + state + execute_blocks
// <name>_testbench.m -> runnable script that instantiates and drives the core
static bool exportToMatlab(const std::filesystem::path& pathToWriteCodeTo,
const ICoreSubsystemTreeNode* sourceTreeNode);
// ---- Verification entry: core + a RECORDING testbench (script) that records
// `portsToRecord` each step (time + signal columns) to <name>_output.csv. ----
static bool exportToMatlabForVerification(const std::filesystem::path& pathToWriteCodeTo,
const ICoreSubsystemTreeNode* sourceTreeNode,
const std::vector<ICorePort*>& portsToRecord);
static std::string toMatlabMatrix(const ICoreMatrix& matrix);
// ---- Fixed-size block-body helpers (each block owns its full solve) ----
// MATLAB expression for a zero matrix sized to `port`.
//
// TWO SPELLINGS PER PORT, AND THE DIFFERENCE IS THE WHOLE DESIGN.
// zerosFor() is STORAGE: a double matrix for every kind that rides in the
// matrix, so that every block body keeps computing in double and the port's
// kind is applied once, by the quantize emitted after the block that wrote
// it. MATLAB makes this the difference between right and wrong rather than
// a matter of taste: assignment carries the class, so an int32 signal would
// make every downstream expression integer arithmetic -- and MATLAB's
// integer arithmetic SATURATES, where a port wraps.
// interfaceZerosFor() is what the port CARRIES, and reaches generated text
// only in the `inputs` struct a caller of the core writes to. String and Bus
// do not ride in the matrix, so storage hands their own spelling back.
static std::string zerosFor(const ICorePort* port);
static std::string interfaceZerosFor(const ICorePort* port);
// The write-time quantize, emitted after the block that wrote `name`, and
// the local functions it calls. Both empty when no port needs one.
static std::string quantizeStatement(const ICorePort* port, const std::string& name,
const std::string& indent);
static std::string quantizeHelpers();
// A block's own LOCAL FUNCTIONS for the end of the class-definition file -- a
// table and its evaluation, which a block body cannot hold, since MATLAB local
// functions must follow the classdef's `end` (the JPL ephemeris blocks are the
// first users). A block registers them from generateBodyCode_Matlab() under a key
// unique to it (its unique block function name); every MATLAB writer clears them
// when it starts a file and appends localFunctions() after the classdef's `end`.
// A key registered twice keeps its first text, so a body generated twice in one
// file still defines each function once.
static void addLocalFunctions(const std::string& key, const std::string& text);
static std::string localFunctions();
static void clearLocalFunctions();
// MATLAB expression that reads an input port's source signal by name, or a zero
// matrix when the port is unconnected / its source is out of the export scope.
static std::string readInputExpr(const ICorePort* inputPort);
// A MATLAB single-quoted char literal, for a baked text stimulus (T5.8).
// A quote inside one is doubled; the stimulus alphabet is ASCII letters, so
// this should never fire, but a String boundary input can also be fed from a
// rig file and one quote would turn a generated .m file into a syntax error.
static std::string matlabStringLiteral(const std::string& text);
// Per-block local clock (replaces the old global t_global). Emits lazily-initialised
// state on obj.state.<blockFuncName>_* and sets obj.state.<blockFuncName>_time.
static std::string getBlockLocalClock(const std::string& blockFuncName);
// The seconds-per-execute_blocks() step the local clocks bake in. Set by the
// export entry points above from the source subsystem's sampling time; any
// OTHER driver of the block-level MATLAB codegen (the parity suite's
// ICoreParityCoreEmitter) must set it before generating bodies, or source
// blocks inherit whatever the last export left here.
static void setSourceSamplingTime(double newSourceSamplingTime);
// -------------------------------------------------------
// Method / Accessor Name Constants
// -------------------------------------------------------
static const std::string SOLVE_METHOD_NAME; // "solve"
static const std::string COMPUTE_METHOD_NAME; // "compute"
static const std::string SIGNALS_STRUCT_NAME; // "obj.signals"
static const std::string PARAMS_STRUCT_NAME; // "obj.params"
static const std::string STATE_STRUCT_NAME; // "obj.state"
// The data store `owner` declared, as a MATLAB block body reaches it:
// obj.signals.<ds>, a double matrix. "" when it is not a store of this export
// (FEATURES_TO_ADD.md BF10.4). A Data Store Read copies out of it and a Data Store
// Write into it, as a body copies a signal.
static std::string dataStoreRef(const ICoreBlock* owner);
};
};
ICorePythonParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICorePythonParser.h
---- Fixed-size block-body helpers (each block owns its full solve) ---- Python expression for a zero-initialised array sized to
port.STORAGE, NOT INTERFACE, and the difference is the whole design. A signal is stored in float64 for every kind that rides in the matrix, so that every block body keeps computing in double and the port's kind is applied exactly once, by the quantize emitted after the block that wrote it. In a dynamically typed language this matters MORE than in a static one: a block body reads
signals.xand computes with whatever dtype it finds, so an int32 array here would make every downstream multiply and divide integer arithmetic, in ~296 block bodies, with nothing declaring it.String and Bus do not ride in the matrix, so their storage IS their type: an object array of empty strings, and a dict.
ICorePythonParser#
ICorePythonParser.h:11 · class · 10 declaration(s)
class ICorePythonParser {
public:
static bool exportToPython(const std::filesystem::path &pathToWriteCodeTo,
const ICoreSubsystemTreeNode* sourceTreeNode,
const std::string& verificationLevel,
const std::vector<ICorePort*>& portsToRecord);
static std::string parseBlockSolveMethod(const ICoreBlock* block, const std::string &prefix);
static std::string toPythonNPArray(const ICoreMatrix& matrix);
// ---- Fixed-size block-body helpers (each block owns its full solve) ----
// Python expression for a zero-initialised array sized to `port`.
//
// STORAGE, NOT INTERFACE, and the difference is the whole design. A signal
// is stored in float64 for every kind that rides in the matrix, so that
// every block body keeps computing in double and the port's kind is applied
// exactly once, by the quantize emitted after the block that wrote it. In a
// dynamically typed language this matters MORE than in a static one: a
// block body reads `signals.x` and computes with whatever dtype it finds,
// so an int32 array here would make every downstream multiply and divide
// integer arithmetic, in ~296 block bodies, with nothing declaring it.
//
// String and Bus do not ride in the matrix, so their storage IS their type:
// an object array of empty strings, and a dict.
static std::string zerosFor(const ICorePort* port);
// The write-time quantize, emitted after the block that wrote `name`, and
// the module-level helpers it calls. Both empty when no port needs one, so
// an all-float64 model's generated text does not move.
static std::string quantizeStatement(const ICorePort* port, const std::string& name,
const std::string& indent);
static std::string quantizeHelpers();
// Python expression that reads an input port's source signal by name, or a fresh
// zero array when the port is unconnected / its source is out of the export scope.
static std::string readInputExpr(const ICorePort* inputPort);
static std::string getParsedIIREmulator();
static double getSourceNodeSamplingTime();
static std::string getBlockLocalClockDeclaration();
static std::string getBlockLocalClock();
static const std::string SOLVE_METHOD_NAME;
static const std::string COMPUTE_METHOD_NAME;
static const std::string INPUTS_VECTOR_NAME;
static const std::string OUTPUTS_VECTOR_NAME;
static const std::string SIGNALS_MAP_NAME;
static const std::string PARAMS_CLASS_NAME;
static const std::string PARAMS_INSTANCE_NAME;
static const std::string MODEL_MODULE_NAME;
// The data store `owner` declared, as a Python block body reaches it:
// signals.<ds>, a float64 ndarray. "" when it is not a store of this export
// (FEATURES_TO_ADD.md BF10.4). A Data Store Read copies out of it and a Data Store
// Write into it, as a body copies a signal.
static std::string dataStoreRef(const ICoreBlock* owner);
};
};
ICoreRustParser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/Others/ICoreRustParser.h
Declares no class of its own — see the file.
ICorePLC_ST_Parser.h#
src/ICoreBlocks/ICoreCodegen/CodeParsers/PLC/ICorePLC_ST_Parser.h
Declares no class of its own — see the file.