Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ccs_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ jobs:
sdk_installer: mcu_plus_sdk_am243x_11_02_00_24-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-ouHbHEm1PK/11.02.00.24/mcu_plus_sdk_am243x_11_02_00_24-linux-x64-installer.run
- device: am261x
mcu_plus_sdk: mcu_plus_sdk_am261x_10_02_00_15
sdk_installer: mcu_plus_sdk_am261x_10_02_00_15-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-h64OlWnQrg/10.02.00.15/mcu_plus_sdk_am261x_10_02_00_15-linux-x64-installer.run
mcu_plus_sdk: mcu_plus_sdk_am261x_26_00_00_06
sdk_installer: mcu_plus_sdk_am261x_26_00_00_06-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-h64OlWnQrg/26.00.00.06.STS/mcu_plus_sdk_am261x_26_00_00_06-linux-x64-installer.run
- device: am263px
mcu_plus_sdk: mcu_plus_sdk_am263px_10_02_00_15
sdk_installer: mcu_plus_sdk_am263px_10_02_00_15-linux-x64-installer.run
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/makefile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ jobs:
sdk_installer: mcu_plus_sdk_am243x_11_02_00_24-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-ouHbHEm1PK/11.02.00.24/mcu_plus_sdk_am243x_11_02_00_24-linux-x64-installer.run
- device: am261x
mcu_plus_sdk: mcu_plus_sdk_am261x_10_02_00_15
sdk_installer: mcu_plus_sdk_am261x_10_02_00_15-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-h64OlWnQrg/10.02.00.15/mcu_plus_sdk_am261x_10_02_00_15-linux-x64-installer.run
mcu_plus_sdk: mcu_plus_sdk_am261x_26_00_00_06
sdk_installer: mcu_plus_sdk_am261x_26_00_00_06-linux-x64-installer.run
sdk_url: https://dr-download.ti.com/software-development/software-development-kit-sdk/MD-h64OlWnQrg/26.00.00.06.STS/mcu_plus_sdk_am261x_26_00_00_06-linux-x64-installer.run
- device: am263px
mcu_plus_sdk: mcu_plus_sdk_am263px_10_02_00_15
sdk_installer: mcu_plus_sdk_am263px_10_02_00_15-linux-x64-installer.run
Expand Down
30 changes: 29 additions & 1 deletion .metadata/product.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"AM243x_ALV_beta",
"AM243x_ALX_beta",
"AM261x_ZFG",
"AM261x_ZCZ"
"AM261x_ZCZ",
"AM261x_ZNC",
"AM261x_ZFG_400",
],
"contexts": {
"AM64x": [{
Expand Down Expand Up @@ -91,6 +93,19 @@
"name": "icss_g1_tx_pru1",
"targetdbId": "icss_g1_tx_pru1"
}],
"AM261x_ZFG_400": [{
"name": "icss_m0_pru0",
"targetdbId": "icss_m0_pru0"
},{
"name": "icss_m0_pru1",
"targetdbId": "icss_m0_pru1"
},{
"name": "icss_m1_pru0",
"targetdbId": "icss_m1_pru0"
},{
"name": "icss_m1_pru1",
"targetdbId": "icss_m1_pru1"
}],
"AM261x_ZFG": [{
"name": "icss_m0_pru0",
"targetdbId": "icss_m0_pru0"
Expand All @@ -117,6 +132,19 @@
"name": "icss_m1_pru1",
"targetdbId": "icss_m1_pru1"
}],
"AM261x_ZNC": [{
"name": "icss_m0_pru0",
"targetdbId": "icss_m0_pru0"
},{
"name": "icss_m0_pru1",
"targetdbId": "icss_m0_pru1"
},{
"name": "icss_m1_pru0",
"targetdbId": "icss_m1_pru0"
},{
"name": "icss_m1_pru1",
"targetdbId": "icss_m1_pru1"
}],
"AM243x_ALX_beta": [{
"name": "icss_g0_pru0",
"targetdbId": "icss_g0_pru0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -930,10 +930,8 @@ function validate(inst, report)
validateR31SimulationInput(inst, report);

// Check for UART blocks and warn about unsupported simulation
const uartTxModule = system.modules["/pru_blocks/pru_io_blocks/uart_tx"];
const uartRxModule = system.modules["/pru_blocks/pru_io_blocks/uart_rx"];
const hasUartBlock = (uartTxModule && uartTxModule.$instances && uartTxModule.$instances.length > 0) ||
(uartRxModule && uartRxModule.$instances && uartRxModule.$instances.length > 0);
const uartModule = system.modules["/pru_blocks/pru_io_blocks/uart_config"];
const hasUartBlock = (uartModule && uartModule.$instances && uartModule.$instances.length > 0)

if (hasUartBlock) {
report.logWarning(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ sysconfig_generated_start:
%if(typeof(labels[iterator]) == "string" && labels[iterator] !== "")
%{
`labels[iterator]`:
%if(labels[iterator].endsWith("_end"))
%{

%}
%}
% if((!pruInstructions[iterator].includes("-1")) && (pruInstructions[iterator] != "0") && (!pruInstructions[iterator].includes("undefined")))
% {
Expand All @@ -128,6 +132,10 @@ sysconfig_generated_start:
%if(typeof(labels[iterator]) == "string" && labels[iterator] !== "")
%{
`labels[iterator]`:
%if(labels[iterator].endsWith("_end"))
%{

%}
%}
% if((!pruInstructions[iterator].includes("-1")) && (pruInstructions[iterator] != "0") && (!pruInstructions[iterator].includes("undefined")))
% {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ function pushInstruction(instance, parentInstance) {
if (moduleInstanceRegisterMap[instanceName]?.conditionCalculated === 1) {
return moduleInstanceRegisterMap[instanceName].label;
}

// Process prev port if it exists and is connected
let label;
if (instance?.["prev"] && instance?.["prev"][0]?.["inst"]) {
Expand Down Expand Up @@ -619,6 +620,10 @@ function pushInstruction(instance, parentInstance) {
};
instruction = `LDI ${getPruRegister(byteOffset, instance.loopCountRegSize)}, ${instance.loopCount}`;
addToPruRegisterAllocationSummary(label, instruction, instance, instanceName, 0);
//push <LoopName>_start label between LDI and LOOP so a Flow Control
//jump back here resumes with the current counter value instead of
//resetting it (jumping to before the LDI would reset the count)
addToPruRegisterAllocationSummary(`${instanceName}_start`, "0", instance, instanceName, 0);
//push loop instruction
instruction = `LOOP endloop_${moduleInstanceRegisterMap[instanceName].instanceNum}, ${getPruRegister(byteOffset, instance.loopCountRegSize)}`;
label = 0;
Expand Down Expand Up @@ -770,8 +775,40 @@ function pushInstruction(instance, parentInstance) {
{
moduleInstanceRegisterMap[instanceName]["peakCycles"] += cycleBudgetMap[instanceName];
}
// Emit a universal <InstanceName>_start label immediately before this
// block's own instruction, as its addressable entry point for Flow
// Control jumps. Placed here (not at function entry) so it lands right
// above this instruction rather than above the whole prev-chain that
// was just recursively processed. Loop and group blocks are excluded —
// they emit their own specific start labels elsewhere (loop: between
// LDI and LOOP / startloop_N; group: <groupName>_start).
if (!instance.$groupContents) {
addToPruRegisterAllocationSummary(`${instanceName}_start`, "0", instance, instanceName, 0);
}
addToPruRegisterAllocationSummary(label, instruction, instance, instanceName, moduleInstanceRegisterMap[instanceName]["peakCycles"]);


// Emit a universal <InstanceName>_end label immediately after this
// block's own instruction, as an addressable "done with this block"
// target for Flow Control jumps (e.g. bailing out of a loop from
// inside a nested branch). Excluded for conditional (If/Else) blocks —
// a conditional has two forward addresses (_TRUE/_FALSE), not one, so
// a single _end here would be meaningless.
if (!instance.$groupContents && !(instance.T && instance.F)) {
addToPruRegisterAllocationSummary(`${instanceName}_end`, "0", instance, instanceName, 0);
}

// Loop blocks get their own <LoopName>_end at the loop's actual exit
// point — right after endloop_N (finite loops) or right after the
// infinite QBA back-edge (infinite loops, i.e. the plain fallthrough
// point past the whole loop subtree). This is a NEW, exposed label
// distinct from the internal endloop_N/startloop_N hardware labels,
// so it can be selected in Flow Control's dropdown as an early-exit
// target (e.g. jump here from inside the loop body to bail out before
// the counter/condition naturally ends the loop).
if (instance.$groupContents && 'infiniteLoop' in instance) {
addToPruRegisterAllocationSummary(`${instanceName}_end`, "0", instance, instanceName, 0);
}

return maxBytesUsed;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,84 @@
const FLOW_CONTROL_MODULE = "/pru_blocks/program_control/flow_control_block";
const CONDITIONAL_MODULE = "/pru_blocks/program_control/conditional_block";

/**
* Walks forward from a T/F branch head via BOTH "next" (control flow) and
* "output1" (data flow fanout) connections — matching the reachability
* model already used elsewhere for branch chains (collectBranchChain in
* pru_register_allocator.js) and cycle detection (detectControlFlowCycle
* in pru_blocks_static_module.syscfg.js). A branch head with no outgoing
* "next" of its own can still be "handled" if a data consumer of its
* output eventually reaches a Flow Control block via ITS OWN "next" chain
* (e.g. Memory_Access -> Arithmetic.input1 (data) -> Arithmetic.next ->
* Flow Control). Only a genuine dead end (no next, no output1 consumers,
* and not a Flow Control block itself) on every reachable path is an error.
* If the chain reaches a nested conditional block, both of ITS T and F
* branches must recursively terminate the same way.
* Returns an error message, or null if the branch terminates correctly.
*/
function branchTerminates(headInst, ownerName, branchLabel) {
const visited = new Set();

function walk(current) {
if (!current || !current.$name || visited.has(current.$name)) {
return true; // Already visited (or a cycle, reported separately) — treat as handled
}
visited.add(current.$name);

// $module is a live object reference on the instance; round-trip
// through JSON to get the plain module path string for comparison
// (matches the pattern used elsewhere in this codebase, e.g.
// crc_block.syscfg.js).
const currentModulePath = JSON.parse(JSON.stringify(current)).$module;

if (currentModulePath === FLOW_CONTROL_MODULE) {
return true; // This path terminates correctly
}

if (currentModulePath === CONDITIONAL_MODULE) {
const trueHead = current.T?.[0]?.inst;
const falseHead = current.F?.[0]?.inst;
if (!trueHead || !falseHead) {
// Nested conditional with an unconnected branch is caught by
// its own validate() call; don't double-report here.
return true;
}
// Both of the nested conditional's own branches must terminate.
return walk(trueHead) && walk(falseHead);
}

const nextInst = current.next?.[0]?.inst;
const outputConsumers = current.output1 ? current.output1.map(c => c?.inst).filter(Boolean) : [];

if (!nextInst && outputConsumers.length === 0) {
return false; // Genuine dead end, never reached a Flow Control block
}

let handled = false;
if (nextInst) {
handled = walk(nextInst) || handled;
}
for (const consumer of outputConsumers) {
handled = walk(consumer) || handled;
}
return handled;
}

const terminatesOk = walk(headInst);
if (terminatesOk) {
return null;
}
return `${ownerName}'s ${branchLabel} branch does not reach a Flow Control block on any path. Without one, execution falls through into the other branch. Add a Flow Control block at the end of this branch (directly, or via a data-consuming block's own control-flow chain).`;
}

function validate(inst, report) {
for(let iterator = 1; iterator <= inst["numOfInputPorts"]; iterator++)
{
if(inst["input"+iterator.toString()].length == 0)
{
report.logWarning("input"+iterator.toString()+" port not connected to output port",inst)
report.logWarning("input"+iterator.toString()+" port not connected to output port",inst)
}
}
}
//verifying if true port is connected true/false port(conditional input) are not
if((inst["T"].length == 0))
{
Expand All @@ -16,6 +89,23 @@ function validate(inst, report) {
{
report.logWarning("f_next port is not connected",inst);
}

// Any branch that IS connected must terminate in a Flow Control block,
// otherwise it falls through into the other branch's code.
const trueHead = inst.T?.[0]?.inst;
if (trueHead) {
const err = branchTerminates(trueHead, inst.$name, "T");
if (err) {
report.logError(err, inst, "T");
}
}
const falseHead = inst.F?.[0]?.inst;
if (falseHead) {
const err = branchTerminates(falseHead, inst.$name, "F");
if (err) {
report.logError(err, inst, "F");
}
}
}

function getAIContext() {
Expand Down Expand Up @@ -85,8 +175,40 @@ scripting.connect(if_else1, "F", false_path_block, "prev");

// Connect control flow input
scripting.connect(prev_block, "next", if_else1, "prev");

// REQUIRED: both true_path_block's and false_path_block's chains must each
// end in a Flow Control block, or validation fails. Example (standalone
// If/Else, NOT inside a Loop block):
const flow_control_block = scripting.addModule("/pru_blocks/program_control/flow_control_block", {}, false);
const flow_true = flow_control_block.addInstance();
flow_true.$name = "Flow_Control_True_End";
flow_true.jumpTarget = "JMP"; // or "HALT", or any real "<blockName>_start"/"_end" target to jump elsewhere
scripting.connect(true_path_block, "next", flow_true, "prev");

const flow_false = flow_control_block.addInstance();
flow_false.$name = "Flow_Control_False_End";
flow_false.jumpTarget = "JMP";
scripting.connect(false_path_block, "next", flow_false, "prev");
\\\`\\\`\\\`

**If the If/Else block is inside a Loop block's \\\`$groupContents\\\`**, "JMP"/"HALT" would exit the loop
entirely (or halt the PRU) instead of continuing the loop — jump to the loop's own \\\`_start\\\`
target instead so the loop keeps iterating:
\\\`\\\`\\\`javascript
// if_else1 is one of the instances inside loop_block1.$groupContents
const flow_true = flow_control_block.addInstance();
flow_true.$name = "Flow_Control_True_End";
flow_true.jumpTarget = "Loop_0_start"; // matches loop_block1.$name + "_start" — continues the loop
scripting.connect(true_path_block, "next", flow_true, "prev");

const flow_false = flow_control_block.addInstance();
flow_false.$name = "Flow_Control_False_End";
flow_false.jumpTarget = "Loop_0_start";
scripting.connect(false_path_block, "next", flow_false, "prev");
\\\`\\\`\\\`
Jumping to the loop's \\\`_start\\\` (not the loop's \\\`_end\\\`) is what resumes normal looping — \\\`_end\\\`
is the dedicated early-exit/break target, for when you want to abandon the loop instead of continuing it.

### Important Notes

1. **Two Inputs Required**: Both input1 and input2 must be connected for comparison.
Expand All @@ -99,7 +221,11 @@ scripting.connect(prev_block, "next", if_else1, "prev");

5. **Port Names**: True path uses "T" port, False path uses "F" port (displayed as t_next/f_next).

6. **Terminate Each Branch with a Flow Control Block**: The code generator places the FALSE path immediately after the branch instruction, with the TRUE path at the branch target label. If the FALSE path has no explicit terminator, execution falls through into the TRUE path — causing both branches to execute regardless of the condition. Always end each branch (T and F) with a Flow Control block (HALT or END) to prevent this fall-through.
6. **Every Connected Branch MUST Terminate in a Flow Control Block**: The code generator places the FALSE path immediately after the branch instruction, with the TRUE path at the branch target label. If a connected branch (T or F) has no explicit terminator, execution falls through into the other branch — causing both to execute regardless of the condition. This is now enforced by SysConfig validation (an error, not a warning) — any connected T or F branch that doesn't end in a Flow Control block will fail validation. A branch left entirely unconnected is still fine (nothing happens on that path).

7. **Where the Flow Control Block Should Jump To Depends On Context**:
- **Standalone If/Else** (not inside a Loop block): jump to \\\`JMP\\\` (SysConfig Generated End), \\\`HALT\\\`, or any other block's \\\`_start\\\`/\\\`_end\\\` target.
- **If/Else nested inside a Loop block**: jump to that Loop's own \\\`<LoopName>_start\\\` target, so execution resumes the loop instead of exiting it. Using \\\`JMP\\\`/\\\`HALT\\\` here ends the whole program early instead of continuing the loop — only do that if that's actually the intent (e.g. an early-exit condition that should stop everything, not just this iteration). To break out of the loop early without ending the whole program, jump to the Loop's \\\`<LoopName>_end\\\` target instead.
`;
}

Expand Down Expand Up @@ -132,12 +258,14 @@ Implements conditional logic (IF/ELSE statements) to control program flow based

**Generated Assembly**:
- ; Example: Greater Than Input2 (QBGT)
- QBGT TRUE_LABEL, input1_reg, input2_reg ; Branch if input1 > input2 (1 cycle)
- FalseBranchHead_start:
- QBGT If_Else_0_TRUE, input1_reg, input2_reg ; Branch if input1 > input2 (1 cycle)
- ; FALSE path code here
- QBA END_LABEL ; Jump to end
- TRUE_LABEL:
- ; FALSE branch MUST end in a Flow Control block (e.g. JMP sysconfig_generated_end)
- If_Else_0_TRUE:
- TrueBranchHead_start:
- ; TRUE path code here
- END_LABEL:
- ; TRUE branch MUST end in a Flow Control block

**PRU Branch Instructions**:
- **QBGT**: Quick Branch if Greater Than (unsigned comparison)
Expand Down Expand Up @@ -170,7 +298,11 @@ Implements conditional logic (IF/ELSE statements) to control program flow based
- The conditional check happens instantly (1 cycle)
- Code on both branches is generated, only one path executes at runtime
- This block does not produce an output value - it only controls flow
- **Always terminate each branch (T and F) with a Flow Control block**: The FALSE path falls through to the TRUE path in the generated assembly unless explicitly stopped. Without a terminator on the FALSE branch, both branches execute sequentially regardless of the condition result.
- **Any connected branch (T or F) MUST terminate in a Flow Control block**: The FALSE path falls through to the TRUE path in the generated assembly unless explicitly stopped, and vice versa. SysConfig validation now enforces this as an error — connect a Flow Control block at the end of every connected branch. Leaving a branch entirely unconnected is fine; only connected-but-unterminated branches are rejected.
- **What that Flow Control block should jump to depends on where the If/Else block lives**:
- **Standalone If/Else** (not inside a Loop block): jump to Sysconfig Generated End, Halt, or any other block's \`_start\`/\`_end\` target.
- **If/Else nested inside a Loop block**: jump to that Loop's \`<LoopName>_start\` target so execution resumes the loop instead of exiting the whole program. Jumping to Sysconfig Generated End or Halt from inside a loop's branch ends the entire program early rather than just this iteration — only intentional if that's really the goal. To break out of the loop early (without ending the whole program), jump to the Loop's \`<LoopName>_end\` target instead.
- Every block, including this one and every block on either branch, has an addressable \`<blockName>_start\` entry point that a Flow Control block elsewhere in the design can jump to (e.g. to re-run this comparison, or to re-enter a Loop block from inside a branch).

### Terminology
- **Conditional branching**: Changing program flow based on a condition
Expand Down
Loading
Loading