Skip to content

User documentation and generated block reference - #26

Open
pratheesh wants to merge 145 commits into
TexasInstruments:mainfrom
pratheesh:docs/new-blocks-reference
Open

pratheesh wants to merge 145 commits into
TexasInstruments:mainfrom
pratheesh:docs/new-blocks-reference

Conversation

@pratheesh

Copy link
Copy Markdown

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

Summary

Builds on the application-blocks pull request. Adds user documentation for the blocks and tools of the previous four
pull requests, a generated block reference, README additions, and the tests that check documentation against the
code.

What changes

  • docs/** user guides (input/output, data handling, program control, utilities, simulation: using the simulator,
    device bindings, Simulation Settings) and docs/readme.md.
  • docs/block-reference/**, generated by node build_block_docs.js: a user guide and a design page for each of the
    76 blocks, an index and a portability page. Nothing else is generated (no test-report or test-spec pages).
    tests/test_block_docs_fresh.py regenerates into a temporary directory and fails if the committed pages differ,
    if a relative link is dead or if a line has trailing whitespace.
  • README.md: PRU GPI is 3 cycles (was 1), and a "Running the tests" section (commands and the SYSCFG_PATH,
    CGT_TI_PRU_PATH and PRU_SIMULATOR_PATH variables).
  • docs/readme.md: the "Configure Constant Table", "PRU LBCO" and "PRU SBCO" rows are removed; no such blocks exist
    in the repository. The PRU GPI row says 3 cycles, as the block does.
  • SPI pages (pru_spi_read, pru_spi_write, pru_spi_transfer): Data Setup Time is counted inside the SCLK half it
    precedes, so it adds no program memory; a maxed controller block links to 2,888-2,904 bytes of .text (a one-block
    design with a Load Constant and a PRU GPO, across the three blocks and four SPI modes) and six of them to
    17,228-17,300 bytes, measured with clpru 2.3.3.
  • The simulation pages state that the in-tool simulator counts instruction cycles but is not cycle-accurate, and that
    gmake sim runs the generated firmware on TI pru-simulator.
  • Tests that check documented numbers against the code: test_delay_cycles.py, test_three_phase_scheduling.py,
    test_spi_cs_idle_prologue.py, test_pwm_jitter_doc.py, test_device_capability_gating.py,
    test_device_model_companion_conflicts.py.

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).

node build_block_docs.js          # 76/76 blocks x 2 pages + index + portability; git diff stays empty
python -m pytest tests scripts/sim -q -p no:cacheprovider
for t in scripts/sim/test_*.js scripts/test_*.js; do node "$t"; done
  • whole tree, with SysConfig, clpru and PRU_SIMULATOR_PATH, without imports.mak: 2788 passed, 0 failed,
    118 skipped;
  • without PRU_SIMULATOR_PATH: 2349 passed, 0 failed, 557 skipped;
  • with PRU_SIMULATOR_PATH, gmake on PATH and a repository-root imports.mak: 2860 passed, 0 failed, 65 skipped
    (the 65 are the device models and the PRU_CTRL model pru-simulator main does not ship, as in the application-blocks
    pull request); this includes every case of test_spi_cs_idle_prologue.py;
  • 21 node test scripts pass;
  • every TI example (39 examples/**/ti-pru-cgt/makefile projects) builds with gmake all (SysConfig 1.28, clpru
    2.3.3, DEVICE set to the project's EXPECTED_DEVICE): 39 of 39.
  • test_three_phase_scheduling.py maps c26 to the simulator's IEP model and runs the IEP at the core clock (the
    documented edge offsets are in cycles = IEP counts): 34 passed on pru-simulator main.

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. The block reference shows the Title Case display names of the blocks.

Known limitations

  • The documentation is not verified against silicon; timing figures come from instruction counts and TI pru-simulator.

🤖 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.
…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.
…on and recognize NEXT/PREV sequence ports

portsOf() called ports() with a proxy whose properties convert to empty
strings, so loops bounded by numOfInputPorts/numOfOutputPorts produced no data
ports, and inferExecution() only recognised IO_NEXT/IO_PREV. Blocks such as SPI
Read therefore listed zero data ports and an unclassified execution model.
Evaluate ports() against the module's default configuration, treat NEXT/PREV
like IO_NEXT/IO_PREV, and regenerate the reference.
The Data Setup Time setting is a 0-10000 ns value converted to PRU cycles, but
the parameter tables still listed it as 0-100 PRU cycles and the setup/hold
section called it a cycle count. Describe it in nanoseconds like CS Setup and
CS Hold.
…blocks implement

The MAC guide listed a Signed/Unsigned mode option and a 64-bit accumulator; the
block has no mode option, selects multiply-only mode and adds the accumulator in
the ALU on the lower 32 bits. The PID guide gave Kd a default of 0.05, listed a
State Variable Base setting and 0-16 gain ranges that do not exist, and applied
Ki to the error before integrating; the macro integrates the raw error and
applies Ki to the integral. Document the actual defaults, the per-instance state
symbol and the formulas.
The guide gave a fixed 8-cycle period for every bit; the block takes 8 cycles
for a sampled 1 and 7 for a sampled 0, as its description says.
…ult use

The tests fall back to sysconfig_1.28.0 under TOOLS_PATH, while imports.mak.default
sets SYSCFG_PATH to sysconfig_1.26.2; say so and tell users to point
SYSCFG_PATH at the same installation.
…rovide

The Park/Clarke guide promised three phase outputs in inverse mode; the block has
two output ports, so the guide now describes the two outputs and how to derive the
third phase. The SDFM guide described a configurable oversampling ratio and a
register offset the block does not use; the block reads one result register per
channel from an assumed base address.
SysConfig now rejects an empty variable name and one that is not a plain
identifier; say so in the parameter table.
Refresh the generated pages for the block option, port and description changes
from the lower layers.
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