PIC24 / dsPIC33 M0 Reference — Encodings, SFRs, Integration Map
Branch: feat/pic24-m0 · Status: implementation reference (no engine wiring yet) · Target: dsPIC33FJ32MC204 (issue #1426)
Sources (retrieved 2026-10-18, facts only): - DS70000157G — 16-Bit MCU and DSC Programmer's Reference Manual (instruction encodings) - DS70283H — dsPIC33FJ32MC202/204 and dsPIC33FJ16MC304 Data Sheet (memory map, SFRs, peripherals)
1. Target facts (DS70283H)
| Item | Value |
|---|---|
| Program flash | 32 KB; GOTO at 0x000000, reset at 0x000004, IVT at 0x0000FE, user flash from 0x000200 |
| Data SRAM | 2 KB: X 0x0800–0x0BFF, Y 0x0C00–0x0FFF; near-data to 0x1FFF |
| SFR space | 0x0000–0x07FF |
| UART1 | U1MODE 0x0220, U1STA 0x0222, U1TXREG 0x0224 (write char here) |
| Core clock | up to 40 MIPS; reset FRC — board-level cpu_hz to be declared |
2. Instruction encodings (DS70000157G, verbatim extraction)
k = literal, s = source Wn, d = destination Wn. 24-bit words, stored as 3 little-endian bytes per instruction in an XC16 executable section (the loader adds the internal slot pad — see §3.1).
| Instruction | Encoding |
|---|---|
NOP |
0000 0000 xxxx xxxx xxxx xxxx |
NOPR |
1111 1111 xxxx xxxx xxxx xxxx |
GOTO Expr |
1st 0000 0100 nnnn nnnn nnnn nnn0 · 2nd 0000 0000 0000 0000 0nnn nnnn |
GOTO Wn |
0000 0001 0100 0000 0000 ssss |
BRA Expr |
0011 0111 nnnn nnnn nnnn nnnn (slit16 offset) |
BRA Wn |
0000 0001 0110 0000 0000 ssss |
CALL Expr |
1st 0000 0010 nnnn nnnn nnnn nnn0 · 2nd 0000 0000 0000 0000 0nnn nnnn |
RCALL Expr |
0000 0111 nnnn nnnn nnnn nnnn |
RETURN |
0000 0110 0000 0000 0000 0000 |
MOV #lit16, Wnd |
0010 kkkk kkkk kkkk kkkk dddd |
MOV{.B} f, Wnd |
1000 0fff ffff ffff ffff dddd |
MOV Wns, f |
1000 1fff ffff ffff ffff ssss |
MOV{.B} f {,WREG} |
1011 1111 1BDf ffff ffff ffff |
MOV{.B} WREG, f |
1011 0111 1B1f ffff ffff ffff |
MOV{.B} Ws, Wd |
0111 1www wBhh hddd dggg ssss (address-modifier form) |
MOV.B #lit8, Wnd |
1011 0011 1100 kkkk kkkk dddd |
MOV{.B} [Ws+Slit10], Wnd |
1001 0kkk kBkk kddd dkkk ssss |
MOV{.B} Wns, [Wd+Slit10] |
1001 1kkk kBkk kddd dkkk ssss |
Cross-check note: the 2-word address instructions carry the target in bits spread across both words (bit 0 of the 1st word's field is always 0 — word-aligned). Decode with unit tests using hand-computed vectors; validate against MPLAB
disasmoutput once a toolchain is available.
3. M0 smoked sequence (fixture program)
Addresses are PC units (2 per instruction), user flash as XC16 lays it out:
0x000200: MOV #0x48, W0 ; 'H'
0x000202: MOV W0, 0x0224 ; U1TXREG
0x000204: NOP ×6
0x000210: BRA $-1 ; self-loop (PC = PC + 2 + 2*(-1))
The fixture ELF carries the real reset vector: a 2-word GOTO 0x200 at PC 0,
so the model resets to PC = 0 and vectors through it (DS70283H Figure 7-1,
"Reset – GOTO Instruction" / "Reset – GOTO Address"). e_entry is 0x200.
Acceptance: UART sink receives H; PC/SP initialization deterministic; repeat runs identical.
3.1 XC16 ELF layout (verified 2026-10-22 against real XC16 v1.70 output)
The M0 note first called the layout "provisional"; #1447 then claimed the ELF
stores 3 bytes per instruction, reading DS50002071F's "Program Counter
Unit" glossary entry ("in an executable section, 2 program counter units are
equivalent to 3 bytes"). That is the 16-bit assembler's conceptual address
arithmetic, not the ELF file layout — and it was disproved by building the
committed dspic33fj32mc204-xc16-uart-smoke.elf with XC16 v1.70 and
inspecting it:
- every executable section (
.text,.reset,.ivt.*,.aivt.*) has a size that is a multiple of 4; - the bytes are 4-byte slots:
0f 80 20 00 | 0e ff 20 00 | …— the 24-bit little-endian word plus one pad byte (the "phantom byte" of the 4-byte container, DS50002071F glossary); - the disassembler decodes with a 4-byte stride while PC addresses advance
by 2 per instruction (
0x200,0x202, …), i.e. addresses are PC units, 2 per instruction; .resetholds the 2-wordGOTOas two slots; each.ivt.*/.aivt.*entry is one slot holding a 24-bit ISR address as data (DS70283H §7.1: "Each interrupt vector contains a 24-bit-wide address").
The model therefore:
- keeps
pcin PC units (PC_UNITS_PER_INSTRUCTION = 2) and one 24-bit word per 4-byte flash slot (INSTR_SLOT_BYTES = 4) — identical to the ELF layout, so the parsers copy executable sections verbatim (xc16_section_is_slot_alignedvalidates the size); - places each section at its own PC-unit address from the section table (program headers can span sections at non-contiguous addresses);
- loads the PC from the vector table on exception entry
(
INTCON2.ALTIVTselects the AIVT at0x104); - skips non-executable PIC24 program-memory sections (PSV/table reads are not modelled).
This matches the datasheet wording "addresses are incremented or decremented by two during code execution" (DS70283H §4.2.3) and the reset/vector figures (DS70283H §4 and §7.1).
3.2 DSP MAC class (M1c, verified 2026-10-23 against DS70000157G examples and real XC16 output)
Implemented in crates/core/src/cpu/pic24.rs; encodings are cross-checked
against xc16-as output in dsp_words_match_xc16_as, and the semantics are
pinned by the PRM's own worked examples in the dsp_* unit tests.
| Instruction | Encoding (24-bit) | Notes |
|---|---|---|
MAC Wm*Wn, Acc {,AWB} |
1100 0mmm A0xx yyii iijj jjaa |
mmm = Table 5-12 |
MPY Wm*Wn, Acc |
1100 0mmm A0xx yyii iijj jj11 |
product replaces Acc |
MPY.N Wm*Wn, Acc |
1100 0mmm A1xx yyii iijj jj11 |
negated product |
MSC Wm*Wn, Acc {,AWB} |
1100 0mmm A1xx yyii iijj jjaa |
Acc −= product |
MAC/MPY Wm*Wm, Acc |
1111 00mm A0xx yyii iijj jj0b |
square, no write-back |
CLR Acc {,AWB} |
1100 0011 A0xx yyii iijj jjaa |
clears Acc and its OA/SA (OB/SB) |
MOVSAC Acc {,AWB} |
1100 0111 A0xx yyii iijj jjaa |
prefetch/write-back only |
LAC Ws/[...], {#Slit4,} Acc |
1100 1010 Awww wrrr rggg ssss |
32-bit shifter |
SAC Acc, {#Slit4,} Wd/[...] |
1100 1100 Awww wrrr rhhh dddd |
40-bit shift, SATDW |
SAC.R Acc, {#Slit4,} Wd/[...] |
1100 1101 Awww wrrr rhhh dddd |
+ RND rounding |
Prefetch fields (Tables 5-7…5-10): iiii/jjjj select the X (W8/W9) and
Y (W10/W11) operation — no prefetch (0100), [Wn], [Wn]±2/4/6, and the
[W9+W12]/[W11+W12] register-offset mode (16-bit signed offset, no
register update); xx/yy select the W4–W7 destinations. aa is the
write-back selector (Table 5-13): W13, [W13]+=2, none.
CORCON modes: IF (bit 0, fractional multiplies are left-shifted one),
RND (bit 1, biased vs convergent rounding), SATDW (bit 5),
SATB/SATA (bits 6/7), ACCSAT (bit 4, 9.31 vs 1.31), US (bit 12,
unsigned multiplies).
Status (SR high byte, §2.16.4): OA/OB = guard-bit overflow (set when
saturation is disabled or in super mode), SA/SB = sticky saturation,
OAB/SAB derived; a write to SR can only clear them, and clearing SAB
clears SA/SB. CLR clears the accumulator's own pair.
Rounding (SAC.R, write-back): conventional rounds up when the low word
is ≥ 0x8000; convergent rounds a 0x8000 tie to even. SATDW saturates a
store to 0x7FFF/0x8000 when the shifted value needs the guard bits (PRM SAC
examples 1–2).
Approximations (documented, not guessed — no PRM example covers them):
LAC's shifter is 32-bit (its examples 1–2 pin this); with super saturation
and an overflowing left shift the exact 40-bit result is kept; MPY.N
follows its "OA, OB, OAB" status list literally (no saturation/SA/SB);
write-back applies SATDW like SAC.R.
Verification: 16 dsp_* unit tests use the PRM's MAC/MPY/MPY.N/MSC/CLR/
MOVSAC/LAC/SAC/SAC.R examples as golden vectors, plus saturation-mode,
rounding-tie, unsigned, sticky-flag and full prefetch-table coverage; a real
XC16 v1.70 firmware (firmware/xc16-dsp-mac.c) boots and reproduces the
example-1 sequence (crates/core/tests/pic24_xc16_dsp.rs).
3.3 Motor Control PWM + QEI (M2, implemented 2026-10-23)
Sources: DS70187E (Section 14, MCPWM), DS70208C (Section 15, QEI) and DS70283H Tables 4-8/4-10 (SFR maps), 7-1 (vectors), 1-1 (pins) and 10-2 (PPS).
SFRs. PWM1: P1TCON 0x01C0, P1TMR 0x01C2, P1TPER 0x01C4,
P1SECMP 0x01C6, PWM1CON1 0x01C8, PWM1CON2 0x01CA,
P1DTCON1 0x01CC, P1DTCON2 0x01CE, P1FLTACON 0x01D0,
P1OVDCON 0x01D4, P1DC1-3 0x01D6/0x01D8/0x01DA. QEI1:
QEI1CON 0x01E0, DFLT1CON 0x01E2, POS1CNT 0x01E4, MAX1CNT 0x01E6.
Interrupts: IFS3 0x008A (FLTA1 bit 15, QEI bit 9, PWM1 bit 8),
IEC3 0x009A, IPC14 0x00C0 (QEIIP 10-8, PWM1IP 6-4),
IPC15 0x00C2 (FLTA1IP 14-12). Inputs: RPINR12 0x0698 (FLTA1R),
RPINR14 0x069C (QEB1R 12-8, QEA1R 4-0), RPINR15 0x069E (INDX1R 4-0).
UPDN is a PPS output (function 11010).
Pins. PWM1H1/L1 = RB14/RB15, PWM1H2/L2 = RB12/RB13, PWM1H3/L3 = RB10/RB11 (fixed peripheral outputs; they override the GPIO/PPS drive). QEA/QEB/INDX are remappable inputs.
PWM model. Time base: free running, single event, continuous up/down and up/down with double update; 1:1/1:4/1:16/1:64 input prescaler and 1:1…1:16 output postscaler (ignored in single-event/double-update modes, §14.9). Edge-aligned outputs go active at TMR = 0 and inactive at the duty match; center-aligned outputs go inactive on the up-count match and active on the down-count match (§14.11.3). Duty 0 is inactive for the whole period and duty > PTPER is active for the whole period. P1TPER and P1DC1-3 are double-buffered; the buffers load at the period boundary (TMR = 0, and at TMR = PTPER for double update) or immediately when PTEN = 0 or IUE = 1 (§14.12). Complementary pairs insert dead time from units A/B before each pin is driven active; DTSxA/DTSxI select the unit per edge (§14.14). P1OVDCON overrides the pins with POUT; the FLTA1 fault input (active low) forces the FAOV states, with high-side priority in complementary mode, and restores at the next period/half-period boundary in cycle-by-cycle and latched modes (§14.15).
QEI model. x4 decoding on every edge and x2 on Phase A edges, using the lead/lag table (§15.4); SWPAB swaps the phases. Match-reset modes roll POS1CNT to 0 at MAXCNT and load MAXCNT at the zero underflow (§15.5.2). Index-reset modes reset to 0 when travelling forward and load MAXCNT in reverse, honouring POSRES and the IMV match value (§15.5.3). In index modes POS1CNT = MAXCNT+1 (up) or 0xFFFF (down) sets CNTERR and, unless CEID is set, raises QEIIF; further errors are ignored until CNTERR is cleared. Timer mode (QEIM = 001) counts the internal prescaled clock or QEA edges, with TQGATE, UDSRC and period match at MAXCNT (§15.6). The digital filter (QEOUT/QECK) accepts a level after three equal filter clocks (§15.3).
Interrupts. The fabric now covers IFS3/IEC3 and IPC14/15, and selects
the highest-priority pending source (lower vector wins a tie). While wiring
it, the M1a vector table was corrected against DS70283H Table 7-1:
IC2/OC2/T2/T3 are vectors 13/14/15/16, not 12/13/14/15 (IFS0 bit 4 is
reserved, so the vector is bit + 8).
Verification. Unit tests cover the PWM time base modes, complementary
PWM with dead time, override, fault entry/release, the postscaler, the QEI
decoder in x2/x4, match/index resets, CNTERR/CEID, timer mode and the
digital filter; the real XC16 firmware xc16-m2-pwm-qei.c configures both
blocks and the integration test checks the register image, the PWM pads and
injected quadrature edges (crates/core/tests/pic24_xc16_m2.rs).
Approximations (documented, not guessed): active-high PWM polarity and PWMPIN = 1 (erased-device FPOR) since configuration bits are not modelled; center-aligned endpoint ticks keep both endpoints per Equation 14-2 (the FRM does not pin the compare order at an endpoint); index recognition uses the INDX rising edge plus the IMV match rather than latching a quadrature transition (§15.5.3.1); diagonal quadrature transitions are ignored; PWM/QEI inputs are sampled once per retired step.
3.4 ADC1 + SPI1 + I2C1 (M3, implemented 2026-10-23)
Sources: DS70183D (Section 16, ADC), DS70206 (Section 18, SPI), DS70000195 (Section 19, I2C) and DS70283H Tables 4-11/4-13/4-15 (SFR maps), 7-1 (vectors) and 1-1 (pins).
SFRs. ADC1: ADC1BUF0-15 0x0300-0x031E, AD1CON1 0x0320,
AD1CON2 0x0322, AD1CON3 0x0324, AD1CHS123 0x0326,
AD1CHS0 0x0328, AD1PCFGL 0x032C, AD1CSSL 0x0330. SPI1:
SPI1STAT 0x0240, SPI1CON1 0x0242, SPI1CON2 0x0244, SPI1BUF 0x0248.
I2C1: I2C1RCV 0x0200, I2C1TRN 0x0202, I2C1BRG 0x0204,
I2C1CON 0x0206, I2C1STAT 0x0208, I2C1ADD 0x020A,
I2C1MSK 0x020C. Interrupts: IFS1 0x0086 (MI2C1 bit 1, SI2C1 bit 0),
IEC1 0x0096, IPC3 0x00AA (AD1IP 6-4) and IPC4 0x00AC
(MI2C1IP 6-4, SI2C1IP 2-0); IFS0 carries AD1IF (13), SPI1IF (10) and
SPI1EIF (9). Vectors: SPI1E 17, SPI1 18, ADC1 21, SI2C1 24, MI2C1 25.
Pins. AN0-AN8 = RA0, RA1, RB0-RB3, RC0-RC2 (DS70283H Table 1-1);
AD1PCFGL bit n = 1 makes ANn digital. SPI1 SDI/SDO/SCK/SS and I2C1
SDA/SCL are PPS-remappable inputs/outputs; this model does not route them
through pads (see approximations).
ADC model. AD1CON1: ADON, AD12B, FORM (00 unsigned integer,
01 signed integer, 10 unsigned fraction, 11 signed fraction), SSRC, SIMSAM,
ASAM, SAMP and DONE. Writing ADON 0→1 powers the ADC; sampling starts
automatically only when ASAM = 1, otherwise software sets SAMP (DS70183D
Register 16-1). With SSRC = 000 software clears SAMP to start the conversion;
SSRC = 111 uses the internal
counter (SAMC × TAD) and ASAM restarts sampling after each sequence.
SSRC = 010 is triggered by the TMR3 period match and SSRC = 011 by the
PWM1 period/half-period match; SSRC = 001 (INT0) is not driven because the
INT0 pin edge is not modelled. TAD = (ADCS + 1) × TCY; a 10-bit
conversion is 12 TAD and a 12-bit conversion 14 TAD (DS70183D §16.4.4).
FORM results are left-justified for the fractional modes. CHPS selects
1-4 channels per sequence (CH0 + CH1-3 from CH123SA); CSCNA walks the
AD1CSSL scan list for CH0; ALTS uses MUX A for the first sequence and MUX B
for the next (switching only after the sequence completes). Results
land in the 16-word buffer; BUFM splits it into two 8-word halves whose
fill status is the read-only BUFS bit. SMPI sets the interrupt period
(every SMPI+1 sequences, as DS70183D §16.4.6 defines for devices without
DMA). Negative inputs (CH0NA, CH123NA) subtract the selected ANx code,
clamped to the converter range; PCFG = 1 reads 0.
SPI model. Master writes to SPI1BUF start a transfer whose duration is
bits × PPRE × SPRE TCY (PPRE 1/4/16/64, SPRE 1-8; DS70206C Equation 18-1:
FSCK = FCY / (PPRE × SPRE)). MODE16 exchanges two bytes (high first).
SPITBF clears when TXB moves into the shift register; a second write while
shifting fills the one-deep TX slot and sets SPITBF again. The received word
lands in SPI1BUF, sets SPIRBF and SPI1IF; an unread previous word is
discarded, sets SPIROV and SPI1EIF (SPI1IF remains set from that previous
word). SPI1CON2 keeps the
framed-mode bits (the framing protocol itself is not driven). Slave mode
loads TXB on a SPI1BUF write and is clocked through the
spi_slave_clock() test seam. Attached SpiDevices are called once per
byte (first device only; no SS routing), so the example declares a wire
loopback (type: "loopback") that returns every byte.
I2C model. I2C1CON implements I2CEN, SEN, RSEN, PEN, RCEN and
ACKEN/ACKDT with the datasheet's hardware-clear behaviour; each operation
takes its wire time ((BRG + 1) TCY per SCL, nine SCL for a byte and
one for start/stop; the 1.111 MHz high-time correction term in the
DS70000195 BRG formula is omitted). A transmit byte is the address after a start/restart
(7-bit; 11110xx starts a 10-bit prefix decode, but the I2cDevice slave
contract is a 7-bit u8 address, so a 10-bit address cannot match a device
here) and data thereafter. Address matching uses
I2cDevice::claims_address, then select_address/start; writes go to
write(), RCEN reads from read(), and PEN calls stop(). ACKSTAT
reports a missing slave's NACK; RBF/TBF track the buffers; reading
I2C1RCV clears RBF; I2COV/IWCOL/BCL are clear-on-0. ADD10 is set at the
second byte of a matched 10-bit address and cleared at Stop
(DS70283H Register 18-2); the 7-bit I2cDevice contract has no 10-bit
match, so a master 10-bit probe is NACKed and ADD10 stays clear. The TMP102 model is
attached by the session builder from the manifest's i2c1 entries
(type: "tmp102", pointer register + 12-bit left-justified temperature).
Slave mode is register-level plus test seams (i2c_slave_address,
i2c_slave_write, i2c_slave_read, i2c_slave_stop); there is no
external master model.
Verification. Unit tests cover all four FORM formats in 10- and 12-bit
modes, ASAM/SSRC = 111, the TMR3 trigger, the scan list, the split buffer,
SMPI, SPI byte/word transfers with the prescaler timing, SPIROV, the slave
seam, an I2C TMP102 temperature read, a missing-slave NACK, I2COV and the
slave seams, plus the IFS1/IPC3/IPC4 vectors. The session-path test removes
both external devices, then each one alone, and requires M3 FAIL every
time. Booting the firmware also
added four base-ISA encodings the XC16 startup uses: 3-operand SUB,
INC Ws,Wd (C/DC/N/OV/Z), and the WREG forms of CLR Wd/SETM Wd
(no status flags; DS70283H Table 22-2 #15/#39/#69), pinned by xc16-as words. The real XC16 firmware
xc16-m3-adc-spi-i2c.c samples AN4, loops 0xA5 through SPI1 and reads the
TMP102; crates/core/tests/pic24_xc16_m3.rs checks the results,
"M3 OK\n" on UART1 and the register image.
Approximations (documented, not guessed): ADRC = 1 (internal RC)
reuses the (ADCS + 1) × TCY TAD formula because the RC frequency is not
characterized here; a conversion's leftover cycles at a step boundary are
not carried into the next channel; the ADC's INT0 trigger and the
simultaneous-sample (SIMSAM) sample instant are not modelled (the channels
convert sequentially); SPI SS/CS routing is absent (first device answers,
the example uses a wire loopback); I2C ACK bits are not delivered to the
slave model (the TMP102 does not need them); 10-bit addressing is decoded
but cannot match a 7-bit slave address; electrical pin mapping of
ANx/SPI/I2C is not routed through the pad model.
4. Engine integration map (M0 wiring)
| Concern | File | Change |
|---|---|---|
| Arch variant | crates/config/src/chip.rs |
add Pic24 (+ serde pic24, alias dspic33) |
| CPU module | crates/core/src/cpu/pic24.rs, cpu/mod.rs |
new module + pub use; implement Cpu trait (mirror Avr for CPU-internal USART + serial sink) |
| Snapshot | crates/core/src/snapshot.rs |
add the PIC24 snapshot variant (verify enum shape) |
| Arch policy | crates/core/src/system/arch_policy.rs |
map Pic24 → machine family + ELF machine (verify the Microchip EM value; keep None if unverified) |
| Session builder | crates/core/src/system/builder/pic24.rs (+ mod) |
new builder mirroring avr.rs (bus, console sink, program load) |
| CLI | crates/cli/src/commands/{run,test}.rs |
arch dispatch arms |
| Test expectations | crates/core/tests/{one_arch_policy,register_coverage,session_catalog}.rs |
extend arch lists/panics |
| Chip + board | configs/chips/dspic33fj32mc204.yaml, configs/systems/… |
per docs/board_onboarding_playbook.md |
| Example package | examples/dspic33fj32mc204/ |
system.yaml, README.md, REQUIRED_DOCS.md, EXTERNAL_COMPONENTS.md, VALIDATION.md |
5. Non-goals for M0
DSP MAC/accumulators, interrupts beyond reset, timers, PWM, QEI, ADC, SPI, I2C — each is its own milestone with its own validation (see the internal spec: docs/strategy/targets/dspic33fj32mc204-onboarding-spec.md).
6. Full onboarding gate (repo policy)
Promotion to "supported" requires the complete P0–P5 package (chip + system + firmware + example docs + validation evidence + unsupported-instruction audit). Nothing here claims support before that gate passes.