Skip to content

Application-specific blocks: encoder initiators, IPC and fieldbus framing - #25

Open
pratheesh wants to merge 125 commits into
TexasInstruments:mainfrom
pratheesh:feat/application-blocks
Open

pratheesh wants to merge 125 commits into
TexasInstruments:mainfrom
pratheesh:feat/application-blocks

Conversation

@pratheesh

Copy link
Copy Markdown

Stacked pull request. Depends on #22, #23, #24. GitHub cannot base a pull request on another fork branch, so until they merge the diff here also contains their commits. The 22 commits of this pull request alone: feat/core-blocks...feat/application-blocks. It will be rebased onto main as the earlier ones merge.

Summary

Builds on the core-blocks pull request. Adds 7 blocks in application_specific, their registrations, tests and the
test 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 no
    CRC, 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_CYCLES is reserved and there is no
    timeout status.
  • ipc_shared_mem_block: shared-memory word write/read between cores (4 cycles); registers itself as the cross-core
    writer 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 are
    added 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 the ssi, bissc and endat device 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 .usect emission and its registration as
    an XFR2SPIN cross-core writer.
  • common/block_control_flow_data.js: control-flow classification of the 7 blocks.

Other files

  • scripts/sysconfig_arm.sh with scripts/fixtures/shared_resource_lock/*.syscfg: a shell script that runs the
    SysConfig 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-block gmake all / gmake sim
    projects used by tests/test_clock_idle.py and tests/test_device_model.py; the tests copy them under examples/
    for the run.
  • tests/test_iep_block.py covers iep_block and scheduler_block of the core-blocks pull request: c26 is mapped to
    the 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 branch 544d5ea). The gmake sim chain tests also need gmake (the CCS one works) and a
repository-root imports.mak.

python -m pytest tests scripts/sim -q -p no:cacheprovider
for t in scripts/sim/test_*.js scripts/test_*.js; do node "$t"; done
node scripts/check_syscfg_arity.js
node scripts/verify_ssi_build.js; node scripts/verify_endat_build.js; node scripts/verify_modbus_build.js
  • with SysConfig, clpru and PRU_SIMULATOR_PATH, without imports.mak: 2718 passed, 0 failed, 82 skipped;
  • without PRU_SIMULATOR_PATH: 2317 passed, 0 failed, 483 skipped;
  • with PRU_SIMULATOR_PATH, gmake on PATH and a repository-root imports.mak: 2754 passed, 0 failed, 65 skipped;
  • 21 node test scripts pass; check_syscfg_arity.js: 76 modules, 0 unexpected failures; the three clpru build checks
    pass.

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; the gmake sim chain tests
(no imports.mak); 2 CTPPR0 cases because pru-simulator main has no PRU_CTRL model.

With a repository-root imports.mak and gmake the chain tests run; the 65 skips that remain are the device models
pru-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.py and
test_clock_idle.py all pass: 120 passed, 0 skipped.

SysConfig Sanity Tests: the eight contexts of make syscfg-tests-am243x, syscfg-tests-am64x and
syscfg-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

  • None of these blocks is verified on silicon.
  • EnDat, SSI and BiSS-C closed-loop tests need pru-simulator device models that main does not ship.
  • Each block has a hidden blockMaturity field (see the core-blocks pull request); IO-Link is metadata-only.

🤖 Generated with Claude Code

…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.
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.
…hey do not shadow the pru-simulator's; firmware test fixture; GUI simulator register tests; neutral wording
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants