Repository navigation
Conversation
…module Register allocator: - release a block's registers after its last data consumer, including values produced outside a loop and read inside it - keep pre-initialized loop values in their own register and support explicit loop accumulator bindings and exported loop results - route each consumer input from the output port it is wired to and refuse connections to ports that own no register - record register-pool exhaustion per block so validation can name it - reserve user-typed registers that blocks emit verbatim and report overlapping ones - give multi-result macros one destination register per output - keep 64-bit (two register) sources and outputs Static module and templates: - report allocation failures, pinned-register conflicts, unconnected required inputs, invalid port connections, loop/container boundary violations and macro arity mismatches at validation time - program CTPPR0 so c28 points at shared RAM when a block uses c28, and drive SPI controller CS pins to their idle level, at every entry into generated code - warn in the GUI when a GPO/GPI pad is shared or a GPO has two drivers - gate blocks that need a hardware widget on the device capability table - define TEMP_REG3/TEMP_REG4 (R26/R27) for macros - expand multi-line instructions in the simulator path New shared scripts: pru_reserved_names.js (clpru reserved identifiers), pru_spi_timing.js (SPI delay limits and IRAM budget), pru_device_caps.js (per-SoC hardware widgets), block_control_flow_data.js.
- CRC16/CRC32: scale the table index to the entry size before the load (the byte offset does not fit the LBBO offset operand); 13 cycles - Access Lookup Table: scale the index for ushort/uint tables (7 cycles) - PRU GPI: build the bit mask in the result register so pins above 7 work; 3 cycles, 4-byte output - PRU SPI Read/Transfer: clear the result register before sampling so short packets are zero-extended; reserve the whole register for 17-24 bit packets; one extra overhead cycle - PRU SPI Read/Write/Transfer: refuse CS/data delays above 256 cycles, warn when the SPI blocks exceed the PRU IRAM budget - If/Else: an unconnected true/false branch is an error (the generated code would reference an undefined label) - Load Constant: optional explicit output width - Loop: reject a hardware LOOP nested in a finite loop body; add exported result blocks and accumulator bindings - Delay: document the exact cycle count and the nesting restriction - Memory Access: warn when two blocks share one auto-created Memory Variable - CRC, Lookup Table, Memory Variable, Group, If/Else: reject symbols that clash with clpru reserved identifiers - PRU GPI/GPO and SPI blocks: declare the pins they use (getPinUsage)
- tests/test_syscfg_add_instance.py: every block module adds an instance through the SysConfig CLI (skipped when SysConfig is not installed) - tests/test_fail_closed_gating.py: the device-capability gate fails closed on every degraded path - tests/test_shared_memvar_rename.py, tests/test_conditional_unconnected_branch.py - scripts/static_module_validate_runner.js, scripts/block_validate_runner.js, scripts/shared_memvar_runner.js: run block validators under node
… test The register allocator now releases a register once its last reader has run, so main.asm can no longer rely on a released register still holding its value. Name the registers once in main.asm, and keep the received frame live on the receiving core by reading it into an otherwise unused block output. Add a firmware test that runs the example on the simulator for both boards and both cores, checking the stored data byte and the CRC match/mismatch result.
…es not exist; neutral test wording
The MODE1 LSB loop advanced its bit counter in the low half, where the delay formulas assume none, so SCLK ran one cycle short high and one cycle long low. The MODE3 LSB loop sampled a 1 bit one instruction slower than a 0 bit, so its high half grew by a cycle on every 1. Both variants now count in the half the MSB variant counts in, and both sample paths take two instructions.
The Memory Variable block reserves buffers in .udmem (data RAM) and .usmem (shared RAM). With no rule for either section clpru placed them in instruction RAM after .text. Each example linker file now places .udmem in the data RAM its .bss uses and .usmem in PRU_SHAREDMEM, and a test checks every tracked linker file.
Behavioural peripheral models (perif/, core/device_model.py, pru_io/), the in-tool device bus and device models used by the Simulation Settings GPI "Attached Device Model" mode and the Device Model block, the device-binding manifest emitted into pru_syscfg.asm, and the TI pru-simulator run path (scripts/sim: ELF co-simulation, bound-device runner, Run on TI pru-simulator launcher). The static module gains the device-mode GPI fields, the device-bus preview, the pad check between attached devices and PRU outputs, and the TI pru-simulator result banner.
Tests for the device models and bindings, the Run on TI pru-simulator launcher, and regression tests of the shared allocator and existing blocks that run the generated firmware on TI pru-simulator (CRC/LUT index scaling, SPI zero-extension, loop nesting, register-pool exhaustion, loop-carried accumulators, load-constant width). A repository-level conftest reports tests that need a TI pru-simulator checkout as skipped, with the reason, when none is available.
…ck registrations only for blocks present
…whose device model the simulator lacks
…hey do not shadow the pru-simulator's; firmware test fixture; GUI simulator register tests; neutral wording
…ware that programs the IEP through c26
validate() passed a copy of the instance, with the companion pins defaulted from the bound block, to validateDeviceInput, which logged errors against that copy. SysConfig's report.logError throws "Unknown instance" for any object it does not own, so a device-mode GPI with a refused setting aborted validation, code generation and serialization. The effective pins are now passed separately and errors are logged against the real instance.
SysConfig display names are Title Case, at most 36 characters. The Device Model block now takes each field's display name from displayTitle() in the device model table: an explicit title where the label is a sentence, else the label converted word by word. The label itself stays the noun used in messages. The live web view settings get Title Case names and the long tooltip moves to the long description.
…dths in every mode and bit order Each read, write and transfer macro variant is built with the block's own delay components, run on TI pru-simulator against a model peripheral, and every SCLK phase inside the packet is checked, along with the word read.
New blocks with their palette entries on AM243x, AM64x and AM261x, and the shared code they need: per-block register pins in the allocator and the fixed-window reservations of XFR2PSI (R1-R5) and SUM32 accumulate (R2-R9), the global validators of the broadside blocks (BSWAP, Stitch FIFO, SUM32, XFR2TR, XFR2SPIN, XFR2PSI, BS RAM, FDB generic RAM), the PID state reservation and the SPI ADC sequencer CS idle level in the static module, and the regenerated block control-flow table. The new blocks use TI's typed input32/output32 data ports so they connect to the existing blocks.
…ster The block declared two output ports but its macro had a single destination that received an 8-byte load, so anything wired to the second port read a register the macro never wrote. Both ports now get their own destination (D/Alpha in OUT1, Q/Beta in OUT2) and the macro loads each into its register.
Clear Event After Wait defaulted to on but only loaded R0 with the input bit and never acknowledged anything, overwriting R0 on the way. The option now defaults to off, the validator rejects it with an explanation, and the macro no longer touches R0. The signal value (system event + 16 = strobe bit plus channel) is documented and covered.
…ary registers The macro always programmed and polled compare 0 registers while the wait tested the selected compare's status bit, so one-shot compares 1..15 waited forever; it now derives the compare register offset, CMP_CFG enable bit and status clear mask from the compare index. The capture sub-function emitted nothing and is now rejected by the validator. Scratch values use the temporary registers instead of R0-R2.
…very task id timerSource external was selectable but the generated dispatch always reads the IEP counter, and only task1_id was range checked. The validator now refuses anything but the IEP timer and applies the 0..31 or 255 rule to all four task ids.
…egister The delay loop counted down in R0, which the register allocator can assign to a live value. It now uses the temporary register reserved for block macros.
The runner accepted --elf on its own, but the ELF loader only records data and symbols and the simulator executes assembly text, so the run failed later with a no-instructions error. The missing-instructions check now applies whether or not an ELF is given and says what --elf provides.
… numbers Pin strings went through parseInt and only NaN was rejected, so "3junk" was read as pin 3 and "-1" or "32" were accepted as pins. A pin must now be a non-negative integer or a string of digits within 0..31; anything else is reported as an unparseable pin.
The macro wrote GPCFG0 and the PRU0_ENDAT_TXCFG/RXCFG/CHn_CFG0 registers whatever core the design targeted, so an instance on PRU1 configured PRU0's peripheral and left its own unconfigured. The opCode now carries the core (m_iolink_master_pru0 / m_iolink_master_pru1) and the macro picks GPCFG0/1 and the PRUn_ENDAT_* aliases from it, for the transmit and the receive setup. A simulator test builds the macro for both cores and checks that the frame leaves the channel of the core that ran it.
The transmit path built the frame in an 8-bit register slice: shifting left by one dropped data bit 7, the stop bit could not be placed, and the second FIFO byte was zero. It also sent 10-bit 8N1 characters, while IEC 61131-9 uses start, 8 data bits LSB first, even parity and stop. The macro now builds each character in a full register (start 0, data, even parity, stop 1), pushes it as two FIFO bytes and starts it with its own GO, once per payload byte (txBytes 1..8, the two 32-bit inputs). The CHn_CFG0 write sets the TX and RX frame sizes to 11 bits. Peripheral mode is selected before the global reinit so the reinit flushes the FIFO bytes pushed by the earlier R30 writes, and R31 commands are written as byte fields because a read-modify-write of R31 re-sends status bits as GO/reinit commands (channel 2 never transmitted with a two-byte FIFO). Simulator tests decode the transmitted characters for all three channels, both cores and up to eight bytes.
…g checks The receive path did not decode a UART character: it treated each valid sample as a whole byte and looped once per byte; it wrote the 10-bit receive frame size into the transmit field (CHn_CFG0 bits 15:11) instead of bits 27:16; it read the data byte from R30, which is the output register; it tried to clear the valid flag by toggling the receive enable in R30 instead of writing a one to the flag in R31; and it ignored the parity bit. The receiver now runs at 8x oversampling and is re-armed for every character. For each of the 11 bits of a character it waits for the channel's valid flag, reads the middle sample from the channel's R31 byte, clears the flag by writing its R31 bit, and shifts the bit in. The start, stop and parity bits are checked and reported in a new status output (bit 0 parity error, bit 1 framing error); the data bytes are returned in output1 with the first byte most significant. rxBytes is limited to 0..4, the width of that output, and the RX clock divider is passed as a new macro argument. Simulator tests drive the channel with an IO-Link device model for both cores and all channels, including wrong parity and stop bits, and a SysConfig-generated design is checked to call the macro with matching arguments and to assemble.
The turnaround used a fixed 174-iteration inner loop for every COM mode, and that count was not even one bit period at COM3: the delay was about 7 us whatever the rate, instead of TURN_BITS bit periods (21.7 us at COM3, 1 ms at COM1). The inner loop count is now derived from the TX clock divider: one bit period is (CLK_DIV + 1) * 25 / 48 two-cycle iterations at the 200 MHz PRU clock. A simulator test measures the time from the end of the last transmitted character to the receiver being armed for COM1, COM2 and COM3.
Validation only required singleTurnBits + multiTurnBits <= totalWidth, so a 25-bit payload in a 25-bit frame with parity enabled was accepted: the macro takes the parity bit from the low end of the frame and shifts it out before the Gray decode, leaving 24 payload bits in a 25-bit configuration. The payload must now fit in totalWidth minus one bit for an enabled parity bit and one for an enabled error bit. The error names the payload bits available.
The macro wrote the parity and error-bit result only into TEMP_REG2.b0, a scratch register the register allocator can hand to any following block, while the block declared the captured frame as its only output. Connected blocks had no value with which to tell a bad reading from a good one. The block now has a second output, STATUS (0 = ok, 1 = parity error, 4 = error bit active, 5 = both), allocated like the frame register and passed to the macro as its second argument; the macro writes the result there instead of into a temporary register. The generated-firmware tests read the STATUS register from the generated call and check it for a good frame and for frames with a wrong parity bit, an active error flag and both.
The IPC block checked the variable name only against the clpru reserved names, and that helper (like the memory reservation and the undefined-symbol check) skips an empty value. A cleared Variable Name therefore passed validation and reached the assembler as an empty macro operand. The block now reports an error unless the name is a non-empty identifier (letter or underscore, then letters, digits and underscores), the same rule the memory variable block applies to its label.
The Park/Clarke macro now loads D into OUT1 and Q into OUT2 with a load each, so its default expansion is 100 instructions instead of 99.
…lders buildDeviceBindings resolves the effective companion pins through devicePinDefaults; the test that evaluates the extracted builders must define it.
READER_BLOCKS listed only the BiSS-C and SSI initiators although the EnDat encoder kind already has a clock-driver rule for the EnDat initiator. A bound EnDat encoder therefore had no reader record in the manifest and no reader in the Device Model display even when an EnDat initiator read its data pin.
…ecks The frame-length guards were written as if QBLE/QBGT compared the register against the operand, but the PRU compares the operand against the register. As generated, every frame of three or more bytes was reported as too short and the buffer store was skipped for the first 254 bytes. Use QBGE and QBLT so frames shorter than four bytes report status 3 and bytes are stored while the buffer has room.
The CRC lookup used the raw table index (0..255) as the LBBO byte offset although the reflected CRC-16 table has 2-byte entries, so any non-zero index read the wrong entry and valid frames were reported as CRC errors. Double the index into a 16-bit offset before the lookup.
…register Receive setup wrote all four bytes of the ENDAT channel CFG0 register, so a paired UART transmitter on the same channel lost its frame size and other transmit settings. Write only bytes 2-3 (receive frame size and bit swap), as the UART configuration block does.
The receiver programmed and shifted in 10-bit characters (start, 8 data, stop) while the silence threshold is computed for 11-bit Modbus characters. With the parity bit (8E1/8O1) or second stop bit (8N2) left unread, the next character started one bit early and the data drifted out of alignment. Program an 11-bit frame size and align the shifted-in character to it; the extra bit after the data is consumed and not decoded (parity is not checked).
t1Clocks other than the two-pulse lead-in produced a command the encoder rejects, and trecCycles between 500 and 999 let the block return before the 10 us recovery time (the loop takes two cycles per iteration), yet both only logged a warning. Report them as errors. The tests that deliberately put such values on the wire now relax that single check in a scratch copy of the block.
…dget The cycle estimate charged the start-bit hunt as a single clock pulse, but a late or absent encoder makes the macro clock up to timeoutCycles pulses before it gives up, so the reported budget was about 99 pulses short in that case. Charge timeoutCycles pulses and update the default to match.
The gateway macro defined sg_tx_turn, sg_tx_done and sg_rx_phase as plain labels, so a second instance in one design defined each symbol twice and the assembler rejected the program. Use '?' local labels like the other blocks.
When a transmit command was pending, the macro loaded MB_SLOT_BYTES bytes starting at TEMP_REG3. That overwrote the mailbox base kept in TEMP_REG3, so the read pointer was stored at an address taken from payload data, and with the default 32-byte slot the load also reached scratch registers and R30/R31. Nothing uses the payload yet because UART transmit is not implemented, so drop the load; the read pointer is now stored at base + 4 and R30/R31 are untouched.
The receive publish stored MB_SLOT_BYTES (up to 32) bytes starting at the single 32-bit status register, so every publish copied whatever registers the allocator had placed after it into the host's mailbox slot. Store the four status bytes only.
The compiled PRU firmware decodes transition 0 -> 1 as forward motion (B rises before A). The Python encoder and decoder models, along with the in-tool JavaScript simulation phase table, now consistently follow this B-leading convention. Forward motion advances positive count across both simulation environments and agrees with firmware cosimulation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds on the core-blocks pull request. Adds 7 blocks in
application_specific, their registrations, tests and thetest projects they need.
New blocks
biss_c_initiator_block: shift-register BiSS-C read. It clocks MA (idles high) and shifts in SLO bits; there is noCRC, start/ACK or timeout handling and the clock rate is not configurable. A bit takes 8 cycles when the sampled
bit is 1 and 7 when it is 0 (clpru ELF on TI pru-simulator,
tests/test_biss_c_initiator.py).endat_initiator_block: EnDat 2.1/2.2 position read with CRC-5.ssi_initiator_block: SSI read with Gray decode and optional parity;TIMEOUT_CYCLESis reserved and there is notimeout status.
ipc_shared_mem_block: shared-memory word write/read between cores (4 cycles); registers itself as the cross-corewriter for the XFR2SPIN checks of the core-blocks pull request.
industrial_serial_gateway_block,modbus_rtu_framer,iolink_master_block: framing over the UART blocks(
uart_config,uart_tx_op,uart_rx_op). These are checked at assembly level and with string-level tests only.IO-Link sends byte 0 of the M-sequence only; multi-byte M-sequences are not implemented.
There is no Tamagawa block in this pull request, so it does not overlap TI's
examples/tamagawa_single_channel.Changes to TI-owned shared files
soc/pru_blocks_am243x.syscfg.js,pru_blocks_am64x.syscfg.js,pru_blocks_am261x.syscfg.js: the 7 blocks areadded to the APPLICATION SPECIFIC palette of the PRU core list; the RTU/TX core list gets BiSS-C, EnDat and IPC
only. The same on all three SoCs.
common/device_model_table.js: reader and clock-driver rules for thessi,bisscandendatdevice kinds(new GUI validation on the existing Device Model block: a second block driving a pin that clocks an attached device
is an error).
common/pru_blocks_static_module.syscfg.js(+19 lines): the IPC block's.usectemission and its registration asan XFR2SPIN cross-core writer.
common/block_control_flow_data.js: control-flow classification of the 7 blocks.Other files
scripts/sysconfig_arm.shwithscripts/fixtures/shared_resource_lock/*.syscfg: a shell script that runs theSysConfig CLI on the XFR2SPIN lock fixtures. It needs a SysConfig CLI install (default
$HOME/ti/sysconfig_1.26.2)and is not called by any test; it was not run for this change.
scripts/verify_ssi_build.js,verify_endat_build.js,verify_modbus_build.js(clpru builds of the macros),scripts/test_headless_sim.js,scripts/test_iolink.js,scripts/sim/test_validator.js,scripts/sim/test_device_bindings.js.tests/fixtures/ssi_encoder_sim,tests/fixtures/ssi_device_model_sim: two one-blockgmake all/gmake simprojects used by
tests/test_clock_idle.pyandtests/test_device_model.py; the tests copy them underexamples/for the run.
tests/test_iep_block.pycoversiep_blockandscheduler_blockof the core-blocks pull request: c26 is mapped tothe IEP model for the run (pru-simulator main maps c26 to 0x00010000).
How it was tested
Tools: node; SysConfig 1.28 (
SYSCFG_PATH); TI PRU CGT 2.3.3 (CGT_TI_PRU_PATH); TI pru-simulator checkout(
PRU_SIMULATOR_PATH, main branch544d5ea). Thegmake simchain tests also need gmake (the CCS one works) and arepository-root
imports.mak.PRU_SIMULATOR_PATH, withoutimports.mak: 2718 passed, 0 failed, 82 skipped;PRU_SIMULATOR_PATH: 2317 passed, 0 failed, 483 skipped;PRU_SIMULATOR_PATH, gmake on PATH and a repository-rootimports.mak: 2754 passed, 0 failed, 65 skipped;check_syscfg_arity.js: 76 modules, 0 unexpected failures; the three clpru build checkspass.
Skips with every tool present except
imports.mak: device models not in pru-simulator main (ssi_encoder,bissc_device_model,endat_encoder,quadrature_encoder,uart_device,sd_modulator_device,bissline_device_model) in the device-binding, EnDat framing and device-model tests; thegmake simchain tests(no
imports.mak); 2 CTPPR0 cases because pru-simulator main has no PRU_CTRL model.With a repository-root
imports.makand gmake the chain tests run; the 65 skips that remain are the device modelspru-simulator main does not ship and the 2 CTPPR0 cases. On a pru-simulator revision that ships those device models
the chain tests of
test_device_model.py,test_device_input_driver.py,test_device_input_drivers.pyandtest_clock_idle.pyall pass: 120 passed, 0 skipped.SysConfig Sanity Tests: the eight contexts of
make syscfg-tests-am243x,syscfg-tests-am64xandsyscfg-tests-am261x, each run as the makefile runs it with SysConfig 1.27.0:Total 0 error(s)and no "failed"line in every context. Display names of the application blocks follow SysConfig's convention (Title Case, at most 36
characters; configuration names are unchanged).
Known limitations
blockMaturityfield (see the core-blocks pull request); IO-Link ismetadata-only.🤖 Generated with Claude Code