flowrs-engine/tools/qemu-mips-wasm/fixtures/baremetal/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

312 lines
8.5 KiB
C

/*
* The PIC32 twin's bare-metal fixture: no library, no framework, registers only, so that what
* it states is about the machine and nothing between. Built with clang for mipsel (MIPS32r2)
* by build.sh; runs on the PIC32MX3xx/4xx and 5xx/6xx/7xx parts (their ports, Timer2, UART1 and
* interrupt controller registers sit at the same addresses; DS61143H Table 4-x, DS60001156J).
*
* UART1 at 115200: the lines below;
* RG6: toggled every 20000 core-timer counts by polling Count -- 1 kHz from Count's
* SYSCLK/2 = 40 MHz (PIC32 FRM 2.12.3);
* RF0: toggled by the core timer's interrupt every 1 ms, priority 7 on the shadow register
* set -- 500 Hz;
* RD1: toggled by Timer2's interrupt, priority 3 on the normal set, PBCLK/8/40000 = 250 Hz
* periods -- 125 Hz, until the second phase.
*
* After DONE, a second phase checks four rules no measurement above depends on: the core timer's
* handler on the shadow set read the interrupted code's stack pointer with RDPGPR (the
* previous set's, not its own); a request is taken only above Status.IPL, not at it (IPL 3
* holds Timer2's priority-3 request, IPL 2 lets it in); OSCCON.PBDIV /2, written after the
* SYSKEY unlock, halves the clock Timer2 counts; a 16-bit timer started above its period
* register counts on to 0xFFFF and wraps to 0 (FRM 14.3.3).
*
* 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 TRISD 0xBF8860C0u
#define PORTD 0xBF8860D0u
#define LATD 0xBF8860E0u
#define TRISF 0xBF886140u
#define LATF 0xBF886160u
#define TRISG 0xBF886180u
#define LATG 0xBF8861A0u
#define CNCON 0xBF8861C0u
#define CNPUE 0xBF8861E0u
#define T2CON 0xBF800800u
#define TMR2 0xBF800810u
#define PR2 0xBF800820u
#define INTCON 0xBF881000u
#define INTSTAT 0xBF881010u
#define IFS0 0xBF881030u
#define IEC0 0xBF881060u
#define IPC0 0xBF881090u
#define IPC2 0xBF8810B0u
#define DEVID 0xBF80F220u
#define SYSKEY 0xBF80F230u
#define OSCCON 0xBF80F000u
static inline uint32_t mfc0_count(void)
{
uint32_t v;
__asm__ volatile("mfc0 %0, $9" : "=r"(v));
return v;
}
static inline uint32_t mfc0_srsctl(void)
{
uint32_t v;
__asm__ volatile("mfc0 %0, $12, 2" : "=r"(v));
return v;
}
static inline void mtc0_compare(uint32_t v)
{
__asm__ volatile("mtc0 %0, $11" : : "r"(v));
}
static inline uint32_t mfc0_status(void)
{
uint32_t v;
__asm__ volatile("mfc0 %0, $12" : "=r"(v));
return v;
}
static inline void mtc0_status(uint32_t v)
{
__asm__ volatile("mtc0 %0, $12\n\tehb" : : "r"(v));
}
/* Busy-wait n counts of Count (40 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 volatile uint32_t ct_ticks, ct_srs, t2_ticks, t2_srs, ct_next;
/* The stack pointer the core timer's handler took from the previous set (start.S). */
volatile uint32_t ct_seen_sp;
void ct_handler(void)
{
if (!ct_ticks) {
ct_srs = mfc0_srsctl();
}
ct_ticks++;
ct_next += 40000; /* 1 ms at 40 MHz */
mtc0_compare(ct_next);
REG(IFS0, CLR) = 1u << 0; /* CTIF */
REG(LATF, INV) = 1u << 0; /* RF0 */
}
void t2_handler(void)
{
if (!t2_ticks) {
t2_srs = mfc0_srsctl();
}
t2_ticks++;
REG(IFS0, CLR) = 1u << 8; /* T2IF */
REG(LATD, INV) = 1u << 1; /* RD1 */
}
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 (;;) {
}
}
/* Timer2 at 1:1 from `start` for 40 counts of Count; what TMR2 reads then. */
static uint32_t t2_count_from(uint32_t start)
{
uint32_t tmr;
SFR(T2CON) = 0;
SFR(PR2) = 100;
SFR(TMR2) = start;
SFR(T2CON) = 1u << 15;
wait_counts(40);
tmr = SFR(TMR2);
SFR(T2CON) = 0;
return tmr;
}
/* The second phase (see the top of the file). */
static void phase2(void)
{
uint32_t sp, st, before, held, ran, tmr;
__asm__ volatile("move %0, $sp" : "=r"(sp));
puts_("SRS rdpgpr sp ok=");
put_dec(ct_seen_sp <= sp && sp - ct_seen_sp < 1024);
puts_("\n");
/* Status.IPL is bits 15:10 with an external interrupt controller (MIPS32 PRA, EIC mode). */
st = mfc0_status();
before = t2_ticks;
mtc0_status((st & ~(0x3fu << 10)) | (3u << 10));
wait_counts(400000); /* 10 ms: two and a half Timer2 periods */
held = t2_ticks == before;
before = t2_ticks;
mtc0_status((st & ~(0x3fu << 10)) | (2u << 10));
wait_counts(400000);
ran = t2_ticks >= before + 2;
mtc0_status(st);
puts_("IPL3 t2 held=");
put_dec(held);
puts_(" IPL2 t2 ran=");
put_dec(ran);
puts_("\n");
/* PBCLK /2: Timer2 at 1:1 counts 2000 ticks in 2000 counts of Count (40 MHz), not 4000. */
REG(IEC0, CLR) = 1u << 8;
SFR(T2CON) = 0;
SFR(TMR2) = 0;
SFR(PR2) = 0xffff;
SFR(SYSKEY) = 0;
SFR(SYSKEY) = 0xAA996655u;
SFR(SYSKEY) = 0x556699AAu;
REG(OSCCON, CLR) = 3u << 19;
REG(OSCCON, SET) = 1u << 19; /* PBDIV 01: /2 */
SFR(SYSKEY) = 0;
SFR(T2CON) = 1u << 15; /* ON, 1:1 */
wait_counts(2000);
tmr = SFR(TMR2);
SFR(T2CON) = 0;
puts_("PBDIV2 tmr2/100=");
put_dec((tmr + 50) / 100);
puts_("\n");
/* Started at 0xFFF0, above PR2 = 100, it takes sixteen ticks to the wrap and goes on from 0:
* it reads sixteen less than the same wait started from 0 (give or take the tick the two
* starts may fall either side of). */
before = t2_count_from(0);
tmr = t2_count_from(0xfff0);
puts_("T2 above PR wraps ok=");
put_dec(before - tmr >= 15 && before - tmr <= 17);
puts_("\nPHASE2 DONE\n");
}
int main(void)
{
uint32_t next, nopull, pullup;
/* 115200 at PBCLK 80 MHz: BRG = 80e6 / (16 * 115200) - 1 = 42 (FRM 21.3). */
SFR(U1BRG) = 42;
SFR(U1STA) = (1u << 10) | (1u << 12); /* UTXEN, URXEN */
SFR(U1MODE) = 1u << 15; /* ON, 8N1 */
puts_("PIC32 bare-metal: start\n");
puts_("DEVID=");
put_hex(SFR(DEVID));
puts_(" OSCCON=");
put_hex(SFR(OSCCON));
puts_("\n");
/* An input: RD5 (CN14) with nothing on it, then with its weak pull-up (FRM 12.2.6). */
nopull = (SFR(PORTD) >> 5) & 1;
REG(CNPUE, SET) = 1u << 14;
pullup = (SFR(PORTD) >> 5) & 1;
REG(CNPUE, CLR) = 1u << 14;
puts_("IN RD5 nopull=");
put_dec(nopull);
puts_(" pullup=");
put_dec(pullup);
puts_("\n");
REG(TRISG, CLR) = 1u << 6;
REG(TRISF, CLR) = 1u << 0;
REG(TRISD, CLR) = 1u << 1;
/* Multi-vector mode; the core timer at priority 7 (IPC0<4:2>), Timer2 at 3 (IPC2<4:2>). */
REG(INTCON, SET) = 1u << 12;
SFR(IPC0) = 7u << 2;
SFR(IPC2) = 3u << 2;
ct_next = mfc0_count() + 40000;
mtc0_compare(ct_next);
SFR(T2CON) = 0;
SFR(TMR2) = 0;
SFR(PR2) = 39999;
SFR(T2CON) = (1u << 15) | (3u << 4); /* ON, 1:8 */
REG(IFS0, CLR) = (1u << 0) | (1u << 8);
REG(IEC0, SET) = (1u << 0) | (1u << 8);
__asm__ volatile("ei; ehb");
next = mfc0_count();
for (;;) {
next += 20000;
while ((int32_t)(mfc0_count() - next) < 0) {
}
REG(LATG, INV) = 1u << 6; /* RG6 */
if (ct_ticks == 200) {
puts_("CT ticks=200 css=");
put_dec(ct_srs & 0xf);
puts_(" pss=");
put_dec((ct_srs >> 6) & 0xf);
puts_(" T2 ticks=");
put_dec(t2_ticks);
puts_(" css=");
put_dec(t2_srs & 0xf);
puts_(" main css=");
put_dec(mfc0_srsctl() & 0xf);
puts_("\nDONE\n");
ct_ticks++; /* say it once */
phase2();
}
}
}