mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
docs: add programming reference module and associated tests
- Implemented to handle publication data for routed resources and integer timing/buffer examples. - Introduced functions for UART, I2C, timer, and I2S timing calculations, along with buffer budget calculations. - Added validation for programming specifications and constants extraction from source files. - Updated to integrate programming dependencies into the system reference collection. - Created comprehensive tests in to ensure the correctness of programming specifications and timing calculations.
This commit is contained in:
18 files changed
+2127
-5
No files matched your search
@@ -46,6 +46,24 @@ to software copy/CRC; its final Linux-ready wait has no firmware-local deadline.
|
||||
CRC is not authentication. Power isolation, qualified entropy, physical ratings
|
||||
and production APU codec support are not inferred from controller capabilities.
|
||||
|
||||
The cross-IP reference additionally covers register access conventions,
|
||||
transaction/credit/arbitration rules, DMA request and interrupt routing,
|
||||
resource coexistence, clock/divider examples and external-memory compatibility.
|
||||
Two usage appendices provide software/buffer budgets and terminology/document
|
||||
navigation. `programming_reference.py` collects the corresponding
|
||||
`system-reference.json.programming` records, verifies RTL/SDK selector parity
|
||||
and connection evidence, extracts interrupt/credit data, and calculates bounded
|
||||
timing and buffer examples. The pure timing calculations are cross-checked
|
||||
against the existing C helpers by `tests/test_publication_programming.py` in the
|
||||
Linux host environment. No MMIO or new firmware/RTL simulation is involved in
|
||||
those calculation checks.
|
||||
|
||||
The current native JPEG path is connected to master slot 6 but receives zero
|
||||
normal read/write credits in the reviewed crossbar. The publication now records
|
||||
that static integration limitation; it does not change RTL or promote standalone
|
||||
codec tests into an end-to-end SoC DMA result. Existing IP register/codec details
|
||||
remain available with this system-level qualification.
|
||||
|
||||
### Spacing and document flow
|
||||
|
||||
The [layout standard](datasheets/style.md) records the implemented page,
|
||||
|
||||
@@ -71,6 +71,8 @@ number is used to imply completion.
|
||||
|
||||
#include "performance-reference.typ"
|
||||
|
||||
#include "usage-appendices.typ"
|
||||
|
||||
= Document Control
|
||||
== Revision History
|
||||
#ds-table("revisions", [Document revision history],
|
||||
@@ -78,7 +80,7 @@ number is used to imply completion.
|
||||
(([2025-09-28],[0.1],[Create document],[Yuchi Miao]),
|
||||
([2025-12-02],[0.2],[Initial draft],[Yuchi Miao]),
|
||||
([2026-02-15],[0.3],[Initial release (historical document entry)],[Yuchi Miao]),
|
||||
([2026-09-06],[0.4 DRAFT],[Expand IP and system-use reference, configuration/support matrices, boot/recovery guidance and qualification boundaries; retain the gray/gold layout and reviewed snapshot.],[Yuchi Miao / ECOS Team])),
|
||||
([2026-09-06],[0.4 DRAFT],[Expand IP/system-use and cross-IP programming references, route/support matrices, calculation examples, usage appendices and qualification boundaries; retain the gray/gold layout and reviewed snapshot.],[Yuchi Miao / ECOS Team])),
|
||||
widths:(0.8fr,0.65fr,1.8fr,1fr),
|
||||
)
|
||||
The retained historical release entry does not establish silicon availability or qualify
|
||||
|
||||
@@ -70,6 +70,8 @@ width multiplied by clock rate as measured application bandwidth.
|
||||
#source-note("rtl/mini/integration/soc_topology.json", title:"Generated matrix source: data_master_policies")
|
||||
#pagebreak()
|
||||
|
||||
#include "bus-programming.typ"
|
||||
|
||||
=== Address Mapping
|
||||
All ranges below are inclusive. The SRAM range is resolved using the reference profile's
|
||||
32 KiB setting. Peripheral windows are address allocations; not every offset implements a
|
||||
@@ -125,6 +127,7 @@ from re-entering a restarted domain.
|
||||
qualification. No crystal-oscillator range or maximum core frequency is specified here.]
|
||||
|
||||
#include "operating-states.typ"
|
||||
#include "clock-programming.typ"
|
||||
|
||||
== Interrupt System
|
||||
The management interrupt vector contains 32 allocated positions. The following numbers are
|
||||
@@ -143,3 +146,4 @@ Claim/complete and priority rules are defined in the HP platform contract.
|
||||
#source-note("docs/ip/hp-platform.md", title:"HP PLIC, local interrupts and mailbox")
|
||||
|
||||
#include "system-management.typ"
|
||||
#include "register-programming.typ"
|
||||
@@ -0,0 +1,64 @@
|
||||
#import "../style.typ": *
|
||||
#let programming = data.system_reference.programming
|
||||
|
||||
=== Bus Transaction Rules and Arbitration <bus-programming>
|
||||
The usable transaction set is the intersection of the issuing master, fabric, adapters and
|
||||
destination. A 64-bit fabric does not make every target a 64-bit register interface. Keep
|
||||
device/MMIO accesses within the target's register contract even when a bus path accepts a wider request.
|
||||
|
||||
#ds-table("bus-path-rules",[Transaction rules by path in the reviewed implementation],
|
||||
([Path],[Admission / ordering],[Application constraint]),
|
||||
(([LP control fabric],[Aligned 1/2/4-byte beats; FIXED/INCR and legal 2/4/8/16-beat WRAP; at most 16 beats. One active transaction per master.],[Stay in one 4 KiB page and decoded target; locked transfers are rejected.]),
|
||||
([HP data crossbar],[Up to 8-byte beats; unlocked FIXED/INCR or legal WRAP lengths; checks the computed last address against the 4 KiB limit.],[Target and adapter limits still apply. The crossbar admission predicate alone is not a guarantee of arbitrary unaligned or 256-beat target transfers.]),
|
||||
([HP MMIO],[Downsized to AXI32 and crossed into the LP control path. Root write policy remains enforced.],[Use register width and side effects; do not infer system-wide exclusive/atomic support.]),
|
||||
([Memory adapters],[Preserve byte lanes, response identity and the supported target transaction contract across width/clock conversion.],[Observe each memory controller's burst, device/page and alignment restrictions.]),
|
||||
([APB4 register targets],[One register access with PSTRB/PSLVERR semantics defined by that IP.],[No APB burst assumption; unsupported offsets, strobes or busy writes can fail.])),
|
||||
widths:(0.85fr,1.8fr,1.5fr))
|
||||
|
||||
The LP fabric explicitly checks natural alignment and the start/end target. The current native
|
||||
crossbar's #code("burst_legal") predicate is different: it rejects #code("AxLOCK"), limits
|
||||
beat size and burst encoding, and checks its computed address span. It must not be treated as
|
||||
a complete implementation of every AXI4 feature. Portable software uses naturally aligned
|
||||
transfers and the narrower documented target limits. See @register-programming for MMIO exceptions.
|
||||
|
||||
==== Outstanding credits and identity
|
||||
#ds-table("master-credits",[Normal master credits extracted from the crossbar functions],
|
||||
([Slot / initiator],[Read credits],[Write credits],[Interpretation]),
|
||||
data.policies.map(p=>([#p.index / #code(p.name)],str(programming.read_credits.at(p.index)),
|
||||
str(programming.write_credits.at(p.index)),
|
||||
if programming.read_credits.at(p.index)==0 and programming.write_credits.at(p.index)==0
|
||||
{[No normal address admission through this slot.]} else {[Subject to target credits, ID and lifecycle gates.]})),
|
||||
widths:(1.5fr,0.55fr,0.6fr,1.65fr))
|
||||
SRAM and SDRAM have separate four-read/two-write target limits; serial and error targets use
|
||||
one read/one write. Target credits and master credits both constrain admission. The source
|
||||
identity receives a fixed master prefix, and the same source ID is blocked while its earlier
|
||||
transaction remains active. Multiple IDs do not remove ordering requirements imposed by software.
|
||||
|
||||
The JPEG private path is connected to slot 6 in #code("soc_data_plane"), but both current
|
||||
master-credit functions return zero for that slot. Consequently normal JPEG payload addresses
|
||||
cannot enter that route in this snapshot. Error handling can use a separate credit allowance;
|
||||
it does not make the normal path usable. This static integration finding does not change the
|
||||
standalone codec/register implementation. See @known-limitations; no RTL fix is included here.
|
||||
|
||||
==== Arbitration and its assumptions
|
||||
Read and write arbitration are separate per target. The normal base priorities are HP I/D 12,
|
||||
I/O gateways 10, DMA/EXT-H 8 and LP gateway 2; the default case, including slot 6, is zero.
|
||||
Incoming QoS can raise a normal request to 15. Continuously eligible requests age to priority
|
||||
16 after the configured 256-cycle interval, while LP recovery receives priority 31.
|
||||
|
||||
These priorities choose among eligible requests. A full credit count, a busy source ID,
|
||||
blocked resource, unavailable target or withheld handshake can prevent eligibility or progress.
|
||||
The aging interval is not a worst-case application latency. Report target readiness,
|
||||
contention, clock and outstanding state when interpreting wait counters.
|
||||
|
||||
==== Error and retry behavior
|
||||
The LP path distinguishes unmapped decode errors from illegal/denied transaction errors.
|
||||
The native data plane can route denied accesses to a finite error target and record source
|
||||
attribution. A target timeout can trigger synthetic error completion and fail-closed isolation.
|
||||
Do not blindly retry a write with side effects after a timeout: first determine whether the
|
||||
target accepted it, preserve the fault and complete the documented recovery/reset sequence.
|
||||
Admission policy and a completed error response do not guarantee recovery of partially modified data.
|
||||
#source-note("rtl/mini/top/axi4_interconnect.sv",title:"Actual LP alignment, burst and target checks")
|
||||
#source-note("rtl/mini/top/axi4_data_crossbar.sv",title:"Native admission, credit and arbitration functions")
|
||||
#source-note("rtl/mini/top/soc_data_plane.sv",title:"Private masters, prefixes, gateways and CDC wiring")
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#import "../style.typ": *
|
||||
#let examples = data.system_reference.programming.timing
|
||||
|
||||
=== Clock, Divider and Timebase Programming <clock-programming>
|
||||
Select a functional clock before calculating a divider. A profile's CPU clock, PCLK, stable
|
||||
memory clock and audio clock are not interchangeable. The existing clock/reset inventory names
|
||||
the domains; the table below records which clock must be supplied to the relevant calculation.
|
||||
|
||||
#ds-table("functional-clock-use",[Functional-clock inputs and configuration consequences],
|
||||
([Consumer],[Calculation input],[Change handling]),
|
||||
(([UART / I2C / general timer],[Actual clock driving the selected engine; check PCLK/root routing.],[Recalculate divisors/timing or counter period after that clock changes.]),
|
||||
([I2S],[External audio-domain frequency, not the HP CPU clock.],[Select a compatible preset or exact programmable divisors while disabled.]),
|
||||
([SDRAM / QPI / OPI / XPI],[Stable memory domain plus the controller's local divider/sampling convention.],[Convert device timing to controller cycles and reinitialize where required.]),
|
||||
([LP CLINT / HP ACLINT],[Configured CLINT timebase. The reference configuration selects 1 MHz.],[Use the timebase for deadlines rather than a core instruction frequency.]),
|
||||
([RTC / watchdog],[Their actual independent timing source and divider contract.],[Do not infer their tick from a CPU frequency change.]),
|
||||
([DVP / ULPI / JTAG],[Externally supplied functional clock under the interface contract.],[Validate external clock availability and board timing separately.])),
|
||||
widths:(0.95fr,1.55fr,1.7fr))
|
||||
|
||||
==== UART fractional divider
|
||||
The published calculator rounds the period to units of 1/256 input-clock cycle:
|
||||
#code("P = floor((Fclk * 256 + floor(Btarget / 2)) / Btarget)").
|
||||
#code("BAUD_INT = P >> 8") and #code("BAUD_FRAC = P & 255"); the nominal resulting rate is
|
||||
#code("Bactual = Fclk * 256 / P"). The accepted range is #code("4096 <= P <= 0xffffffff").
|
||||
Both zero inputs and a period below the supported minimum must be rejected.
|
||||
|
||||
#ds-table("uart-divider-examples",[UART integer/fraction examples checked against the SDK],
|
||||
([Input Hz],[Target baud],[INT / FRAC],[Nominal baud],[Error ppm]),
|
||||
examples.uart.map(e=>(str(e.clock),str(e.target),[#e.integer / #e.fraction],str(e.actual_hz),str(e.error_ppm))),
|
||||
widths:(0.85fr,0.8fr,0.8fr,1.1fr,0.65fr))
|
||||
The 24 MHz row represents that input frequency; the 72 MHz row is a conditional calculation
|
||||
example, not a statement that every UART receives 72 MHz in the reference profile. Confirm
|
||||
the source used by the caller and the actual engine clock. Nominal quantization error excludes
|
||||
oscillator tolerance, jitter, receiver margin and pad timing.
|
||||
|
||||
==== I2C timing cycles
|
||||
The SDK selects Standard/Fast/Fast-mode Plus timing constraints from the requested rate.
|
||||
Each minimum time is converted with #code("ceil(Fclk * time_ns / 1e9)"). It then lengthens
|
||||
the low interval if required so the total cycle count is at least #code("ceil(Fclk / Ftarget)").
|
||||
Start, stop, setup and bus-free counters have their own constraints; a single SCL divider is insufficient.
|
||||
|
||||
#ds-table("i2c-divider-examples",[I2C nominal SCL timing at a 24 MHz engine clock],
|
||||
([Target Hz],[Low cycles],[High cycles],[Nominal Hz],[Error ppm]),
|
||||
examples.i2c.map(e=>(str(e.target),str(e.cycles.at(0)),str(e.cycles.at(1)),str(e.actual_hz),str(e.error_ppm))),
|
||||
widths:(0.85fr,0.7fr,0.7fr,1.1fr,0.75fr))
|
||||
All programmed timing counters must fit their 16-bit fields. Clock stretching, bus busy time,
|
||||
arbitration and physical rise/fall time can make observed transaction throughput lower. A
|
||||
nominal 1 MHz setting does not qualify a board or target for Fast-mode Plus electrical operation.
|
||||
|
||||
==== General timer periods
|
||||
For a millisecond request, the helper first computes #code("C = ceil(Fclk * ms / 1000)").
|
||||
It chooses #code("D = max(1, ceil(C / 2^32))") and #code("N = ceil(C / D)").
|
||||
The register values are #code("PRESCALE = D - 1") and #code("LOAD = N - 1"); the nominal
|
||||
period for this helper's configuration is #code("D * N / Fclk"). This formula does not replace
|
||||
the timer chapter's rules for other count directions or operating modes.
|
||||
|
||||
#ds-table("timer-divider-examples",[General-timer period examples at 24 MHz],
|
||||
([Target ms],[PRESCALE],[LOAD],[Nominal ms]),
|
||||
examples.timer.map(e=>(str(e.target_ms),str(e.prescale),str(e.load),str(e.actual_ms))),
|
||||
widths:(1fr,1fr,1fr,1fr))
|
||||
Check overflow before multiplying clock and duration; use the bounded helper instead of a
|
||||
32-bit intermediate. The divisor cannot exceed 65536. A counter tick and an interrupt-handler
|
||||
response time are different quantities.
|
||||
|
||||
==== I2S sample-rate and packing modes
|
||||
For the toggle-divider implementation, #code("SCLK = Faudio / (2 * (SCLK_DIV + 1))") and
|
||||
#code("Fs = SCLK / (2 * (LRCK_DIV + 1))"). The exact helper selects #code("LRCK_DIV = bits - 1")
|
||||
and requires an integral #code("Faudio / (4 * Fs * bits)") between 1 and 256. It rejects
|
||||
unsupported widths or an inexact ratio instead of silently rounding to a different sample rate.
|
||||
|
||||
#ds-table("i2s-divider-examples",[Exact I2S configurations for the 18.432 MHz reference audio input],
|
||||
([Bits],[Target Hz],[SCLK_DIV],[LRCK_DIV],[Nominal Hz]),
|
||||
examples.i2s.map(e=>(str(e.bits),str(e.target),str(e.sclk_div),str(e.lrck_div),str(e.actual_hz))),
|
||||
widths:(0.5fr,1fr,0.8fr,0.8fr,1fr))
|
||||
For example, 44.1 kHz at this input frequency is not accepted by the exact helper. Program
|
||||
format and divider selection while disabled, then configure the matching stream packing.
|
||||
16-bit mode stores two samples in one 32-bit word; 24-bit mode stores one sample per word.
|
||||
The resulting buffer budget is worked through in @software-memory-budget.
|
||||
|
||||
==== Reconfiguration checklist
|
||||
+ Read the current source and capability, and quiesce the affected peripheral and DMA client.
|
||||
+ Calculate every related timing field, validate range and rounding, then configure while idle/disabled.
|
||||
+ Clear stale status, restart the interface and verify a bounded transfer before normal traffic.
|
||||
+ On a failed clock transition, keep the observed safe-source configuration and recalculate
|
||||
against that actual source; do not continue with divisors for the rejected request.
|
||||
#source-note("crt/src/hal/uart_math.c",title:"UART rounding and field bounds")
|
||||
#source-note("crt/src/hal/i2c_math.c",title:"I2C minimum-time and rate calculations")
|
||||
#source-note("crt/src/hal/timer_math.c",title:"Timer period calculation and overflow checks")
|
||||
#source-note("crt/src/hal/i2s_math.c",title:"Exact I2S divider and sample-packing helpers")
|
||||
#source-note("rtl/ip/serial/i2s_clkgen.sv",title:"I2S divider edge behavior")
|
||||
@@ -0,0 +1,39 @@
|
||||
#import "../style.typ": *
|
||||
|
||||
== Peripheral Coexistence and Resource Conflicts <resource-conflicts>
|
||||
This matrix describes shared resources and integration constraints. Hardware exclusions,
|
||||
software conventions and bandwidth competition require different decisions. Allowed combinations
|
||||
still require workload-specific validation.
|
||||
|
||||
#ds-table("coexistence-matrix",[Shared-resource constraints across IP boundaries],
|
||||
([Shared resource],[Constraint class],[Condition / integration action]),
|
||||
data.system_reference.programming.conflicts.map(r=>(r.resource,r.state,r.condition)),
|
||||
widths:(1fr,0.95fr,2.2fr))
|
||||
|
||||
=== Choosing a coexistence configuration
|
||||
+ Start from the generated GPIO alternate-function table and memory-pad mode. A single pad
|
||||
cannot be driven by two independent peripheral outputs; identify the complete pin group.
|
||||
+ Allocate software DMA contexts and check endpoint exclusivity. Reserve the entire buffer
|
||||
lifetime, not just the interval needed to program the channel.
|
||||
+ Check the central Resource Controller's owner. Its DMA resource represents the shared engine,
|
||||
not an independent LP/HP owner bit for each software channel convention.
|
||||
+ Account for gateway sharing, memory target credits and conservative drain/block behavior.
|
||||
A second controller can be electrically independent yet share the same data admission path.
|
||||
+ Establish clocks, stream-switch selection and interrupt ownership before enabling producers.
|
||||
+ Validate the intended combination with actual input/output traffic, completion and error handling.
|
||||
|
||||
For example, DVP capture and an I2S convenience DMA client cannot both assume exclusive use
|
||||
of bulk channel 3. Explicitly allocating a different context can address a software convention,
|
||||
but it does not change a shared endpoint, stream-switch mode or target bandwidth limit. QPI/OPI
|
||||
pad exclusion cannot be solved by choosing another DMA channel.
|
||||
|
||||
=== Failure handling and evidence
|
||||
Reject a conflicting request without overwriting active state. Capture the context, owner,
|
||||
pad mode and fault. Complete recovery before reassignment. An admission error is not permission
|
||||
to widen an ACL; simultaneous-workload qualification remains separate.
|
||||
#block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
|
||||
#set text(size:9pt)
|
||||
#source("rtl/mini/integration/soc_topology.json",title:"Pin and initiator policy") ·
|
||||
#source("rtl/mini/top/soc_data_plane.sv",title:"Gateway/lifecycle wiring") ·
|
||||
#source("docs/ip/resource-controller.md",title:"Ownership and drain contract")
|
||||
]
|
||||
@@ -23,6 +23,8 @@ and selected component. Do not assume arbitrary QSPI or HyperBus devices are int
|
||||
XPI writes use indirect commands; a CPU store into the read-only data-plane flash mapping is
|
||||
not a programming operation. Verify erase/program completion before making code executable.
|
||||
|
||||
#include "device-compatibility.typ"
|
||||
|
||||
==== GPIO handoff and signal integrity
|
||||
+ Reserve a pin group and check alternate-function conflicts before assigning a peripheral.
|
||||
+ Stop the previous function and wait for its documented idle condition. Establish the
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#import "../style.typ": *
|
||||
|
||||
==== External Memory and Device Compatibility <device-compatibility>
|
||||
The table distinguishes a controller protocol profile, a simulation model and a qualified
|
||||
physical component. Geometry or a similar command set is insufficient to establish drop-in
|
||||
compatibility. Device order codes, supply, speed grade and board timing must be reviewed together.
|
||||
|
||||
#ds-table("external-device-profiles",[External-memory profiles and their evidence boundary],
|
||||
([Device / profile],[Protocol and geometry],[Initialization / compatibility condition]),
|
||||
data.system_reference.programming.devices.map(r=>(r.name,[#r.protocol \ #r.geometry],r.setup)),
|
||||
widths:(1fr,1.4fr,1.75fr))
|
||||
|
||||
#ds-table("external-device-evidence",[Model coverage versus physical-device qualification],
|
||||
([Device / profile],[Model evidence indexed],[Board],[Silicon]),
|
||||
data.system_reference.programming.devices.map(r=>(r.name,r.model,r.board,r.silicon)),
|
||||
widths:(1.1fr,1.85fr,0.6fr,0.6fr))
|
||||
Model source and test availability are not a reviewed passing run. The fast XPI backend is
|
||||
specifically narrower than a pin-level flash model: it is useful for selected boot/regression
|
||||
work and must not qualify erase/program behavior, all LUT modes or board sampling margins.
|
||||
|
||||
Before substituting a component, compare its reset state, command/address encoding, mode/dummy
|
||||
phases, latency configuration, page and chip boundaries, supported sampling clocks and status
|
||||
bits. Validate the complete initialization and recovery sequence, not only a successful ID read.
|
||||
For SDRAM, match bank/row/column geometry and convert device timings to the actual memory clock.
|
||||
|
||||
The QPI controller is device-specific. The OPI and modified single-clock HyperBus-style paths
|
||||
use different protocol encoders and are boot/profile-selected; they do not imply generic
|
||||
compatibility with every commercial octal or HyperBus device. A supplier's maximum device clock
|
||||
is not the characterized maximum frequency of the Mini-to-board interface.
|
||||
|
||||
If no qualified device is listed, retain the profile/model status and obtain the missing
|
||||
component, board and timing evidence before releasing a BOM. Do not select an arbitrary
|
||||
commercial part merely to fill this table. Firmware must also respect @resource-conflicts,
|
||||
because QPI and OPI use mutually exclusive shared pads.
|
||||
#source-note("docs/ip/xpi.md",title:"XPI slot/LUT and full versus fast flash model")
|
||||
#source-note("docs/ip/psram.md",title:"ESP-PSRAM64H initialization and device boundaries")
|
||||
#source-note("docs/ip/opipsram.md",title:"Prototype OPI and single-clock HyperBus-style profiles")
|
||||
#source-note("docs/ip/sdram.md",title:"SDRAM geometry, clocking and timing configuration")
|
||||
|
||||
@@ -98,6 +98,10 @@ an independently configured memory job. The drawing does not assert a direct har
|
||||
connection from DVP to the JPEG engine. I2S uses its independent audio clock and compatible
|
||||
external codec wiring. Production APU codec/KWS availability must be checked separately.
|
||||
|
||||
The current native JPEG master has zero normal admission credits in the reviewed crossbar.
|
||||
The diagram describes the intended memory-mediated composition; an end-to-end JPEG private-DMA
|
||||
application requires the separately corrected and validated route described in @bus-programming.
|
||||
|
||||
+ Select external interface modes, pin routing and clocks before starting capture/playback.
|
||||
+ Allocate aligned buffers, prepare ownership, and configure the consumer before enabling
|
||||
a producer that cannot tolerate sustained backpressure.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#import "../style.typ": *
|
||||
|
||||
== Register Access and Programming Conventions <register-programming>
|
||||
The following conventions explain how to read the IP register chapters. They do not override
|
||||
an individual register's access width, strobe, reset or side-effect contract. Use the current
|
||||
IP version and capability before interpreting a familiar offset from another device or revision.
|
||||
|
||||
=== Access attributes and side effects
|
||||
#ds-table("register-access-conventions",[Register access attributes and safe software interpretation],
|
||||
([Attribute / behavior],[Meaning],[Programming consequence]),
|
||||
(([RO],[Hardware supplies a value; software writes are not part of the interface.],[Do not assume a write is harmless; an IP can reject it.]),
|
||||
([RW],[Software can update documented writable fields.],[Read-modify-write is valid only when reads and writes have no conflicting side effects.]),
|
||||
([WO / command],[A write submits an action or value; reads need not return the last write.],[Use the command encoding and required state; do not read it to construct another command.]),
|
||||
([W1C / RW1C],[Writing one clears the selected documented status bit.],[Write an explicit acknowledgement mask; a read-modify-write can clear unrelated pending events.]),
|
||||
([Set / clear / toggle alias],[A specific IP provides a dedicated operation.],[Use only that IP's documented address/mask; there is no global SoC alias scheme.]),
|
||||
([Read/pop or clear-on-read],[Reading consumes data or changes state.],[Avoid speculative reads, repeated debugger watches and reads used only for display.]),
|
||||
([Live / snapshot],[A live value can change between accesses; a snapshot is captured by a defined operation.],[Use the register's snapshot/latch sequence before combining multiple words.]),
|
||||
([Reserved / unsupported],[The offset or field is not a supported software feature.],[Follow stated reserved-bit rules; reject unsupported modes and preserve defined state.])),
|
||||
widths:(0.85fr,1.55fr,1.75fr))
|
||||
|
||||
For an event that arrives concurrently with a software acknowledgement, the IP's stated
|
||||
hardware-set versus software-clear priority applies. No single priority rule is imposed on
|
||||
all peripherals. Clear only events already captured by the handler and re-check pending state
|
||||
using the documented sequence before reenabling interrupts.
|
||||
|
||||
=== Access width, byte lanes and alignment
|
||||
Use naturally aligned 32-bit register operations unless the IP explicitly supports another
|
||||
width. APB4 PSTRB support is per register and per instance. A data window can require all four
|
||||
byte strobes while adjacent configuration registers accept partial updates. The bus fabric's
|
||||
supported beat sizes do not imply that a byte store is meaningful for every peripheral.
|
||||
|
||||
Offsets in the register tables are byte offsets from the listed base. Bit 0 is the least
|
||||
significant bit of the displayed register value. Separate numeric bit numbering, bus byte lanes
|
||||
and protocol payload packing. The boot-bundle serialization has its own little-endian format;
|
||||
camera byte swaps and audio sample order must follow their IP definitions rather than a
|
||||
generic byte-copy assumption.
|
||||
|
||||
#ds-table("register-access-exceptions",[Examples requiring an IP-specific access sequence],
|
||||
([IP / case],[Exception],[Required handling]),
|
||||
(([I2S data windows],[TXDATA/RXDATA are word interfaces; RXDATA can pop data.],[Use full-word accesses and the selected PIO/stream ownership mode.]),
|
||||
([DVP RXDATA],[PIO pop is permitted only when stream output is disabled.],[Do not consume a frame simultaneously through PIO and DMA.]),
|
||||
([DMA active configuration],[Unsupported widths/strobes and writes forbidden while busy can fail.],[Configure idle context first; inspect command/error status rather than assuming a write took effect.]),
|
||||
([GPIO atomic outputs],[Dedicated output operations avoid an ordinary output-register read-modify-write race.],[Use documented GPIO operations; they are not general atomic memory operations.]),
|
||||
([MPW demo IPs],[Legacy examples ignore PSTRB and have a small aliased decode.],[Use aligned full-word operations and the MPW contract; do not generalize to PRODUCT.]),
|
||||
([Dynamic reset values],[Some status/identification fields reflect configuration or live inputs.],[Do not assume every reset value is a fixed zero constant.])),
|
||||
widths:(0.9fr,1.5fr,1.75fr))
|
||||
|
||||
=== Busy-state writes and read-modify-write
|
||||
+ Check identity/capability and the current busy/owner state before touching configuration.
|
||||
+ Build a value from documented fields. Do not preserve arbitrary readback bits from a command,
|
||||
FIFO, W1C or mixed-access register.
|
||||
+ Stop or quiesce the engine when required, then write the supported field/word width.
|
||||
+ Read a documented status or ordinary configuration register to confirm acceptance. A posted
|
||||
write at one layer is not proof that an external device completed the operation.
|
||||
+ On a bus error or rejected command, retain the cause and recover the previous operation
|
||||
before retrying. Blind command replay can duplicate a side effect.
|
||||
|
||||
The volatile qualifier controls compiler treatment of an MMIO access; it does not provide
|
||||
cache coherency, buffer ownership, device completion or an arbitrary inter-core ordering
|
||||
guarantee. Use the platform's required barriers and the sequences in @memory-coherency.
|
||||
Neither the LP fabric nor the native crossbar admits locked transactions in the reviewed
|
||||
predicate; do not infer a global exclusive-access service from CPU ISA names alone.
|
||||
|
||||
=== RV32 access to multiword registers
|
||||
For CLINT, the SDK reads high, low and high again, accepting the result only if both high
|
||||
words match; its retry count is bounded. It writes the compare register by temporarily
|
||||
setting the low word to all ones, then writing high and the final low word. These are the
|
||||
actual CLINT helper sequences, not a blanket rule for every 64-bit peripheral value.
|
||||
|
||||
Use an IP's latch or snapshot trigger when one exists. A latched counter, FIFO pair, write-only
|
||||
command pair and live monotonic counter can require different orders. Avoid inventing a high/low
|
||||
retry loop for a read that pops data. If the interface lacks a safe coherent observation,
|
||||
describe the result as separate samples rather than claiming an atomic 64-bit read.
|
||||
#source-note("crt/src/hal/clint.c",title:"Bounded CLINT reads and compare-write order")
|
||||
#source-note("docs/ip/i2s.md",title:"I2S strobes, data windows and event priority")
|
||||
#source-note("docs/ip/dvp.md",title:"DVP PIO/stream exclusion and frame semantics")
|
||||
#source-note("docs/ip/gpio.md",title:"GPIO-specific atomic operations and pad control")
|
||||
#source-note("rtl/ip/peripheral/dma_reg.sv",title:"DMA register legality and busy-state handling")
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#import "../style.typ": *
|
||||
#let programming = data.system_reference.programming
|
||||
|
||||
== DMA Request, Interrupt and Event Routing <dma-routing>
|
||||
Request selectors describe pacing or stream endpoints. Channel numbers describe software
|
||||
contexts, and interrupt numbers describe completion delivery. None can be inferred from another.
|
||||
Central DMA channel conventions are not independent hardware ownership or privilege domains.
|
||||
|
||||
=== Central DMA request map
|
||||
#ds-table("dma-request-map",[DMA request numbers checked against RTL, SDK and top-level connections],
|
||||
([Request],[Endpoint],[Data transport],[Software channel convention],[Endpoint LP IRQ]),
|
||||
programming.dma_routes.map(r=>(str(r.number),r.label,r.transport,r.channel,r.lp_irq)),
|
||||
widths:(0.5fr,0.9fr,1.25fr,1.15fr,0.65fr))
|
||||
The two SDK XPI selectors map to the RTL's historical QSPI names with identical values.
|
||||
UART selectors connect to UART0, not UART1. I2S, DVP and crypto have dedicated stream ports;
|
||||
the other paced clients use peripheral data windows through the MM-to-MM path. Software
|
||||
selector zero has no dedicated peripheral request wire. WS2812 uses that software-paced path.
|
||||
|
||||
The endpoint IRQ column identifies the peripheral's own event line where one exists; it is
|
||||
not the DMA transfer-completion interrupt. Central DMA channel events aggregate at the DMA
|
||||
resource's LP IRQ or HP PLIC source according to the central resource owner. Crypto has no
|
||||
separate LP vector entry in this integration; inspect its register status and DMA completion path.
|
||||
|
||||
=== Software channel allocation
|
||||
#ds-table("dma-channel-conventions",[Current SDK and application channel conventions],
|
||||
([Channel],[Client / convention],[Basis]),
|
||||
programming.channels.map(r=>(str(r.number),r.label,source(r.sources.last(),title:"Driver / allocation source"))),
|
||||
widths:(0.5fr,2.7fr,0.9fr))
|
||||
The bulk convention is shared and must be serialized by the application. XPI accepts an
|
||||
explicit channel argument, but selecting another number does not bypass an active stream
|
||||
endpoint, resource ownership or buffer-lifetime restriction. Crypto uses separate input/output
|
||||
contexts. The HP boot loader uses its declared boot context; channel 7 remains reserved.
|
||||
|
||||
The central DMA start validator currently accepts only its 32-bit width code, even though
|
||||
the SDK type also names 8- and 16-bit widths. Streams require a full-word length and the
|
||||
supported source/destination increment mode. Do not infer DRE, narrow stream writes, cyclic
|
||||
descriptors or 2D operation merely from fields in the descriptor structure.
|
||||
|
||||
=== Private DMA masters and owner-directed completion
|
||||
#ds-table("private-dma-routes",[Central and private-master routes with owner-directed interrupts],
|
||||
([Engine],[Native master / gateway],[LP vector bit],[HP PLIC source]),
|
||||
programming.engines.map(r=>(link(label(r.id),r.name),r.master,str(r.lp_irq),str(r.hp_irq))),
|
||||
widths:(1.2fr,1.5fr,0.65fr,0.7fr))
|
||||
These HP source numbers are read from the actual PLIC assignments. The Resource Controller
|
||||
selects one owner's route and masks routes according to its lifecycle contract; it does not
|
||||
deliver a completion to both owners. Private DMA engines do not consume a central DMA channel
|
||||
simply because both move memory. They can still share a gateway or destination bandwidth.
|
||||
JPEG's slot-6 admission limitation is described in @bus-programming.
|
||||
|
||||
=== Completion and event handling
|
||||
+ Prepare the receiving handler and buffers before enabling the channel, peripheral event and owner route.
|
||||
+ On interrupt, identify the aggregate source and inspect the channel/IP event fields; a DMA
|
||||
completion does not by itself prove that a serial device finished its on-wire operation.
|
||||
+ Check actual length, error and endpoint status, then acknowledge only the documented sticky causes.
|
||||
+ Observe each stream's boundary definition. I2S RX has no TLAST, while DVP TLAST marks a line;
|
||||
a generic “TLAST means complete frame” rule is incorrect.
|
||||
+ On timeout, stop submissions and complete abort/drain before releasing the context or changing ownership.
|
||||
|
||||
The central DMA receiver requires full byte qualification. Odd-pixel DVP lines create a
|
||||
partial final stream word and are not supported by its current DMA path. Use the documented
|
||||
PIO handling or a separately implemented extension; do not discard the partial-word error.
|
||||
#source-note("rtl/ip/peripheral/dma_pkg.sv",title:"RTL request numbers and transfer kinds")
|
||||
#source-note("crt/include/retrosoc/hal/dma.h",title:"SDK selectors, contexts and descriptor type")
|
||||
#source-note("rtl/mini/top/apb4_periph.sv",title:"Request, stream and PLIC wiring")
|
||||
#source-note("rtl/ip/peripheral/dma_core.sv",title:"Start validation, endpoint exclusion and stream acceptance")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#import "../style.typ": *
|
||||
#import "../system-figures.typ": sequence-diagram
|
||||
|
||||
#include "routing-reference.typ"
|
||||
|
||||
== Multicore Operation and Resource Ownership <multicore-operation>
|
||||
Hazard3 is the root-management hart; VexiiRiscv is the application hart. Linux does not take
|
||||
over the SoC's root clock, reset, admission and recovery controls merely by booting. Decide
|
||||
@@ -58,6 +60,8 @@ by the lifecycle policy. See @memory-coherency and @operating-states.
|
||||
#source-note("crt/src/hal/resource.c",title:"Actual HAL handoff and acknowledgement sequence")
|
||||
#source-note("docs/ip/hp-platform.md",title:"HP interrupt and mailbox platform")
|
||||
|
||||
#include "coexistence-reference.typ"
|
||||
|
||||
== Security and Access-Control Boundaries <security-boundaries>
|
||||
The current protection model limits bus admission and root-control access. It must be used
|
||||
with explicit software ownership and does not constitute a certified secure-boot or key-storage
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#import "../style.typ": *
|
||||
|
||||
= Appendix: Software Memory and Buffer Requirements <software-memory-budget>
|
||||
Memory-map capacity, image allocation, linked image size and peak runtime use are different
|
||||
quantities. This appendix supplies source-based layouts and illustrative budgets. No matching
|
||||
ELF/MAP files or measured stack/runtime high-water report were available for this update;
|
||||
actual firmware occupation remains unprovided.
|
||||
|
||||
== Linker Layout and Runtime Accounting
|
||||
#ds-table("software-memory-accounting",[Memory quantities and the evidence needed to publish them],
|
||||
([Quantity],[How to obtain it],[Current boundary]),
|
||||
(([Physical / mapped capacity],[Selected profile and fitted memory/controller geometry.],[The reference SRAM and address map are configuration data, not an application's usage.]),
|
||||
([Flash load image],[ELF/MAP LMA, initialized sections and the final packaged image.],[Actual built size not supplied. Do not count NOLOAD BSS as stored bytes.]),
|
||||
([Runtime static memory],[VMA for code/rodata/data/BSS in the chosen linker layout.],[Actual section sizes not supplied. Placement differs between SRAM, SDRAM and PSRAM layouts.]),
|
||||
([Stack / working space],[Linker constraints plus reviewed worst-case or measured runtime demand.],[A stack-top symbol does not reserve or measure a safe stack budget.]),
|
||||
([Boot artifacts],[Loader header allocation bounds and actual package entries.],[Maximum allocations are already listed in the boot chapter; actual image lengths must come from the bundle.]),
|
||||
([DMA and media buffers],[Packing, alignment, lifetime, descriptor count and buffer multiplicity.],[The examples below are calculations, not measured allocations.])),
|
||||
widths:(1fr,1.7fr,1.5fr))
|
||||
|
||||
The all-SRAM script places the main code/rodata/data/BSS in SRAM with initialized content
|
||||
loaded from flash. Its #code("_stack_point") is at the SRAM-region end, and #code("_heap_start")
|
||||
is a location symbol after static sections. These symbols do not prove an independently bounded
|
||||
heap or stack reservation. The freestanding SDK's lack of dynamic allocation does not eliminate
|
||||
stack, library scratch or application-buffer requirements.
|
||||
|
||||
The SDRAM and PSRAM linker variants place application material in their named external memory
|
||||
regions; inspect the actual script and generated memory regions rather than relying on a
|
||||
stale comment or filename. Retain both VMA and LMA in a size report. Compare the sum of static
|
||||
sections, reserved buffers and required stack/scratch margin with the available region before boot.
|
||||
|
||||
== Worked Buffer Budgets
|
||||
The examples use 64-byte allocation alignment for shared HP cache-block ownership. This does
|
||||
not replace the peripheral's minimum alignment. Each independent buffer receives its own aligned
|
||||
stride so a maintenance operation does not include another owner's boundary bytes.
|
||||
|
||||
#ds-table("buffer-budget-examples",[Payload, stored representation and aligned allocation],
|
||||
([Scenario],[Payload / buffer],[Stored / buffer],[Aligned stride],[Total allocation]),
|
||||
data.system_reference.programming.budgets.map(r=>(r.name,str(r.payload_bytes),str(r.storage_bytes),str(r.stride_bytes),str(r.total_bytes))),
|
||||
widths:(1.65fr,0.65fr,0.65fr,0.65fr,0.75fr),notes:[All numeric entries are bytes. Total includes the stated buffer count.])
|
||||
|
||||
=== DVP RGB565 frame storage
|
||||
An even-width RGB565 row has two bytes of effective payload per pixel. The current DVP core
|
||||
collects two 16-bit pixels into a 32-bit stream word. For storage planning, use
|
||||
#code("row_bytes = ceil(width / 2) * 4") and #code("frame_bytes = row_bytes * height").
|
||||
The higher FIFO-side framing metadata is not appended to memory by the central DMA.
|
||||
|
||||
The VGA example therefore uses 614400 bytes per buffer and 1228800 bytes for two buffers.
|
||||
This cannot fit in the 32 KiB reference SRAM. Two buffers are an application budgeting example;
|
||||
the current DVP convenience interface is a single-frame operation, not automatic hardware ping-pong.
|
||||
|
||||
The 641-pixel example illustrates partial final words and storage rounding. It is not a
|
||||
supported central-DMA capture configuration: odd-pixel lines assert partial TKEEP, while the
|
||||
DMA receiver requires full words. Use the documented PIO path or a separately implemented
|
||||
extension, and allocate explicit software frame metadata where the application needs it.
|
||||
|
||||
=== I2S audio blocks
|
||||
For stereo, #code("sample_count = Fs * duration_ms / 1000 * 2"). Require a whole sample-frame
|
||||
count. In 16-bit mode two samples occupy one 32-bit word; in 24-bit mode one sample occupies
|
||||
one 32-bit word although only 24 bits are payload. A 48 kHz, 10 ms block therefore stores
|
||||
1920 bytes in 16-bit mode and 3840 bytes in 24-bit mode. Double buffering requires 3840 and
|
||||
7680 bytes respectively with the stated alignment.
|
||||
|
||||
Include application metadata, queue state and scheduling margin separately. The buffer
|
||||
duration is not an end-to-end latency guarantee and does not establish tolerance to arbitrary
|
||||
memory stalls. I2S RX has no TLAST; the programmed transfer length and software lifetime remain binding.
|
||||
|
||||
=== Descriptor arrays and overflow checks
|
||||
Each central DMA TCD occupies 64 bytes and must satisfy its alignment. Eight descriptors
|
||||
occupy 512 bytes. This describes a finite descriptor array; it does not imply supported cyclic
|
||||
or 2D operation. Account for payload buffers separately from descriptor storage.
|
||||
|
||||
Validate nonzero dimensions, sample count and buffer count before multiplication. Check the
|
||||
range before alignment rounding and before multiplying the aligned stride by the number of
|
||||
buffers. The publication calculator rejects allocations outside a 32-bit byte range, invalid
|
||||
alignment and incomplete audio frames; the application must apply equivalent bounds for its ABI.
|
||||
#block(above:rhythm.metadata-before,below:rhythm.metadata-after,breakable:false)[
|
||||
#set text(size:9pt)
|
||||
#source("crt/linker/ld2_all_sram.lds",title:"SRAM layout") ·
|
||||
#source("crt/linker/ld2_sdram.lds",title:"SDRAM layout") ·
|
||||
#source("crt/linker/ld2_psram.lds",title:"PSRAM layout") ·
|
||||
#source("rtl/ip/multimedia/dvp_core.sv",title:"DVP packing") ·
|
||||
#source("crt/src/hal/i2s_math.c",title:"I2S packing")
|
||||
]
|
||||
|
||||
= Appendix: Terminology, Notation and Document Map <document-map>
|
||||
== Terminology and Units
|
||||
#ds-table("system-glossary",[System terminology used in this publication],
|
||||
([Term],[Meaning and boundary]),
|
||||
data.system_reference.programming.glossary.map(r=>(r.term,r.meaning)),widths:(0.85fr,3.3fr))
|
||||
|
||||
== Reading Numbers and Status
|
||||
Register offsets are in bytes; bit ranges name inclusive bit indices. Address-map ranges use
|
||||
the stated inclusive end address. Numeric suffixes in signal names denote signal indices,
|
||||
not package pin numbers. `_i`, `_o` and `_n` retain their declared direction/polarity meaning.
|
||||
An RTL `inout` pad wrapper may contain a functionally input-only route.
|
||||
|
||||
LP interrupt vector bits, HP PLIC source IDs, DMA request values, software channel contexts
|
||||
and native master slots are separate namespaces. A matching integer does not connect them.
|
||||
Clock configuration, nominal calculated rate, model timing and characterized pin timing are
|
||||
also distinct. Interpret every Min/Typ/Max entry with its stated evidence and conditions.
|
||||
|
||||
"Integrated" and "Partial" describe implementation scope. "Source reviewed", "Tests available"
|
||||
and a matching "Reported pass" describe evidence. None silently implies board or silicon
|
||||
qualification. Reserved, unsupported and TBD are different states; use the specific meaning
|
||||
rather than assuming that an absent value is zero or that an address window guarantees a device.
|
||||
|
||||
== Document and Evidence Navigation
|
||||
#ds-table("document-navigation",[Where to find an authoritative answer],
|
||||
([Question],[Primary reference],[How to use this datasheet]),
|
||||
(([Addresses, routes and pins],[Canonical memory/topology/pin inputs and generated bindings.],[Use the generated tables and cross-IP route/conflict summaries.]),
|
||||
([Register behavior],[Current RTL, handwritten C definitions and reviewed IP contract.],[Use Register Programming plus the individual register exceptions.]),
|
||||
([Driver sequence],[SDK implementation, platform source and the IP's software contract.],[Follow the supported path and its error/ownership boundaries.]),
|
||||
([Board connection],[Approved board constraints/schematic and selected device/pad specifications.],[Use connection/compatibility tables to identify prerequisites and missing evidence.]),
|
||||
([Validation or performance],[Matching source/profile report, tool versions, device/model and conditions.],[Use the support and characterization sections without promoting source presence to a pass.]),
|
||||
([Publication identity],[This PDF's manifest and matching publication source set.],[Use Sources and Reproducibility for publication-only metadata links.])),
|
||||
widths:(0.85fr,1.75fr,1.55fr))
|
||||
The datasheet presents the reviewed snapshot for integration. Detailed IP contracts explain
|
||||
local behavior; SDK sources show the implemented calling sequence; board guides define a
|
||||
particular physical system; result reports establish only the scope actually tested. Where
|
||||
an old narrative conflicts with current executable integration, use the current implementation
|
||||
and record the discrepancy rather than silently copying the older statement.
|
||||
#source-note("publications/datasheets/system-reference.json",title:"Publication content and evidence index")
|
||||
#source-note("docs/README.md",title:"Engineering document map")
|
||||
@@ -68,6 +68,60 @@ are not characterized operating limits or measured system performance. Keep the
|
||||
benchmark profile, workload, placement, measurement boundary and report with any
|
||||
published result; quick CoreMark regression is not a qualified public score.
|
||||
|
||||
## Cross-IP programming and usage appendices
|
||||
|
||||
Bus Transaction Rules and Arbitration belongs inside Interconnect; Clock,
|
||||
Divider and Timebase Programming belongs inside Clock and Reset. Place the
|
||||
DMA Request/Interrupt/Event map after Interrupt System and the coexistence
|
||||
matrix after Multicore Operation. Register Access and Programming Conventions
|
||||
precedes Peripherals. Device Compatibility is a child of External Interfaces.
|
||||
The Software Memory/Buffer and Terminology/Document Map appendices precede
|
||||
Document Control. Keep existing per-IP register details and link to exceptions
|
||||
instead of reproducing them in every global table.
|
||||
|
||||
`system-reference.json.programming` owns reviewed route bindings, channel
|
||||
conventions, conflicts, device-profile evidence and terminology. The publication
|
||||
collector checks RTL/SDK request-number parity (including explicit QSPI/XPI
|
||||
aliases), coverage, actual connection snippets and referenced instances. Channel
|
||||
conventions are supported by driver call sites, not inferred from selector IDs.
|
||||
Private masters and central DMA contexts are different resources. Preserve
|
||||
endpoint IRQ, aggregate DMA IRQ, LP vector bit and HP PLIC source as separate
|
||||
fields/namespaces. Derive PLIC assignments and normal master credits from the
|
||||
current implementation; mark a zero-credit connected route explicitly.
|
||||
|
||||
Cross-IP rules are explanatory defaults with documented exceptions. Do not
|
||||
invent a global atomic-register alias, exclusive-access guarantee or universal
|
||||
multiword read/write order. Describe PSTRB, W1C, FIFO pop, busy-write and dynamic
|
||||
reset behavior at the correct register/instance scope. A value present in an SDK
|
||||
enumeration does not establish that the current hardware start validator accepts it.
|
||||
|
||||
Timing examples state input clock, target, integer register values, achieved
|
||||
nominal rate and rounding/error. Cross-check UART/I2C/timer/I2S calculations
|
||||
against the existing pure C helpers, including illegal and boundary inputs.
|
||||
For I2S also check the actual toggle-divider implementation. Nominal digital
|
||||
rates do not qualify board timing or device speed grades. Never substitute STA
|
||||
periods for a peripheral's configured functional input frequency.
|
||||
|
||||
Compatibility tables distinguish protocol/geometry, model source/test coverage,
|
||||
board validation and silicon validation. A board/silicon pass needs matching
|
||||
report context, source revision and profile; absent evidence remains explicit.
|
||||
Do not pick an arbitrary commercial part or copy its rated speed into Mini ratings.
|
||||
|
||||
Buffer examples state payload packing, word padding, alignment, buffer count and
|
||||
overflow bounds. Distinguish VMA/LMA, static section sizes, image allocation and
|
||||
runtime demand. A linker stack-top or heap-start symbol is not an actual usage
|
||||
measurement. DVP odd-line partial words and I2S 16/24-bit packing must be accounted
|
||||
for; an illustrative double-buffer budget does not imply a hardware ping-pong,
|
||||
cyclic-descriptor or frame-ring capability. Missing ELF/MAP or high-water evidence
|
||||
must remain unprovided, not filled with a calculated maximum allocation.
|
||||
|
||||
Validate duplicate/missing routes, number mismatches, missing connections, unknown
|
||||
instances and unsupported qualification claims. Check rounding, field limits,
|
||||
sample packing, alignment and allocation overflow. Host-only C calculation tests
|
||||
do not touch MMIO and are not firmware or hardware measurements. Render all new
|
||||
wide tables, formulas and appendices, check every directory destination, compare
|
||||
unchanged IP engineering data, and repeat the PDF build in an independent directory.
|
||||
|
||||
## Page, typography and color
|
||||
|
||||
| Element | Current rule |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,237 @@
|
||||
"""Publication data for routed resources and checked integer timing/buffer examples."""
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
UINT32_MAX = (1 << 32) - 1
|
||||
COLLECTIONS = ("dma_routes", "channels", "engines", "conflicts", "devices", "glossary")
|
||||
|
||||
|
||||
def dependencies(spec: dict) -> set[str]:
|
||||
paths = set(spec.get("sources", []))
|
||||
for name in COLLECTIONS:
|
||||
for row in spec.get(name, []):
|
||||
paths.update(row.get("sources", []))
|
||||
paths.update(x["file"] for x in row.get("bindings", []))
|
||||
for report in row.get("reports", []):
|
||||
paths.update((report["path"], report["profile"]))
|
||||
return paths
|
||||
|
||||
|
||||
def positive(*values: int) -> None:
|
||||
if any(type(v) is not int or v <= 0 or v > UINT32_MAX for v in values):
|
||||
raise ValueError("positive 32-bit inputs required")
|
||||
|
||||
|
||||
def uart_timing(clock: int, baud: int) -> dict | None:
|
||||
positive(clock, baud)
|
||||
scaled = (clock * 256 + baud // 2) // baud
|
||||
if not 4096 <= scaled <= 0xFFFFFFFF:
|
||||
return None
|
||||
return {"integer": scaled >> 8, "fraction": scaled & 255,
|
||||
"actual_hz": round(clock * 256 / scaled, 6),
|
||||
"error_ppm": round((clock * 256 / scaled / baud - 1) * 1_000_000, 3)}
|
||||
|
||||
|
||||
def i2c_timing(clock: int, rate: int) -> dict | None:
|
||||
positive(clock, rate)
|
||||
if rate > 1_000_000:
|
||||
return None
|
||||
ns = ((4700, 4000, 4000, 4700, 250, 4000, 4700) if rate <= 100_000 else
|
||||
(1300, 600, 600, 600, 100, 600, 1300) if rate <= 400_000 else
|
||||
(500, 260, 260, 260, 50, 260, 500))
|
||||
cycles = [(clock * value + 999_999_999) // 1_000_000_000 for value in ns]
|
||||
cycles[0] += max(0, (clock + rate - 1) // rate - cycles[0] - cycles[1])
|
||||
if any(not 0 < value <= 65535 for value in cycles):
|
||||
return None
|
||||
return {"cycles": cycles, "actual_hz": round(clock / (cycles[0] + cycles[1]), 6),
|
||||
"error_ppm": round((clock / (cycles[0] + cycles[1]) / rate - 1) * 1_000_000, 3)}
|
||||
|
||||
|
||||
def timer_timing(clock: int, milliseconds: int) -> dict | None:
|
||||
positive(clock, milliseconds)
|
||||
cycles = (clock * milliseconds + 999) // 1000
|
||||
divider = max(1, (cycles + (1 << 32) - 1) // (1 << 32))
|
||||
if divider > 65536:
|
||||
return None
|
||||
ticks = (cycles + divider - 1) // divider
|
||||
return {"prescale": divider - 1, "load": ticks - 1,
|
||||
"actual_ms": round(divider * ticks * 1000 / clock, 9)}
|
||||
|
||||
|
||||
def i2s_timing(clock: int, rate: int, bits: int) -> dict | None:
|
||||
positive(clock, rate, bits)
|
||||
divisor = rate * 4 * bits
|
||||
if bits not in (16, 24) or clock % divisor or not 1 <= clock // divisor <= 256:
|
||||
return None
|
||||
return {"sclk_div": clock // divisor - 1, "lrck_div": bits - 1,
|
||||
"actual_hz": rate, "error_ppm": 0}
|
||||
|
||||
|
||||
def aligned_bytes(size: int, alignment: int = 64) -> int:
|
||||
positive(size, alignment)
|
||||
if alignment & (alignment - 1):
|
||||
raise ValueError("alignment must be a power of two")
|
||||
result = (size + alignment - 1) // alignment * alignment
|
||||
if result > UINT32_MAX:
|
||||
raise ValueError("buffer allocation overflows 32 bits")
|
||||
return result
|
||||
|
||||
|
||||
def buffer_budget(kind: str, *, width=0, height=0, rate=0, bits=0, milliseconds=0,
|
||||
count=1, descriptors=0, alignment=64) -> dict:
|
||||
positive(count)
|
||||
if kind == "frame":
|
||||
positive(width, height)
|
||||
payload = width * height * 2
|
||||
storage = ((width + 1) // 2) * 4 * height
|
||||
supported = width % 2 == 0
|
||||
elif kind == "audio":
|
||||
positive(rate, bits, milliseconds)
|
||||
if bits not in (16, 24) or rate * milliseconds % 1000:
|
||||
raise ValueError("audio block requires whole 16/24-bit sample frames")
|
||||
samples = rate * milliseconds // 1000 * 2
|
||||
payload = samples * bits // 8
|
||||
storage = samples * (2 if bits == 16 else 4)
|
||||
supported = True
|
||||
elif kind == "descriptors":
|
||||
positive(descriptors)
|
||||
payload = storage = descriptors * 64
|
||||
supported = True
|
||||
else:
|
||||
raise ValueError("unknown buffer kind")
|
||||
stride = aligned_bytes(storage, alignment)
|
||||
if stride * count > UINT32_MAX:
|
||||
raise ValueError("buffer allocation overflows 32 bits")
|
||||
return {"payload_bytes": payload, "storage_bytes": storage, "stride_bytes": stride,
|
||||
"total_bytes": stride * count, "dma_supported": supported}
|
||||
|
||||
|
||||
def constants(text: str, prefix: str, rtl=False) -> dict[str, int]:
|
||||
text = re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S)
|
||||
pattern = (rf"\b({prefix}\w+)\s*=\s*\d+'d(\d+)\s*;" if rtl else
|
||||
rf"\b({prefix}\w+)\s*(?:=\s*|\s+)(\d+)(?:U)?(?=\s*[,;\n])")
|
||||
pairs = re.findall(pattern, text)
|
||||
if len({name for name, _ in pairs}) != len(pairs):
|
||||
raise ValueError(f"duplicate constant: {prefix}")
|
||||
return {name: int(value) for name, value in pairs}
|
||||
|
||||
|
||||
def clean_source(text: str) -> str:
|
||||
return re.sub(r"\s+", "", re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S))
|
||||
|
||||
|
||||
def validate_programming(spec: dict, ids: set[str], root: Path, revision: str) -> None:
|
||||
for relative in dependencies(spec):
|
||||
path = root / relative
|
||||
if Path(relative).is_absolute() or ".." in Path(relative).parts or not path.resolve().is_relative_to(root.resolve()):
|
||||
raise ValueError("programming path escapes repository")
|
||||
if not path.is_file():
|
||||
raise ValueError(f"missing programming source: {relative}")
|
||||
for name in COLLECTIONS:
|
||||
rows = spec[name]
|
||||
keys = [r["id"] for r in rows]
|
||||
if len(set(keys)) != len(keys):
|
||||
raise ValueError(f"duplicate programming identifier: {name}")
|
||||
for row in rows:
|
||||
if not row.get("sources"):
|
||||
raise ValueError(f"programming source missing: {row['id']}")
|
||||
if not set(row.get("ips", [])) <= ids:
|
||||
raise ValueError(f"unknown programming IP: {row['id']}")
|
||||
for binding in row.get("bindings", []):
|
||||
text = (root / binding["file"]).read_text(encoding="utf-8")
|
||||
if clean_source(binding["text"]) not in clean_source(text):
|
||||
raise ValueError(f"programming binding changed: {row['id']}")
|
||||
if name == "devices":
|
||||
if row["board"] not in ("Not established", "Reported pass") or row["silicon"] not in ("Not established", "Reported pass"):
|
||||
raise ValueError("invalid device qualification state")
|
||||
for stage in ("board", "silicon"):
|
||||
if row[stage] == "Reported pass":
|
||||
reports = [r for r in row.get("reports", []) if r.get("stage") == stage]
|
||||
if not reports or not all(r.get("revision") == revision and r.get("result") == "pass" and r.get("path") and r.get("profile") for r in reports):
|
||||
raise ValueError(f"device claim requires matching evidence: {row['id']}")
|
||||
sdk = constants((root / "crt/include/retrosoc/hal/dma.h").read_text(), "RS_DMA_REQUEST_")
|
||||
rtl = constants((root / "rtl/ip/peripheral/dma_pkg.sv").read_text(), "DMA_REQUEST_", rtl=True)
|
||||
routes = spec["dma_routes"]
|
||||
if {r["sdk"] for r in routes} != set(sdk) or {r["rtl"] for r in routes} != set(rtl):
|
||||
raise ValueError("DMA route coverage mismatch")
|
||||
if len(routes) != len(sdk) or len({r["sdk"] for r in routes}) != len(routes):
|
||||
raise ValueError("duplicate DMA request mapping")
|
||||
for row in routes:
|
||||
if sdk[row["sdk"]] != rtl[row["rtl"]]:
|
||||
raise ValueError(f"RTL/SDK DMA request mismatch: {row['id']}")
|
||||
if sdk[row["sdk"]] and not row.get("bindings"):
|
||||
raise ValueError(f"missing DMA connection evidence: {row['id']}")
|
||||
|
||||
|
||||
def credit_values(text: str, function: str, masters: int) -> list[int]:
|
||||
"""Read only the bounded if/return form of the two current credit functions."""
|
||||
body = re.search(rf"function automatic[^;]*\b{function}\([^;]*;(.+?)endfunction", text, re.S)
|
||||
if body is None:
|
||||
raise ValueError("credit function missing")
|
||||
branches = re.findall(r"if\s*\((.*?)\)\s*return CountWidth'\((\d+)\);", body[1], re.S)
|
||||
remainder = re.sub(r"if\s*\((.*?)\)\s*return CountWidth'\((\d+)\);", "", body[1], flags=re.S)
|
||||
if clean_source(remainder) != "return'0;":
|
||||
raise ValueError("unsupported credit function shape")
|
||||
allowed = (ast.Expression, ast.BoolOp, ast.And, ast.Or, ast.Compare, ast.Name, ast.Load,
|
||||
ast.Constant, ast.Eq, ast.LtE, ast.GtE, ast.Lt, ast.Gt)
|
||||
parsed = []
|
||||
for condition, value in branches:
|
||||
tree = ast.parse(condition.replace("&&", " and ").replace("||", " or "), mode="eval")
|
||||
if any(not isinstance(node, allowed) or isinstance(node, ast.Name) and node.id != "master" for node in ast.walk(tree)):
|
||||
raise ValueError("unsupported credit condition")
|
||||
parsed.append((compile(tree, "<credit condition>", "eval"), int(value)))
|
||||
return [next((value for expression, value in parsed if eval(expression, {"__builtins__": {}}, {"master": master})), 0) for master in range(masters)]
|
||||
|
||||
|
||||
def collect_programming(root: Path, spec: dict, ids: set[str], revision: str) -> dict:
|
||||
validate_programming(spec, ids, root, revision)
|
||||
result = copy.deepcopy(spec)
|
||||
sdk_text = (root / "crt/include/retrosoc/hal/dma.h").read_text()
|
||||
sdk = constants(sdk_text, "RS_DMA_REQUEST_")
|
||||
channels = constants(sdk_text, "RS_DMA_CHANNEL_")
|
||||
channels.update(constants((root / "crt/include/retrosoc/hal/dma_regs.h").read_text(), "RS_DMA_CHANNEL_"))
|
||||
channels.pop("RS_DMA_CHANNEL_COUNT")
|
||||
if {r["symbol"] for r in spec["channels"]} != set(channels):
|
||||
raise ValueError("DMA channel allocation coverage mismatch")
|
||||
for row in result["dma_routes"]:
|
||||
row["number"] = sdk[row["sdk"]]
|
||||
for row in result["channels"]:
|
||||
row["number"] = channels[row["symbol"]]
|
||||
result["dma_routes"].sort(key=lambda r: r["number"])
|
||||
result["channels"].sort(key=lambda r: r["number"])
|
||||
topology = json.loads((root / "rtl/mini/integration/soc_topology.json").read_text())
|
||||
interrupts = {row["name"]: row["core_bit"] for row in topology["interrupts"]}
|
||||
wiring = clean_source((root / "rtl/mini/top/apb4_periph.sv").read_text())
|
||||
for row in result["dma_routes"]:
|
||||
if row["irq"] is not None and row["irq"] not in interrupts:
|
||||
raise ValueError(f"unknown endpoint interrupt: {row['id']}")
|
||||
row["lp_irq"] = ("Client-dependent" if row["number"] == 0 else
|
||||
str(interrupts[row["irq"]]) if row["irq"] else "No separate line")
|
||||
for row in result["engines"]:
|
||||
if row["irq"] not in interrupts:
|
||||
raise ValueError(f"unknown DMA engine interrupt: {row['id']}")
|
||||
routed = re.findall(r"s_hp_plic_source\[(\d+)\]=" + re.escape(row["hp_signal"]) + ";", wiring)
|
||||
if len(routed) != 1:
|
||||
raise ValueError(f"HP interrupt routing changed: {row['id']}")
|
||||
row["lp_irq"], row["hp_irq"] = interrupts[row["irq"]], int(routed[0])
|
||||
crossbar = (root / "rtl/mini/top/axi4_data_crossbar.sv").read_text()
|
||||
result["read_credits"] = credit_values(crossbar, "master_read_limit", 8)
|
||||
result["write_credits"] = credit_values(crossbar, "master_write_limit", 8)
|
||||
result["timing"] = {
|
||||
"uart": [{"clock": c, "target": b, **uart_timing(c, b)} for c, b in ((24_000_000, 115200), (72_000_000, 115200))],
|
||||
"i2c": [{"clock": 24_000_000, "target": rate, **i2c_timing(24_000_000, rate)} for rate in (100_000, 400_000, 1_000_000)],
|
||||
"timer": [{"clock": 24_000_000, "target_ms": t, **timer_timing(24_000_000, t)} for t in (1, 1000)],
|
||||
"i2s": [{"clock": 18_432_000, "target": rate, "bits": bits, **i2s_timing(18_432_000, rate, bits)} for bits in (16, 24) for rate in (48000, 96000)],
|
||||
}
|
||||
result["budgets"] = [
|
||||
{"name": "VGA RGB565, 640 x 480, two buffers", **buffer_budget("frame", width=640, height=480, count=2)},
|
||||
{"name": "Odd-width example, 641 x 480, one buffer", **buffer_budget("frame", width=641, height=480)},
|
||||
*[{"name": f"Stereo {bits}-bit, 48 kHz, 10 ms, two buffers", **buffer_budget("audio", rate=48000, bits=bits, milliseconds=10, count=2)} for bits in (16, 24)],
|
||||
{"name": "Eight 64-byte DMA TCDs", **buffer_budget("descriptors", descriptors=8)},
|
||||
]
|
||||
return result
|
||||
@@ -5,11 +5,14 @@ import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from publications.programming_reference import collect_programming, dependencies
|
||||
|
||||
REFERENCE = "publications/datasheets/system-reference.json"
|
||||
|
||||
|
||||
def source_paths(reference: dict) -> set[str]:
|
||||
paths = set(reference["sources"])
|
||||
paths.update(dependencies(reference.get("programming", {})))
|
||||
for row in [*reference["support"], *reference["limitations"]]:
|
||||
paths.update(row["sources"])
|
||||
paths.update(row.get("tests", []))
|
||||
@@ -77,4 +80,5 @@ def collect_system_reference(root: Path, revision: str) -> dict:
|
||||
layout.append({"name": title, "address": f"0x{values['ADDRESS']:08X}",
|
||||
"max_size_kib": values["MAX_SIZE"] // 1024})
|
||||
reference["boot_layout"] = layout
|
||||
reference["programming"] = collect_programming(root, reference["programming"], {row["id"] for row in index}, revision)
|
||||
return reference
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Cross-IP publication tables must match routed hardware and executable SDK math."""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from publications import programming_reference as pr
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
REFERENCE = json.loads((ROOT / "publications/datasheets/system-reference.json").read_text(encoding="utf-8"))
|
||||
IDS = {row["id"] for row in REFERENCE["support"]}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def spec():
|
||||
return copy.deepcopy(REFERENCE["programming"])
|
||||
|
||||
|
||||
def check(spec):
|
||||
pr.validate_programming(spec, IDS, ROOT, REFERENCE["source_revision"])
|
||||
|
||||
|
||||
def test_complete_dma_routes_and_native_credit_boundary(spec):
|
||||
result = pr.collect_programming(ROOT, spec, IDS, REFERENCE["source_revision"])
|
||||
assert [r["number"] for r in result["dma_routes"]] == list(range(14))
|
||||
assert [r["number"] for r in result["channels"]] == list(range(8))
|
||||
assert result["read_credits"] == [4, 4, 4, 2, 2, 1, 0, 4]
|
||||
assert result["write_credits"] == [0, 2, 2, 1, 1, 1, 0, 2]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mutation", ["missing", "duplicate", "unknown_ip", "connection"])
|
||||
def test_route_inventory_rejects_ambiguous_or_unwired_entries(spec, mutation):
|
||||
if mutation == "missing":
|
||||
spec["dma_routes"].pop()
|
||||
elif mutation == "duplicate":
|
||||
spec["dma_routes"].append(copy.deepcopy(spec["dma_routes"][0]))
|
||||
elif mutation == "unknown_ip":
|
||||
spec["dma_routes"][0]["ips"] = ["nonexistent"]
|
||||
else:
|
||||
spec["dma_routes"][1]["bindings"][0]["text"] = ".missing_port(missing_signal)"
|
||||
with pytest.raises(ValueError, match="coverage|duplicate|unknown programming IP|binding changed"):
|
||||
check(spec)
|
||||
|
||||
|
||||
def test_sdk_request_number_drift_is_rejected(spec, monkeypatch):
|
||||
original = pr.constants
|
||||
|
||||
def drift(text, prefix, rtl=False):
|
||||
result = original(text, prefix, rtl)
|
||||
if prefix == "RS_DMA_REQUEST_":
|
||||
result["RS_DMA_REQUEST_DVP_RX"] = 14
|
||||
return result
|
||||
|
||||
monkeypatch.setattr(pr, "constants", drift)
|
||||
with pytest.raises(ValueError, match="RTL/SDK DMA request mismatch"):
|
||||
check(spec)
|
||||
|
||||
|
||||
def test_device_board_claim_needs_a_matching_report(spec):
|
||||
spec["devices"][0]["board"] = "Reported pass"
|
||||
with pytest.raises(ValueError, match="requires matching evidence"):
|
||||
check(spec)
|
||||
|
||||
|
||||
def test_channel_coverage_cannot_drop_the_boot_channel(spec):
|
||||
spec["channels"] = [r for r in spec["channels"] if r["id"] != "hp"]
|
||||
with pytest.raises(ValueError, match="channel allocation coverage"):
|
||||
pr.collect_programming(ROOT, spec, IDS, REFERENCE["source_revision"])
|
||||
|
||||
|
||||
def test_credit_parser_fails_closed_on_unreviewed_shape():
|
||||
with pytest.raises(ValueError, match="unsupported credit function"):
|
||||
pr.credit_values("function automatic int limits(input int master); return compute(master); endfunction", "limits", 8)
|
||||
|
||||
|
||||
def test_memory_payload_packing_and_alignment_are_distinct():
|
||||
frame = pr.buffer_budget("frame", width=640, height=480, count=2)
|
||||
assert frame["storage_bytes"] == 614400 and frame["total_bytes"] == 1228800
|
||||
odd = pr.buffer_budget("frame", width=641, height=480)
|
||||
assert odd["payload_bytes"] == 615360 and odd["storage_bytes"] == 616320
|
||||
assert odd["dma_supported"] is False
|
||||
audio16 = pr.buffer_budget("audio", rate=48000, bits=16, milliseconds=10, count=2)
|
||||
audio24 = pr.buffer_budget("audio", rate=48000, bits=24, milliseconds=10, count=2)
|
||||
assert audio16["total_bytes"] == 3840
|
||||
assert audio24["payload_bytes"] == 2880 and audio24["total_bytes"] == 7680
|
||||
assert pr.buffer_budget("descriptors", descriptors=8)["total_bytes"] == 512
|
||||
|
||||
|
||||
@pytest.mark.parametrize("args", [
|
||||
{"kind": "frame", "width": 0, "height": 480},
|
||||
{"kind": "frame", "width": 65536, "height": 65536},
|
||||
{"kind": "audio", "rate": 44100, "bits": 16, "milliseconds": 1},
|
||||
{"kind": "audio", "rate": 48000, "bits": 32, "milliseconds": 10},
|
||||
{"kind": "descriptors", "descriptors": 1, "alignment": 3},
|
||||
{"kind": "descriptors", "descriptors": 1, "count": 1 << 31},
|
||||
])
|
||||
def test_budget_rejects_invalid_sizes_alignment_and_overflow(args):
|
||||
with pytest.raises(ValueError):
|
||||
pr.buffer_budget(**args)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def sdk_math(tmp_path_factory):
|
||||
if os.name == "nt":
|
||||
pytest.skip("SDK C math comparison runs in the Linux host test environment")
|
||||
cc = shutil.which("cc") or shutil.which("gcc")
|
||||
if cc is None:
|
||||
pytest.skip("host C compiler unavailable")
|
||||
folder = tmp_path_factory.mktemp("publication-math")
|
||||
include = folder / "retrosoc/generated"
|
||||
include.mkdir(parents=True)
|
||||
# The pure calculations do not use MMIO. This only permits unrelated address
|
||||
# accessor functions in i2s_math.c to compile; none are called by this driver.
|
||||
(include / "memory_map.h").write_text("#define RS_SOC_APB4_I2S_BASE 0U\n")
|
||||
driver = folder / "compare.c"
|
||||
driver.write_text(r'''
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <retrosoc/hal/uart.h>
|
||||
#include <retrosoc/hal/i2c.h>
|
||||
#include <retrosoc/hal/timer.h>
|
||||
#include <retrosoc/hal/i2s.h>
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 4) return 2;
|
||||
uint32_t clock = (uint32_t)strtoul(argv[2], NULL, 10);
|
||||
uint32_t value = (uint32_t)strtoul(argv[3], NULL, 10);
|
||||
if (!strcmp(argv[1], "uart")) {
|
||||
rs_uart_timing_t t = {0};
|
||||
int s = rs_uart_timing_calculate(clock, value, &t);
|
||||
printf("%d %u %u\n", s, t.baud_integer, t.baud_fraction);
|
||||
} else if (!strcmp(argv[1], "i2c")) {
|
||||
rs_i2c_timing_t t = {0};
|
||||
int s = rs_i2c_timing_calculate(clock, value, &t);
|
||||
printf("%d %u %u %u %u %u %u %u\n", s, t.scl_low_cycles, t.scl_high_cycles,
|
||||
t.start_hold_cycles, t.start_setup_cycles, t.data_setup_cycles, t.stop_setup_cycles, t.bus_free_cycles);
|
||||
} else if (!strcmp(argv[1], "timer")) {
|
||||
rs_timer_period_t t = {0};
|
||||
int s = rs_timer_period_from_ms(clock, value, &t);
|
||||
printf("%d %u %u\n", s, t.prescale, t.load);
|
||||
} else if (!strcmp(argv[1], "i2s") && argc == 5) {
|
||||
uint8_t a = 0, b = 0;
|
||||
int s = rs_i2s_div_from_hz(clock, value, (uint32_t)strtoul(argv[4], NULL, 10), &a, &b);
|
||||
printf("%d %u %u\n", s, (unsigned)a, (unsigned)b);
|
||||
} else return 2;
|
||||
return 0;
|
||||
}
|
||||
''', encoding="utf-8")
|
||||
exe = folder / "compare"
|
||||
files = [ROOT / f"crt/src/hal/{name}_math.c" for name in ("uart", "i2c", "timer", "i2s")]
|
||||
subprocess.run([cc, "-std=c11", "-Wall", "-Wextra", "-Werror", "-I", str(folder),
|
||||
"-I", str(ROOT / "crt/include"), str(driver), *map(str, files), "-o", str(exe)], check=True)
|
||||
return exe
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind,clock,value,bits", [
|
||||
("uart", 24000000, 115200, 0), ("uart", 72000000, 115200, 0),
|
||||
("uart", 16000000, 1000000, 0), ("uart", 16000000, 1000001, 0), ("uart", 0, 115200, 0),
|
||||
("i2c", 24000000, 100000, 0), ("i2c", 24000000, 400000, 0),
|
||||
("i2c", 24000000, 1000000, 0), ("i2c", 24000000, 1000001, 0), ("i2c", 24000000, 1, 0),
|
||||
("timer", 24000000, 1, 0), ("timer", 24000000, 1000, 0),
|
||||
("timer", 4294967295, 1001, 0), ("timer", 1, 1, 0), ("timer", 24000000, 0, 0),
|
||||
("i2s", 18432000, 48000, 16), ("i2s", 18432000, 96000, 16),
|
||||
("i2s", 18432000, 48000, 24), ("i2s", 18432000, 96000, 24),
|
||||
("i2s", 18432000, 44100, 16), ("i2s", 18432000, 48000, 32),
|
||||
])
|
||||
def test_published_integer_math_matches_sdk_c(sdk_math, kind, clock, value, bits):
|
||||
output = subprocess.check_output([str(sdk_math), kind, str(clock), str(value), *([str(bits)] if kind == "i2s" else [])], text=True)
|
||||
status, *actual = map(int, output.split())
|
||||
try:
|
||||
expected = getattr(pr, kind + "_timing")(clock, value, *([bits] if kind == "i2s" else []))
|
||||
except ValueError:
|
||||
expected = None
|
||||
assert (status == 0) == (expected is not None)
|
||||
if expected is not None:
|
||||
fields = {"uart": ("integer", "fraction"), "timer": ("prescale", "load"), "i2s": ("sclk_div", "lrck_div")}
|
||||
wanted = expected["cycles"] if kind == "i2c" else [expected[key] for key in fields[kind]]
|
||||
assert actual == wanted
|
||||
Reference in new issue
Block a user