flowrs-engine/tools/qemu-mips-wasm/fixtures/pps/main.c
flowRS 49c9d04fbc flowRS engine, cut from flowRS bd343e051a2e
Built alone from an empty CARGO_HOME, its tests passed, and flowrsc checked, ran and exported a
document from the conformance corpus; the export built alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 13:37:09 -05:00

349 lines
10 KiB
C

/*
* The PIC32 twin's bare-metal fixture for the PIC32MX1xx/2xx, whose peripherals reach their
* pins through peripheral pin select (PPS; PIC32 FRM Section 12.3.1, DS60001120F). Registers
* only, built with clang (build.sh), for the PIC32MX250F128B: every address, IRQ, vector and PPS
* code below is the part's, from its EDC file (DS60001168L Tables 7-1, 11-1, 11-2).
*
* UART1 at 115200, U1TX on RPB3 (RPB3R = 0001): the lines below;
* RB13: toggled every 10000 core-timer counts by polling Count -- 1 kHz from Count's
* SYSCLK/2 = 20 MHz;
* RB14: toggled by the core timer's interrupt every 1 ms, priority 7 -- 500 Hz;
* RB8: toggled by Timer2's interrupt, priority 3, PBCLK/8/20000 = 250 Hz periods -- 125 Hz;
* OC1: PWM from Timer3 at 1 kHz, 25 %, on RPB7 (RPB7R = 0101) for the first 100 ms, then
* moved to RPB15: RB7 stops, RB15 goes on.
*
* Then, once: INT4 read through PPS counts the rising edges RB5 makes through a jumper the
* runner wires to RA0: none with INT4R = 0010 (RPB4), five of nine with 0000 (RPA0); change
* notice by port --
* CNCONB.ON, CNENB.11 -- counts the four changes RB10 makes on RB11 through a jumper, each
* cleared by reading PORTB; an analog pin (ANSELB.2 set at reset) held high reads 0 until its
* ANSEL bit is cleared; RB4 reads its pull-up (CNPUB); with CFGCON.IOLOCK set (after the SYSKEY
* unlock) a write to RPB15R changes nothing, and with DEVCFG3.IOL1WAY 1 IOLOCK does not clear.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#define SFR(a) (*(volatile uint32_t *)(a))
#define CLR 1
#define SET 2
#define INV 3
#define REG(a, op) SFR((a) + 4 * (op))
#define U1MODE 0xBF806000u
#define U1STA 0xBF806010u
#define U1TXREG 0xBF806020u
#define U1BRG 0xBF806040u
#define T2CON 0xBF800800u
#define TMR2 0xBF800810u
#define PR2 0xBF800820u
#define T3CON 0xBF800A00u
#define TMR3 0xBF800A10u
#define PR3 0xBF800A20u
#define OC1CON 0xBF803000u
#define OC1R 0xBF803010u
#define OC1RS 0xBF803020u
#define INTCON 0xBF881000u
#define IFS0 0xBF881030u
#define IFS1 0xBF881040u
#define IEC0 0xBF881060u
#define IEC1 0xBF881070u
#define IPC0 0xBF881090u
#define IPC2 0xBF8810B0u
#define IPC4 0xBF8810D0u
#define IPC8 0xBF881110u
#define ANSELA 0xBF886000u
#define TRISA 0xBF886010u
#define ANSELB 0xBF886100u
#define TRISB 0xBF886110u
#define PORTB 0xBF886120u
#define LATB 0xBF886130u
#define CNPUB 0xBF886150u
#define CNCONB 0xBF886170u
#define CNENB 0xBF886180u
#define CNSTATB 0xBF886190u
#define CFGCON 0xBF80F200u
#define DEVID 0xBF80F220u
#define SYSKEY 0xBF80F230u
#define OSCCON 0xBF80F000u
#define INT4R 0xBF80FA10u
#define RPB3R 0xBF80FB38u
#define RPB7R 0xBF80FB48u
#define RPB15R 0xBF80FB68u
static inline uint32_t mfc0_count(void)
{
uint32_t v;
__asm__ volatile("mfc0 %0, $9" : "=r"(v));
return v;
}
static inline void mtc0_compare(uint32_t v)
{
__asm__ volatile("mtc0 %0, $11" : : "r"(v));
}
/* Busy-wait n counts of Count (20 MHz). */
static void wait_counts(uint32_t n)
{
uint32_t t = mfc0_count();
while (mfc0_count() - t < n) {
}
}
static void putc_(char c)
{
while (SFR(U1STA) & (1u << 9)) { /* UTXBF */
}
SFR(U1TXREG) = (uint8_t)c;
}
static void puts_(const char *s)
{
while (*s) {
putc_(*s++);
}
}
static void put_hex(uint32_t v)
{
puts_("0x");
for (int i = 28; i >= 0; i -= 4) {
putc_("0123456789abcdef"[(v >> i) & 0xf]);
}
}
static void put_dec(uint32_t v)
{
char b[11];
int n = 0;
do {
b[n++] = '0' + v % 10;
v /= 10;
} while (v);
while (n) {
putc_(b[--n]);
}
}
static void unlock(void)
{
SFR(SYSKEY) = 0;
SFR(SYSKEY) = 0xAA996655u;
SFR(SYSKEY) = 0x556699AAu;
}
static volatile uint32_t ct_ticks, t2_ticks, ct_next, int4_hits, cn_hits;
void ct_handler(void)
{
ct_ticks++;
ct_next += 20000; /* 1 ms at 20 MHz */
mtc0_compare(ct_next);
REG(IFS0, CLR) = 1u << 0; /* CTIF */
REG(LATB, INV) = 1u << 14; /* RB14 */
}
void t2_handler(void)
{
t2_ticks++;
REG(IFS0, CLR) = 1u << 9; /* T2IF: IRQ 9 */
REG(LATB, INV) = 1u << 8; /* RB8 */
}
void int4_handler(void)
{
int4_hits++;
REG(IFS0, CLR) = 1u << 23; /* INT4IF: IRQ 23 */
}
/* Reading PORTB ends the mismatch; then the flag is cleared (FRM 12.3.3.1). */
void cn_handler(void)
{
(void)SFR(PORTB);
cn_hits++;
REG(IFS1, CLR) = 1u << 14; /* CNBIF: IRQ 46 */
}
void exc_handler(void)
{
uint32_t cause, epc;
__asm__ volatile("mfc0 %0, $13" : "=r"(cause));
__asm__ volatile("mfc0 %0, $14" : "=r"(epc));
puts_("EXC cause=");
put_hex(cause);
puts_(" epc=");
put_hex(epc);
puts_("\n");
for (;;) {
}
}
static void once(void)
{
uint32_t held, digital, nopull, pullup, before, after, lock, unlocked, elsewhere;
/*
* INT4 through PPS: RB5 drives RA0 through the jumper. With INT4R 0010 (RPB4, group 1)
* INT4 is on RB4, which nothing drives, and counts none of RB5's edges; with INT4R 0000
* (RPA0, also the reset value) it counts its five rising edges of nine.
*/
REG(ANSELA, CLR) = 1u << 0;
SFR(INT4R) = 2;
REG(INTCON, SET) = 1u << 4; /* INT4EP: rising */
REG(IPC4, SET) = 2u << 26; /* vector 19: IPC4<28:26> */
REG(TRISB, CLR) = 1u << 5;
REG(LATB, CLR) = 1u << 5;
REG(IFS0, CLR) = 1u << 23;
REG(IEC0, SET) = 1u << 23;
for (int pass = 0; pass < 2; pass++) {
if (pass) {
elsewhere = int4_hits;
REG(LATB, CLR) = 1u << 5;
wait_counts(20000);
SFR(INT4R) = 0;
}
for (int i = 0; i < 9; i++) {
if (i & 1) {
REG(LATB, CLR) = 1u << 5;
} else {
REG(LATB, SET) = 1u << 5;
}
wait_counts(20000);
}
}
puts_("INT4 via RPB4 rising=");
put_dec(elsewhere);
puts_(" via RPA0 rising=");
put_dec(int4_hits - elsewhere);
puts_("\n");
/* Change notice on port B: RB11 enabled, RB10 drives it through the jumper. */
REG(TRISB, CLR) = 1u << 10;
REG(LATB, CLR) = 1u << 10;
SFR(CNCONB) = 1u << 15; /* ON, mismatch mode */
SFR(CNENB) = 1u << 11;
(void)SFR(PORTB); /* latch the inputs */
REG(IPC8, SET) = 2u << 18; /* vector 34: IPC8<20:18> */
REG(IFS1, CLR) = 1u << 14;
REG(IEC1, SET) = 1u << 14;
for (int i = 0; i < 4; i++) {
REG(LATB, INV) = 1u << 10;
wait_counts(20000);
}
REG(IEC1, CLR) = 1u << 14;
puts_("CN port B RB11 changes=");
put_dec(cn_hits);
puts_("\n");
/* RB2 (AN4) held high by the runner: analog at reset, it reads 0 (12.3.4). */
held = (SFR(PORTB) >> 2) & 1;
REG(ANSELB, CLR) = 1u << 2;
digital = (SFR(PORTB) >> 2) & 1;
/* RB4 with nothing on it, then with its pull-up. */
nopull = (SFR(PORTB) >> 4) & 1;
REG(CNPUB, SET) = 1u << 4;
pullup = (SFR(PORTB) >> 4) & 1;
puts_("RB2 analog=");
put_dec(held);
puts_(" digital=");
put_dec(digital);
puts_(" RB4 nopull=");
put_dec(nopull);
puts_(" pullup=");
put_dec(pullup);
puts_("\n");
/* IOLOCK: set after the unlock; a write to RPB15R is then ignored. */
before = SFR(RPB15R);
unlock();
REG(CFGCON, 0) = SFR(CFGCON) | (1u << 13);
SFR(SYSKEY) = 0;
SFR(RPB15R) = 0;
after = SFR(RPB15R);
lock = (SFR(CFGCON) >> 13) & 1;
unlock();
SFR(CFGCON) = SFR(CFGCON) & ~(1u << 13);
SFR(SYSKEY) = 0;
unlocked = !((SFR(CFGCON) >> 13) & 1);
puts_("IOLOCK=");
put_dec(lock);
puts_(" RPB15R kept=");
put_dec(before == 5 && after == 5);
puts_(" cleared=");
put_dec(unlocked);
puts_("\nONCE DONE\n");
}
int main(void)
{
uint32_t next;
int moved = 0;
/* UART1 on RPB3; 115200 at PBCLK 40 MHz with BRGH: 40e6 / (4 * 115200) - 1 = 86. */
REG(ANSELB, CLR) = (1u << 3) | (1u << 13) | (1u << 14) | (1u << 15);
SFR(RPB3R) = 1; /* U1TX (group 1, 0001) */
SFR(U1BRG) = 86;
SFR(U1STA) = (1u << 10) | (1u << 12); /* UTXEN, URXEN */
SFR(U1MODE) = (1u << 15) | (1u << 3); /* ON, BRGH, 8N1 */
puts_("PIC32MX1/2 bare-metal: start\n");
puts_("DEVID=");
put_hex(SFR(DEVID));
puts_(" OSCCON=");
put_hex(SFR(OSCCON));
puts_(" RPB3R=");
put_dec(SFR(RPB3R));
puts_("\n");
REG(TRISB, CLR) = (1u << 13) | (1u << 14) | (1u << 8);
/* OC1: PWM (OCM 110) on Timer3 (OCTSEL), 1 kHz: PBCLK 40 MHz / 8 / 5000, OC1RS 1250. */
SFR(RPB7R) = 5; /* OC1 (group 1, 0101) */
SFR(T3CON) = 0;
SFR(TMR3) = 0;
SFR(PR3) = 4999;
SFR(OC1RS) = 1250;
SFR(OC1R) = 1250;
SFR(OC1CON) = (1u << 15) | (1u << 3) | 6;
SFR(T3CON) = (1u << 15) | (3u << 4); /* ON, 1:8 */
/* Multi-vector mode; the core timer at priority 7 (IPC0<4:2>), Timer2 (vector 8) at 3. */
REG(INTCON, SET) = 1u << 12;
SFR(IPC0) = 7u << 2;
SFR(IPC2) = 3u << 2;
ct_next = mfc0_count() + 20000;
mtc0_compare(ct_next);
SFR(T2CON) = 0;
SFR(TMR2) = 0;
SFR(PR2) = 19999;
SFR(T2CON) = (1u << 15) | (3u << 4); /* ON, 1:8 */
REG(IFS0, CLR) = (1u << 0) | (1u << 9);
REG(IEC0, SET) = (1u << 0) | (1u << 9);
__asm__ volatile("ei; ehb");
next = mfc0_count();
for (;;) {
next += 10000;
while ((int32_t)(mfc0_count() - next) < 0) {
}
REG(LATB, INV) = 1u << 13; /* RB13 */
if (ct_ticks >= 100 && !moved) {
/* OC1 moves from RPB7 to RPB15 (both group 1): RB7 is a port pin again. */
SFR(RPB7R) = 0;
SFR(RPB15R) = 5;
moved = 1;
}
if (ct_ticks == 200) {
puts_("CT ticks=200 T2 ticks=");
put_dec(t2_ticks);
puts_(" OC1 on RPB15R=");
put_dec(SFR(RPB15R));
puts_("\nDONE\n");
ct_ticks++; /* say it once */
once();
}
}
}