pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
tms9900_test.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - the TMS9900 cores' unit tests: ARM assembly against portable C
4 *
5 * Copyright (c) 2026 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Runs tests sequentially against the ARM assembly core (run9900) then
12 * the portable C core (run9900_c), printing PASS/FAIL for each.
13 * Each test prints its name BEFORE running so a hang is immediately
14 * identifiable from the output.
15 *
16 * Memory layout:
17 * 0x0000 - workspace (WP=0x0000, registers R0..R15 at offsets 0..30)
18 * 0x0100 - test program
19 * 0x0200 - scratch / indirect target area
20 */
21
22#include <stdio.h>
23#include <string.h>
24#include <stdint.h>
25
26#ifdef PICO_BUILD
27#include "pico/stdlib.h"
28#endif
29#include "tms9900.h"
30
31/* A host has no assembly core to compare against, so both passes run the portable
32 one and every check stands against the value the case states. Which is the point
33 of running here at all: on a board these cases are a cross-check, and off it they
34 are the only per-instruction test the portable core gets. */
35#ifdef PICO_BUILD
36extern uint16_t run9900(uint8_t* memory, uint16_t pc, uint16_t wp, uint8_t* regx38);
37#endif
38
39
40/* -------------------------------------------------------------------------
41 * Memory layout constants
42 * ---------------------------------------------------------------------- */
43#define MEM_SIZE (0x10000u + 36)
44#define WP 0xFFFEu
45#define PROG 0x0100u
46#define SCRATCH 0x0200u
47#define REG(r) ((uint32_t)(WP + (r) * 2u))
48
49static _Alignas(4) uint8_t mem[MEM_SIZE];
50
51static int total = 0;
52static int passed = 0;
53static int failed = 0;
54
55/* Off-target the two passes over a case are the SAME core, so counting both doubles
56 the headline for no extra coverage. These count one pass, and are what gets
57 reported there. On a board both passes are independent and all four agree. */
58static int indepTotal = 0;
59static int indepPassed = 0;
60static int indepFailed = 0;
61static int indepPass = 1;
62
63/* -------------------------------------------------------------------------
64 * Memory helpers (big-endian word access)
65 * ---------------------------------------------------------------------- */
66static inline void w16(uint32_t addr, uint16_t v)
67{
68 mem[addr] = (uint8_t)(v >> 8);
69 mem[addr + 1] = (uint8_t)(v & 0xFF);
70}
71
72static inline uint16_t r16(uint32_t addr)
73{
74 return (uint16_t)((mem[addr] << 8) | mem[addr + 1]);
75}
76
77/* -------------------------------------------------------------------------
78 * Test runner
79 *
80 * Call run_asm() or run_c() after setting up mem[], then use CHECK_* macros.
81 * ---------------------------------------------------------------------- */
82
83/* Reset mem and set registers from array (pass NULL for all-zero) */
84static void setup(const uint16_t* regs)
85{
86 memset(mem, 0, sizeof(mem));
87 if (regs) {
88 for (int i = 0; i < 16; i++)
89 w16(REG(i), regs[i]);
90 }
91}
92
93/* Place program at PROG */
94static void load_prog(const uint8_t* prog, uint16_t len)
95{
96 memcpy(mem + PROG, prog, len);
97}
98
99
100static void run_c(void);
101
102static void run_asm(void)
103{
104#ifdef PICO_BUILD
105 uint8_t r38 = 1;
106 run9900(mem, PROG, WP, &r38);
107#else
108 run_c();
109 /* That was the C core, and every case runs run_c() as its second pass, so the
110 checks about to be made are a repeat of it. Set AFTER the call - run_c() has
111 just marked itself countable. */
112 indepPass = 0;
113#endif
114}
115
116/* Which map the next run_c() gets. Only test_decode touches it: everything else asserts
117 what both cores do, which is the flat one an assembly core has no choice about. */
118static bool f18aMemory = false;
119
120static void run_c(void)
121{
122 uint8_t r38 = 1;
123 Tms9900Cpu cpu;
124 tms9900_init(&cpu, mem, &r38, PROG, WP);
125 cpu.f18aMemory = f18aMemory;
126 run9900_c(&cpu);
127 indepPass = 1;
128}
129
130/* Print a test result */
131static void check(const char* label, int ok, const char* detail)
132{
133 total++;
134 if (indepPass) indepTotal++;
135 if (ok) {
136 passed++;
137 if (indepPass) indepPassed++;
138 printf(" PASS: %s\n", label);
139 } else {
140 failed++;
141 if (indepPass) indepFailed++;
142 printf(" FAIL: %s - %s\n", label, detail);
143 printf(" regs:");
144 for (int i = 0; i < 8; i++) printf(" R%d=%04X", i, r16(REG(i)));
145 printf("\n");
146 }
147}
148
149/* Helpers so we don't need snprintf everywhere */
150static char _det[128];
151
152#define CHECK_REG(lbl, reg, expected) do { \
153 uint16_t _v = r16(REG(reg)); \
154 if (_v == (expected)) { check(lbl, 1, ""); } \
155 else { snprintf(_det,sizeof(_det),"R%d=%04X expected %04X",reg,_v,(uint16_t)(expected)); check(lbl,0,_det); } \
156} while(0)
157
158#define CHECK_MEM16(lbl, addr, expected) do { \
159 uint16_t _v = r16(addr); \
160 if (_v == (expected)) { check(lbl, 1, ""); } \
161 else { snprintf(_det,sizeof(_det),"[%04X]=%04X expected %04X",(uint16_t)(addr),_v,(uint16_t)(expected)); check(lbl,0,_det); } \
162} while(0)
163
164#define CHECK_MEM8(lbl, addr, expected) do { \
165 uint8_t _v = mem[addr]; \
166 if (_v == (expected)) { check(lbl, 1, ""); } \
167 else { snprintf(_det,sizeof(_det),"[%04X]=%02X expected %02X",(uint16_t)(addr),_v,(uint8_t)(expected)); check(lbl,0,_det); } \
168} while(0)
169
170/* -------------------------------------------------------------------------
171 * Instruction encoders
172 * ---------------------------------------------------------------------- */
173#define MAX_PROG 128u
174
175static inline void emit(uint8_t* b, uint16_t* o, uint16_t w)
176{
177 b[(*o)++] = (uint8_t)(w >> 8);
178 b[(*o)++] = (uint8_t)(w & 0xFF);
179}
180
181#define IDLE 0x0340u
182
183static inline void li (uint8_t* b, uint16_t* o, uint8_t rd, uint16_t imm) { emit(b,o,(uint16_t)(0x0200u|rd)); emit(b,o,imm); }
184static inline void ai (uint8_t* b, uint16_t* o, uint8_t rd, uint16_t imm) { emit(b,o,(uint16_t)(0x0220u|rd)); emit(b,o,imm); }
185static inline void andi_op(uint8_t* b, uint16_t* o, uint8_t rd, uint16_t imm) { emit(b,o,(uint16_t)(0x0240u|rd)); emit(b,o,imm); }
186static inline void ori_op (uint8_t* b, uint16_t* o, uint8_t rd, uint16_t imm) { emit(b,o,(uint16_t)(0x0260u|rd)); emit(b,o,imm); }
187static inline void ci (uint8_t* b, uint16_t* o, uint8_t rd, uint16_t imm) { emit(b,o,(uint16_t)(0x0280u|rd)); emit(b,o,imm); }
188static inline void stwp (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x02A0u|rd)); }
189static inline void stst (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x02C0u|rd)); }
190static inline void lwpi (uint8_t* b, uint16_t* o, uint16_t wp) { emit(b,o,0x02E0u); emit(b,o,wp); }
191static inline void limi (uint8_t* b, uint16_t* o, uint16_t mask) { emit(b,o,0x0300u); emit(b,o,mask); }
192static inline void rtwp (uint8_t* b, uint16_t* o) { emit(b,o,0x0380u); }
193
194static inline void blwp (uint8_t* b, uint16_t* o, uint16_t addr) { emit(b,o,(uint16_t)(0x0400u|(2u<<4)|0)); emit(b,o,addr); }
195static inline void b_ind (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0440u|(1u<<4)|rd)); }
196static inline void b_abs (uint8_t* b, uint16_t* o, uint16_t addr) { emit(b,o,(uint16_t)(0x0440u|(2u<<4)|0)); emit(b,o,addr); }
197/* X takes the operand's VALUE as the instruction, so Ts=00 executes what Rd holds */
198static inline void x_reg (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0480u|rd)); }
199static inline void clr (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x04C0u|rd)); }
200static inline void neg (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0500u|rd)); }
201static inline void inv (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0540u|rd)); }
202static inline void inc (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0580u|rd)); }
203static inline void inct (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x05C0u|rd)); }
204static inline void dec (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0600u|rd)); }
205static inline void dect (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0640u|rd)); }
206static inline void bl_abs (uint8_t* b, uint16_t* o, uint16_t addr) { emit(b,o,(uint16_t)(0x0680u|(2u<<4)|0)); emit(b,o,addr); }
207static inline void swpb (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x06C0u|rd)); }
208static inline void seto (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0700u|rd)); }
209static inline void abs_r (uint8_t* b, uint16_t* o, uint8_t rd) { emit(b,o,(uint16_t)(0x0740u|rd)); }
210static inline void rt (uint8_t* b, uint16_t* o) { emit(b,o,(uint16_t)(0x0440u|(1u<<4)|11u)); }
211
212/* Shift format: 0000 1ooo CCCC WWWW (C=count in bits 7-4, W=register in bits 3-0) */
213static inline void sra (uint8_t* b, uint16_t* o, uint8_t rd, uint8_t c) { emit(b,o,(uint16_t)(0x0800u|((uint16_t)(c&0xF)<<4)|(rd&0xF))); }
214static inline void srl (uint8_t* b, uint16_t* o, uint8_t rd, uint8_t c) { emit(b,o,(uint16_t)(0x0900u|((uint16_t)(c&0xF)<<4)|(rd&0xF))); }
215static inline void sla (uint8_t* b, uint16_t* o, uint8_t rd, uint8_t c) { emit(b,o,(uint16_t)(0x0A00u|((uint16_t)(c&0xF)<<4)|(rd&0xF))); }
216static inline void src_op (uint8_t* b, uint16_t* o, uint8_t rd, uint8_t c) { emit(b,o,(uint16_t)(0x0B00u|((uint16_t)(c&0xF)<<4)|(rd&0xF))); }
217
218/* Jump format: 0001 CCCC DDDDDDDD. The condition nibble is emitted from the index both
219 cores dispatch on - handle_branch_group in gpu/tms9900.c, JMPTBL in thumb9900_*.S,
220 which agree row for row - rather than written out per instruction: three of the
221 hand-written constants here were wrong, and because those three encoders were never
222 called, nothing said so. 0x1A00 is JL not JLT, 0x1C00 is JOP not JOC, and 0x1D00 is
223 SBO - a no-op, so a test written with it would have proved nothing at all. */
224static inline void jcc(uint8_t* b, uint16_t* o, uint8_t cond, int8_t off)
225{
226 emit(b, o, (uint16_t)(0x1000u | ((uint16_t)(cond & 0xF) << 8) | (uint8_t)off));
227}
228
229static inline void jmp (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x0,off); }
230static inline void jlt (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x1,off); }
231static inline void jle (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x2,off); }
232static inline void jeq (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x3,off); }
233static inline void jhe (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x4,off); }
234static inline void jgt (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x5,off); }
235static inline void jne (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x6,off); }
236static inline void jnc (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x7,off); }
237static inline void joc (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x8,off); }
238static inline void jno (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0x9,off); }
239static inline void jl (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0xA,off); }
240static inline void jh (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0xB,off); }
241static inline void jop (uint8_t* b, uint16_t* o, int8_t off) { jcc(b,o,0xC,off); }
242
243static inline void coc (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x2000u|((uint16_t)rd<<6)|rs)); }
244static inline void czc (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x2400u|((uint16_t)rd<<6)|rs)); }
245static inline void xor_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x2800u|((uint16_t)rd<<6)|rs)); }
246static inline void pix (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x2C00u|((uint16_t)rd<<6)|rs)); }
247static inline void mpy (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x3800u|((uint16_t)rd<<6)|rs)); }
248static inline void div_op (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x3C00u|((uint16_t)rd<<6)|rs)); }
249
250/* Format 1. The byte forms sit 0x1000 above their word form and address the operand's
251 EVEN byte, which for a register operand is its high byte. */
252static inline void szc_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x4000u|((uint16_t)rd<<6)|rs)); }
253static inline void szcb_rr(uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x5000u|((uint16_t)rd<<6)|rs)); }
254static inline void sub_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x6000u|((uint16_t)rd<<6)|rs)); }
255static inline void sb_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x7000u|((uint16_t)rd<<6)|rs)); }
256static inline void cb_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0x9000u|((uint16_t)rd<<6)|rs)); }
257static inline void add_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xA000u|((uint16_t)rd<<6)|rs)); }
258static inline void ab_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xB000u|((uint16_t)rd<<6)|rs)); }
259static inline void mov_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xC000u|((uint16_t)rd<<6)|rs)); }
260static inline void mov_ir (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xC000u|((uint16_t)rd<<6)|(1u<<4)|rs)); }
261static inline void mov_ri (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xC000u|((uint16_t)(0x10u|rd)<<6)|rs)); }
262static inline void mov_ar (uint8_t* b, uint16_t* o, uint16_t addr, uint8_t rd) { emit(b,o,(uint16_t)(0xC000u|((uint16_t)rd<<6)|(2u<<4)|0)); emit(b,o,addr); }
263static inline void movb_rr(uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xD000u|((uint16_t)rd<<6)|rs)); }
264static inline void soc_rr (uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xE000u|((uint16_t)rd<<6)|rs)); }
265static inline void socb_rr(uint8_t* b, uint16_t* o, uint8_t rs, uint8_t rd) { emit(b,o,(uint16_t)(0xF000u|((uint16_t)rd<<6)|rs)); }
266
267/* -------------------------------------------------------------------------
268 * Test groups - each test prints its name, runs ASM then C, reports PASS/FAIL
269 * ---------------------------------------------------------------------- */
270
271static void test_data_transfer(void)
272{
273 printf("\n=== Data Transfer ===\n");
274 uint8_t p[MAX_PROG]; uint16_t n;
275
276 /* LI R0, 0x1234 */
277 printf(" LI R0,0x1234\n");
278 n=0; li(p,&n,0,0x1234); emit(p,&n,IDLE);
279 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("LI R0,0x1234 [ASM]", 0, 0x1234);
280 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("LI R0,0x1234 [C]", 0, 0x1234);
281
282 /* MOV R0, R1 */
283 printf(" MOV R0,R1\n");
284 n=0; li(p,&n,0,0xABCD); mov_rr(p,&n,0,1); emit(p,&n,IDLE);
285 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("MOV R0->R1", 1, 0xABCD);
286 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("MOV R0->R1", 1, 0xABCD);
287
288 /* MOV indirect: *R0 -> R1 (R0 points to SCRATCH, SCRATCH holds 0xBEEF) */
289 printf(" MOV *R0,R1\n");
290 n=0; mov_ir(p,&n,0,1); emit(p,&n,IDLE);
291 { uint16_t regs[16]={0}; regs[0]=SCRATCH;
292 setup(regs); w16(SCRATCH,0xBEEF); load_prog(p,n);
293 run_asm(); CHECK_REG("MOV *R0->R1", 1, 0xBEEF);
294 setup(regs); w16(SCRATCH,0xBEEF); load_prog(p,n);
295 run_c(); CHECK_REG("MOV *R0->R1", 1, 0xBEEF); }
296
297 /* MOV R0, *R1 (store to address in R1) */
298 printf(" MOV R0,*R1\n");
299 n=0; mov_ri(p,&n,0,1); emit(p,&n,IDLE);
300 { uint16_t regs[16]={0}; regs[0]=0x1234; regs[1]=SCRATCH;
301 setup(regs); load_prog(p,n); run_asm(); CHECK_MEM16("MOV R0->*R1", SCRATCH, 0x1234);
302 setup(regs); load_prog(p,n); run_c(); CHECK_MEM16("MOV R0->*R1", SCRATCH, 0x1234); }
303
304 /* MOV @addr, R1 (absolute load) */
305 printf(" MOV @addr,R1\n");
306 n=0; mov_ar(p,&n,SCRATCH,1); emit(p,&n,IDLE);
307 setup(NULL); w16(SCRATCH,0xCAFE); load_prog(p,n);
308 run_asm(); CHECK_REG("MOV @abs->R1", 1, 0xCAFE);
309 setup(NULL); w16(SCRATCH,0xCAFE); load_prog(p,n);
310 run_c(); CHECK_REG("MOV @abs->R1", 1, 0xCAFE);
311
312 /* MOV *R0+, R2 (auto-increment) */
313 printf(" MOV *R0+,R2\n");
314 n=0; emit(p,&n,(uint16_t)(0xC000u|((uint16_t)2<<6)|(3u<<4)|0)); emit(p,&n,IDLE); /* src=mode3/R0 */
315 { uint16_t regs[16]={0}; regs[0]=SCRATCH;
316 setup(regs); w16(SCRATCH,0x5A5A); load_prog(p,n);
317 run_asm(); CHECK_REG("autoinc R0", 0, SCRATCH+2); CHECK_REG("autoinc R2", 2, 0x5A5A);
318 setup(regs); w16(SCRATCH,0x5A5A); load_prog(p,n);
319 run_c(); CHECK_REG("autoinc R0", 0, SCRATCH+2); CHECK_REG("autoinc R2", 2, 0x5A5A); }
320
321 /* MOVB Rs, Rd - high byte of source copied to high byte of dest */
322 printf(" MOVB R0,R1\n");
323 n=0; li(p,&n,0,0x5500); movb_rr(p,&n,0,1); emit(p,&n,IDLE);
324 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("MOVB R0->R1", 1, 0x5500);
325 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("MOVB R0->R1", 1, 0x5500);
326}
327
328static void test_arithmetic(void)
329{
330 printf("\n=== Arithmetic ===\n");
331 uint8_t p[MAX_PROG]; uint16_t n;
332
333 /* A: basic add */
334 printf(" A R0,R1 (3+4=7)\n");
335 n=0; li(p,&n,0,3); li(p,&n,1,4); add_rr(p,&n,0,1); emit(p,&n,IDLE);
336 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("A 3+4=7", 1, 7);
337 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("A 3+4=7", 1, 7);
338
339 /* A: carry (0xFFFF + 1 = 0, carry set) */
340 printf(" A carry (0xFFFF+1)\n");
341 n=0; li(p,&n,0,0xFFFF); li(p,&n,1,1); add_rr(p,&n,0,1); stst(p,&n,2); emit(p,&n,IDLE);
342 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("ADD carry result", 1, 0);
343 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("ADD carry result", 1, 0);
344 /* carry bit = 0x10 (C flag) in stst word */
345 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(2));
346 snprintf(_det,sizeof(_det),"ST=%04X C-bit missing",st);
347 check("ADD carry flag [ASM]", (st&0x1000)!=0, _det); }
348 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(2));
349 snprintf(_det,sizeof(_det),"ST=%04X C-bit missing",st);
350 check("ADD carry flag [C]", (st&0x1000)!=0, _det); }
351
352 /* S: subtract */
353 printf(" S R0,R1 (5-3=2)\n");
354 n=0; li(p,&n,0,3); li(p,&n,1,5); sub_rr(p,&n,0,1); emit(p,&n,IDLE);
355 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("S 5-3=2", 1, 2);
356 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("S 5-3=2", 1, 2);
357
358 /* NEG */
359 printf(" NEG R0 (1->0xFFFF)\n");
360 n=0; li(p,&n,0,1); neg(p,&n,0); emit(p,&n,IDLE);
361 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("NEG 1", 0, 0xFFFF);
362 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("NEG 1", 0, 0xFFFF);
363
364 /* ABS positive */
365 printf(" ABS R0 (0x0005)\n");
366 n=0; li(p,&n,0,5); abs_r(p,&n,0); emit(p,&n,IDLE);
367 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("ABS pos", 0, 5);
368 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("ABS pos", 0, 5);
369
370 /* ABS negative */
371 printf(" ABS R0 (0xFFFB->5)\n");
372 n=0; li(p,&n,0,0xFFFB); abs_r(p,&n,0); emit(p,&n,IDLE);
373 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("ABS neg", 0, 5);
374 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("ABS neg", 0, 5);
375
376 /* INC */
377 printf(" INC R0 (4->5)\n");
378 n=0; li(p,&n,0,4); inc(p,&n,0); emit(p,&n,IDLE);
379 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("INC", 0, 5);
380 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("INC", 0, 5);
381
382 /* INCT */
383 printf(" INCT R0 (4->6)\n");
384 n=0; li(p,&n,0,4); inct(p,&n,0); emit(p,&n,IDLE);
385 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("INCT", 0, 6);
386 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("INCT", 0, 6);
387
388 /* DEC */
389 printf(" DEC R0 (5->4)\n");
390 n=0; li(p,&n,0,5); dec(p,&n,0); emit(p,&n,IDLE);
391 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("DEC", 0, 4);
392 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("DEC", 0, 4);
393
394 /* DECT */
395 printf(" DECT R0 (6->4)\n");
396 n=0; li(p,&n,0,6); dect(p,&n,0); emit(p,&n,IDLE);
397 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("DECT", 0, 4);
398 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("DECT", 0, 4);
399
400 /* AI */
401 printf(" AI R0,0x10 (5+16=21)\n");
402 n=0; li(p,&n,0,5); ai(p,&n,0,0x10); emit(p,&n,IDLE);
403 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("AI", 0, 21);
404 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("AI", 0, 21);
405
406 /* MPY */
407 printf(" MPY R1,R0 (3*4=12)\n");
408 n=0; li(p,&n,0,3); li(p,&n,1,4); mpy(p,&n,1,0); emit(p,&n,IDLE);
409 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("MPY hi", 0, 0); CHECK_REG("MPY lo", 1, 12);
410 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("MPY hi", 0, 0); CHECK_REG("MPY lo", 1, 12);
411
412 /* DIV: 12/4=3 rem 0 */
413 printf(" DIV R2,R0 (12/4=3)\n");
414 n=0; li(p,&n,0,0); li(p,&n,1,12); li(p,&n,2,4); div_op(p,&n,2,0); emit(p,&n,IDLE);
415 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("DIV quot", 0, 3); CHECK_REG("DIV rem", 1, 0);
416 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("DIV quot", 0, 3); CHECK_REG("DIV rem", 1, 0);
417
418 /* AB / SB - the byte arithmetic pair, on a register's HIGH byte, with a low half
419 that has to survive. No carry out of the byte here: that is a separate rule from
420 the word forms and is what the low half proves. */
421 printf(" AB R0,R1 (0x01+0x02 in the high byte)\n");
422 n=0; li(p,&n,0,0x0100); li(p,&n,1,0x0234); ab_rr(p,&n,0,1); emit(p,&n,IDLE);
423 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("AB", 1, 0x0334);
424 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("AB", 1, 0x0334);
425
426 printf(" SB R0,R1 (0x05-0x02 in the high byte)\n");
427 n=0; li(p,&n,0,0x0200); li(p,&n,1,0x0534); sb_rr(p,&n,0,1); emit(p,&n,IDLE);
428 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SB", 1, 0x0334);
429 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SB", 1, 0x0334);
430
431 /* DIV overflow, both ways in. The dividend is the register PAIR, so the rule is
432 "divisor greater than the high word" - and a zero divisor is the degenerate case
433 of it. Either way the pair is left alone, which is the part worth pinning: a core
434 that computed first and checked after would have written a quotient here. */
435 printf(" DIV by zero (overflow, operands unchanged)\n");
436 n=0; li(p,&n,0,1); li(p,&n,1,2); li(p,&n,2,0); div_op(p,&n,2,0); stst(p,&n,3); emit(p,&n,IDLE);
437 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("DIV/0 hi", 0, 1); CHECK_REG("DIV/0 lo", 1, 2);
438 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("DIV/0 hi", 0, 1); CHECK_REG("DIV/0 lo", 1, 2);
439 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(3));
440 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
441 check("DIV/0 OV [ASM]", (st&0x0800)!=0, _det); }
442 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(3));
443 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
444 check("DIV/0 OV [C]", (st&0x0800)!=0, _det); }
445
446 printf(" DIV quotient too wide (overflow)\n");
447 n=0; li(p,&n,0,5); li(p,&n,1,0); li(p,&n,2,4); div_op(p,&n,2,0); stst(p,&n,3); emit(p,&n,IDLE);
448 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("DIV OV hi", 0, 5); CHECK_REG("DIV OV lo", 1, 0);
449 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("DIV OV hi", 0, 5); CHECK_REG("DIV OV lo", 1, 0);
450 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(3));
451 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
452 check("DIV wide OV [ASM]", (st&0x0800)!=0, _det); }
453 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(3));
454 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
455 check("DIV wide OV [C]", (st&0x0800)!=0, _det); }
456
457 /* NEG 0x8000 -> overflow */
458 printf(" NEG 0x8000 (overflow)\n");
459 n=0; li(p,&n,0,0x8000); neg(p,&n,0); stst(p,&n,1); emit(p,&n,IDLE);
460 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("NEG 0x8000 result", 0, 0x8000);
461 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("NEG 0x8000 result", 0, 0x8000);
462 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(1));
463 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
464 check("NEG 0x8000 OV [ASM]", (st&0x0800)!=0, _det); }
465 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(1));
466 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
467 check("NEG 0x8000 OV [C]", (st&0x0800)!=0, _det); }
468}
469
470static void test_logical(void)
471{
472 printf("\n=== Logical ===\n");
473 uint8_t p[MAX_PROG]; uint16_t n;
474
475 /* SZC (AND NOT) */
476 printf(" SZC R0,R1\n");
477 n=0; li(p,&n,0,0x00FF); li(p,&n,1,0xFFFF); szc_rr(p,&n,0,1); emit(p,&n,IDLE);
478 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SZC", 1, 0xFF00);
479 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SZC", 1, 0xFF00);
480
481 /* SOC (OR) */
482 printf(" SOC R0,R1\n");
483 n=0; li(p,&n,0,0x0F0F); li(p,&n,1,0xF0F0); soc_rr(p,&n,0,1); emit(p,&n,IDLE);
484 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SOC", 1, 0xFFFF);
485 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SOC", 1, 0xFFFF);
486
487 /* XOR */
488 printf(" XOR R0,R1\n");
489 n=0; li(p,&n,0,0xAAAA); li(p,&n,1,0xFFFF); xor_rr(p,&n,0,1); emit(p,&n,IDLE);
490 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("XOR", 1, 0x5555);
491 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("XOR", 1, 0x5555);
492
493 /* INV */
494 printf(" INV R0\n");
495 n=0; li(p,&n,0,0xAAAA); inv(p,&n,0); emit(p,&n,IDLE);
496 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("INV", 0, 0x5555);
497 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("INV", 0, 0x5555);
498
499 /* CLR */
500 printf(" CLR R0\n");
501 n=0; li(p,&n,0,0x1234); clr(p,&n,0); emit(p,&n,IDLE);
502 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("CLR", 0, 0);
503 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("CLR", 0, 0);
504
505 /* SETO */
506 printf(" SETO R0\n");
507 n=0; seto(p,&n,0); emit(p,&n,IDLE);
508 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SETO", 0, 0xFFFF);
509 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SETO", 0, 0xFFFF);
510
511 /* ANDI */
512 printf(" ANDI R0,0x0F0F\n");
513 n=0; li(p,&n,0,0xFFFF); andi_op(p,&n,0,0x0F0F); emit(p,&n,IDLE);
514 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("ANDI", 0, 0x0F0F);
515 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("ANDI", 0, 0x0F0F);
516
517 /* ORI */
518 printf(" ORI R0,0xFF00\n");
519 n=0; li(p,&n,0,0x00FF); ori_op(p,&n,0,0xFF00); emit(p,&n,IDLE);
520 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("ORI", 0, 0xFFFF);
521 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("ORI", 0, 0xFFFF);
522
523 /* COC - EQ set if (src & dst)==src */
524 printf(" COC R0,R1 (match)\n");
525 n=0; li(p,&n,0,0x0F0F); li(p,&n,1,0xFFFF); coc(p,&n,0,1); stst(p,&n,2); emit(p,&n,IDLE);
526 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(2));
527 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
528 check("COC EQ [ASM]", (st&0x2000)!=0, _det); }
529 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(2));
530 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
531 check("COC EQ [C]", (st&0x2000)!=0, _det); }
532
533 /* CZC - EQ set when every bit the mask sets is zero in the destination. The
534 complement of COC above, and the only other instruction in that group. */
535 printf(" CZC R0,R1 (all clear)\n");
536 n=0; li(p,&n,0,0x00FF); li(p,&n,1,0xFF00); czc(p,&n,0,1); stst(p,&n,2); emit(p,&n,IDLE);
537 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(2));
538 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
539 check("CZC EQ [ASM]", (st&0x2000)!=0, _det); }
540 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(2));
541 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
542 check("CZC EQ [C]", (st&0x2000)!=0, _det); }
543
544 printf(" CZC R0,R1 (one bit set)\n");
545 n=0; li(p,&n,0,0x00FF); li(p,&n,1,0xFF01); czc(p,&n,0,1); stst(p,&n,2); emit(p,&n,IDLE);
546 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(2));
547 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit should be clear",st);
548 check("CZC not EQ [ASM]", (st&0x2000)==0, _det); }
549 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(2));
550 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit should be clear",st);
551 check("CZC not EQ [C]", (st&0x2000)==0, _det); }
552
553 /* The byte forms of SZC and SOC. A register operand's byte is its HIGH byte, so the
554 low half must come through untouched - which is the half a word-wide
555 implementation of these would quietly clobber. */
556 printf(" SZCB R0,R1\n");
557 n=0; li(p,&n,0,0x0F00); li(p,&n,1,0xFF55); szcb_rr(p,&n,0,1); emit(p,&n,IDLE);
558 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SZCB", 1, 0xF055);
559 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SZCB", 1, 0xF055);
560
561 printf(" SOCB R0,R1\n");
562 n=0; li(p,&n,0,0x0F00); li(p,&n,1,0xF055); socb_rr(p,&n,0,1); emit(p,&n,IDLE);
563 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SOCB", 1, 0xFF55);
564 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SOCB", 1, 0xFF55);
565
566 /* CB (compare byte) - sets EQ when high bytes match */
567 printf(" CB R0,R1 (equal)\n");
568 n=0; li(p,&n,0,0xAA00); li(p,&n,1,0xAAFF); cb_rr(p,&n,0,1); stst(p,&n,2); emit(p,&n,IDLE);
569 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(2));
570 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
571 check("CB EQ [ASM]", (st&0x2000)!=0, _det); }
572 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(2));
573 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
574 check("CB EQ [C]", (st&0x2000)!=0, _det); }
575}
576
577static void test_shifts(void)
578{
579 printf("\n=== Shifts ===\n");
580 uint8_t p[MAX_PROG]; uint16_t n;
581
582 /* SRA 4 */
583 printf(" SRA R0,4\n");
584 n=0; li(p,&n,0,0x8000); sra(p,&n,0,4); emit(p,&n,IDLE);
585 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SRA 4", 0, 0xF800);
586 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SRA 4", 0, 0xF800);
587
588 /* SRL 4 */
589 printf(" SRL R0,4\n");
590 n=0; li(p,&n,0,0x8000); srl(p,&n,0,4); emit(p,&n,IDLE);
591 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SRL 4", 0, 0x0800);
592 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SRL 4", 0, 0x0800);
593
594 /* SLA 4 */
595 printf(" SLA R0,4\n");
596 n=0; li(p,&n,0,0x0001); sla(p,&n,0,4); emit(p,&n,IDLE);
597 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SLA 4", 0, 0x0010);
598 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SLA 4", 0, 0x0010);
599
600 /* SRC 4 */
601 printf(" SRC R0,4\n");
602 n=0; li(p,&n,0,0x1234); src_op(p,&n,0,4); emit(p,&n,IDLE);
603 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SRC 4", 0, 0x4123);
604 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SRC 4", 0, 0x4123);
605
606 /* SRA by R0 (count=0 means use R0 low nibble) */
607 printf(" SRA R1,R0 (count from R0)\n");
608 n=0; li(p,&n,0,4); li(p,&n,1,0x8000); sra(p,&n,1,0); emit(p,&n,IDLE);
609 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SRA R0-count", 1, 0xF800);
610 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SRA R0-count", 1, 0xF800);
611
612 /* SLA overflow flag */
613 printf(" SLA overflow (0x4000<<1)\n");
614 n=0; li(p,&n,0,0x4000); sla(p,&n,0,1); stst(p,&n,1); emit(p,&n,IDLE);
615 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(1));
616 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
617 check("SLA OV [ASM]", (st&0x0800)!=0, _det); }
618 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(1));
619 snprintf(_det,sizeof(_det),"ST=%04X OV-bit missing",st);
620 check("SLA OV [C]", (st&0x0800)!=0, _det); }
621}
622
623static void test_branches(void)
624{
625 printf("\n=== Branches ===\n");
626 uint8_t p[MAX_PROG]; uint16_t n;
627
628 /* CI + JEQ taken: skip LI R0,0xDEAD, R0 stays 0 */
629 printf(" CI/JEQ taken\n");
630 n=0;
631 li(p,&n,0,5); ci(p,&n,0,5); /* R0=5; CI R0,5 -> EQ */
632 jeq(p,&n,2); /* JEQ +2 words -> skip next LI */
633 li(p,&n,0,0xDEAD); /* should be skipped */
634 emit(p,&n,IDLE);
635 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("JEQ taken", 0, 5);
636 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("JEQ taken", 0, 5);
637
638 /* CI + JEQ not taken: R0 gets overwritten */
639 printf(" CI/JEQ not taken\n");
640 n=0;
641 li(p,&n,0,5); ci(p,&n,0,6); /* R0=5; CI R0,6 -> not EQ */
642 jeq(p,&n,2); /* JEQ not taken */
643 li(p,&n,0,0x1111); /* R0 = 0x1111 */
644 emit(p,&n,IDLE);
645 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("JEQ not taken", 0, 0x1111);
646 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("JEQ not taken", 0, 0x1111);
647
648 /* JNE loop (count down from 3 to 0) */
649 printf(" JNE loop (3 iters)\n");
650 n=0;
651 li(p,&n,0,3); /* R0=3 */
652 /* loop: DEC R0 (2 bytes), CI R0,0 (4 bytes), JNE -4 (2 bytes) = 8 bytes */
653 dec(p,&n,0); /* 2 bytes @ +4 */
654 ci(p,&n,0,0); /* 4 bytes @ +6 */
655 jne(p,&n,(int8_t)(-4)); /* PC after=+12, target=+12+(-4*2)=+4 (DEC) */
656 emit(p,&n,IDLE);
657 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("JNE loop R0", 0, 0);
658 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("JNE loop R0", 0, 0);
659
660 /* BL / RT */
661 printf(" BL/RT\n");
662 /* Program: LI R0,0; BL @sub; IDLE
663 * sub: LI R0,0xBEEF; RT */
664 uint16_t sub_off = 0;
665 n=0;
666 li(p,&n,0,0);
667 uint16_t bl_off = n; bl_abs(p,&n,0); /* BL @0 - patch below */
668 emit(p,&n,IDLE);
669 sub_off = n;
670 li(p,&n,0,0xBEEF);
671 rt(p,&n);
672 /* patch BL target: absolute address of sub = PROG + sub_off */
673 uint16_t sub_addr = PROG + sub_off;
674 p[bl_off+2] = (uint8_t)(sub_addr>>8);
675 p[bl_off+3] = (uint8_t)(sub_addr&0xFF);
676 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("BL/RT R0", 0, 0xBEEF);
677 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("BL/RT R0", 0, 0xBEEF);
678
679 /* B *Rn - the register holds the target. RT is this instruction with Rn=R11, so a
680 core can pass the BL/RT case above and still have the general form wrong. */
681 printf(" B *R5\n");
682 n=0;
683 uint16_t li5_off = n; /* the immediate is the word after the opcode */
684 li(p,&n,5,0); /* patched below to the target address */
685 b_ind(p,&n,5);
686 li(p,&n,0,0xDEAD); /* skipped when the branch works */
687 emit(p,&n,IDLE);
688 uint16_t bind_target = (uint16_t)(PROG + n);
689 li(p,&n,0,0xB00B);
690 emit(p,&n,IDLE);
691 p[li5_off+2] = (uint8_t)(bind_target >> 8);
692 p[li5_off+3] = (uint8_t)(bind_target & 0xFF);
693 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("B *R5", 0, 0xB00B);
694 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("B *R5", 0, 0xB00B);
695
696 /* B @addr - same target, symbolic operand, and no link register written */
697 printf(" B @addr\n");
698 n=0;
699 uint16_t babs_off = n;
700 b_abs(p,&n,0); /* patched below */
701 li(p,&n,0,0xDEAD); /* skipped */
702 emit(p,&n,IDLE);
703 uint16_t babs_target = (uint16_t)(PROG + n);
704 li(p,&n,0,0xCAFE);
705 emit(p,&n,IDLE);
706 p[babs_off+2] = (uint8_t)(babs_target >> 8);
707 p[babs_off+3] = (uint8_t)(babs_target & 0xFF);
708 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("B @addr", 0, 0xCAFE);
709 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("B @addr", 0, 0xCAFE);
710}
711
712/* Every encodable jump condition, taken and not taken. Worth its own group because the
713 conditions are the part a hand-written core gets subtly wrong: JLT and JL differ only
714 in signedness, JLE and JHE are the two that also fire on EQ, and JOP reads a flag only
715 the byte operations write. */
716static void test_jumps(void)
717{
718 printf("\n=== Jump conditions ===\n");
719 uint8_t p[MAX_PROG]; uint16_t n;
720
721 /* Each case sets the flags, jumps two words over a marker LI, then reads R1: taken
722 leaves it at zero, not taken leaves the marker. The jump must be the last thing
723 emitted before the check. */
724#define CHECK_JUMP(lbl, taken) do { \
725 li(p,&n,1,0xDEAD); emit(p,&n,IDLE); \
726 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG(lbl, 1, (taken) ? 0 : 0xDEAD); \
727 setup(NULL); load_prog(p,n); run_c(); CHECK_REG(lbl, 1, (taken) ? 0 : 0xDEAD); \
728} while(0)
729
730 printf(" JMP\n");
731 n=0; jmp(p,&n,2); CHECK_JUMP("JMP", 1);
732
733 /* CI leaves LGT (unsigned >), AGT (signed >) and EQ. -1 is unsigned-high and
734 signed-low at once, which is what separates the two families. */
735 printf(" JLT / JGT (signed)\n");
736 n=0; li(p,&n,0,0xFFFF); ci(p,&n,0,1); jlt(p,&n,2); CHECK_JUMP("JLT taken", 1);
737 n=0; li(p,&n,0,5); ci(p,&n,0,3); jlt(p,&n,2); CHECK_JUMP("JLT not taken", 0);
738 n=0; li(p,&n,0,5); ci(p,&n,0,3); jgt(p,&n,2); CHECK_JUMP("JGT taken", 1);
739 n=0; li(p,&n,0,0xFFFF); ci(p,&n,0,1); jgt(p,&n,2); CHECK_JUMP("JGT not taken", 0);
740
741 printf(" JL / JH (unsigned)\n");
742 n=0; li(p,&n,0,1); ci(p,&n,0,5); jl(p,&n,2); CHECK_JUMP("JL taken", 1);
743 n=0; li(p,&n,0,5); ci(p,&n,0,1); jl(p,&n,2); CHECK_JUMP("JL not taken", 0);
744 n=0; li(p,&n,0,5); ci(p,&n,0,1); jh(p,&n,2); CHECK_JUMP("JH taken", 1);
745 n=0; li(p,&n,0,5); ci(p,&n,0,5); jh(p,&n,2); CHECK_JUMP("JH not taken", 0);
746
747 /* The two that also fire on EQ */
748 printf(" JLE / JHE\n");
749 n=0; li(p,&n,0,1); ci(p,&n,0,5); jle(p,&n,2); CHECK_JUMP("JLE on less", 1);
750 n=0; li(p,&n,0,5); ci(p,&n,0,5); jle(p,&n,2); CHECK_JUMP("JLE on equal", 1);
751 n=0; li(p,&n,0,5); ci(p,&n,0,1); jle(p,&n,2); CHECK_JUMP("JLE not taken", 0);
752 n=0; li(p,&n,0,5); ci(p,&n,0,1); jhe(p,&n,2); CHECK_JUMP("JHE on greater", 1);
753 n=0; li(p,&n,0,5); ci(p,&n,0,5); jhe(p,&n,2); CHECK_JUMP("JHE on equal", 1);
754 n=0; li(p,&n,0,1); ci(p,&n,0,5); jhe(p,&n,2); CHECK_JUMP("JHE not taken", 0);
755
756 printf(" JOC / JNC\n");
757 n=0; li(p,&n,0,0xFFFF); ai(p,&n,0,1); joc(p,&n,2); CHECK_JUMP("JOC taken", 1);
758 n=0; li(p,&n,0,0); ai(p,&n,0,1); joc(p,&n,2); CHECK_JUMP("JOC not taken", 0);
759 n=0; li(p,&n,0,0); ai(p,&n,0,1); jnc(p,&n,2); CHECK_JUMP("JNC taken", 1);
760 n=0; li(p,&n,0,0xFFFF); ai(p,&n,0,1); jnc(p,&n,2); CHECK_JUMP("JNC not taken", 0);
761
762 /* 0x7FFF+1 is the signed overflow that is not a carry, so this separates the two */
763 printf(" JNO\n");
764 n=0; li(p,&n,0,1); ai(p,&n,0,1); jno(p,&n,2); CHECK_JUMP("JNO taken", 1);
765 n=0; li(p,&n,0,0x7FFF); ai(p,&n,0,1); jno(p,&n,2); CHECK_JUMP("JNO not taken", 0);
766
767 /* Parity comes from CB's SOURCE byte: 0x01 is one bit, 0x03 is two */
768 printf(" JOP\n");
769 n=0; li(p,&n,2,0x0100); li(p,&n,3,0); cb_rr(p,&n,2,3); jop(p,&n,2);
770 CHECK_JUMP("JOP taken", 1);
771 n=0; li(p,&n,2,0x0300); li(p,&n,3,0); cb_rr(p,&n,2,3); jop(p,&n,2);
772 CHECK_JUMP("JOP not taken", 0);
773
774#undef CHECK_JUMP
775}
776
777static void test_misc(void)
778{
779 printf("\n=== Misc ===\n");
780 uint8_t p[MAX_PROG]; uint16_t n;
781
782 /* SWPB */
783 printf(" SWPB R0\n");
784 n=0; li(p,&n,0,0x1234); swpb(p,&n,0); emit(p,&n,IDLE);
785 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("SWPB", 0, 0x3412);
786 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("SWPB", 0, 0x3412);
787
788 /* STST */
789 printf(" STST R0 (after EQ)\n");
790 n=0; li(p,&n,0,5); ci(p,&n,0,5); stst(p,&n,1); emit(p,&n,IDLE);
791 setup(NULL); load_prog(p,n); run_asm(); { uint16_t st=r16(REG(1));
792 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
793 check("STST EQ [ASM]", (st&0x2000)!=0, _det); }
794 setup(NULL); load_prog(p,n); run_c(); { uint16_t st=r16(REG(1));
795 snprintf(_det,sizeof(_det),"ST=%04X EQ-bit missing",st);
796 check("STST EQ [C]", (st&0x2000)!=0, _det); }
797
798 /* STWP */
799 printf(" STWP R0\n");
800 n=0; stwp(p,&n,0); emit(p,&n,IDLE);
801 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("STWP", 0, WP);
802 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("STWP", 0, WP);
803
804 /* X - the operand's VALUE is the instruction. R5 holds INC R0 (0x0580), so a core
805 that executed it correctly increments R0 without ever fetching 0x0580 from the
806 program stream. */
807 printf(" X R5 (executing INC R0)\n");
808 n=0; li(p,&n,5,0x0580); li(p,&n,0,5); x_reg(p,&n,5); emit(p,&n,IDLE);
809 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("X INC R0", 0, 6);
810 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("X INC R0", 0, 6);
811
812 /* LWPI - the following LI has to land in the NEW workspace, so this checks the
813 register file moved rather than just that a word was consumed. */
814 printf(" LWPI (register file moves)\n");
815 n=0; lwpi(p,&n,SCRATCH); li(p,&n,0,0x1234); stwp(p,&n,1); emit(p,&n,IDLE);
816 setup(NULL); load_prog(p,n); run_asm();
817 CHECK_MEM16("LWPI new R0", SCRATCH, 0x1234); CHECK_MEM16("LWPI WP", SCRATCH+2, SCRATCH);
818 setup(NULL); load_prog(p,n); run_c();
819 CHECK_MEM16("LWPI new R0", SCRATCH, 0x1234); CHECK_MEM16("LWPI WP", SCRATCH+2, SCRATCH);
820
821 /* LIMI - this core has no interrupt mask, so the only thing it can get wrong is the
822 immediate word: consume it and the LI below runs, fetch it as an opcode and the
823 program derails. That is exactly the failure worth a test. */
824 printf(" LIMI (immediate consumed)\n");
825 n=0; limi(p,&n,2); li(p,&n,0,7); emit(p,&n,IDLE);
826 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("LIMI then LI", 0, 7);
827 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("LIMI then LI", 0, 7);
828}
829
830/* PIX in BL mode, on the three things a byte-per-four-pixels layer decides:
831 the row stride, which pixel of the byte, and where the address wraps. A
832 width that is not a multiple of four is what separates the stride from the
833 pixel count, so every case here uses one. */
834static void test_pix(void)
835{
836 printf("\n=== F18A PIX (BL) ===\n");
837 uint8_t p[MAX_PROG]; uint16_t n;
838
839 /* Address only: VR35=10 is a stride of 3 bytes, so (x=5,y=3) is byte 10. */
840 printf(" PIX address, VR35=10\n");
841 n=0; li(p,&n,1,0x0503); li(p,&n,2,0x4000); pix(p,&n,1,2); emit(p,&n,IDLE);
842 setup(NULL); load_prog(p,n); mem[0x6020]=0; mem[0x6023]=10;
843 run_asm(); CHECK_REG("PIX addr", 2, 0x000A);
844 setup(NULL); load_prog(p,n); mem[0x6020]=0; mem[0x6023]=10;
845 run_c(); CHECK_REG("PIX addr", 2, 0x000A);
846
847 /* VR32=0xFF puts the layer at 0x3FC0, so row 100 leaves 16KB and wraps. */
848 printf(" PIX address wraps at 16KB\n");
849 n=0; li(p,&n,1,0x0064); li(p,&n,2,0x4000); pix(p,&n,1,2); emit(p,&n,IDLE);
850 setup(NULL); load_prog(p,n); mem[0x6020]=0xFF; mem[0x6023]=4;
851 run_asm(); CHECK_REG("PIX wrap", 2, 0x0024);
852 setup(NULL); load_prog(p,n); mem[0x6020]=0xFF; mem[0x6023]=4;
853 run_c(); CHECK_REG("PIX wrap", 2, 0x0024);
854
855 /* Colour 3 at x=5 is the second pixel of the byte, whatever the stride. */
856 printf(" PIX write, colour 3 at x=5\n");
857 n=0; li(p,&n,1,0x0503); li(p,&n,2,0x0003); pix(p,&n,1,2); emit(p,&n,IDLE);
858 setup(NULL); load_prog(p,n); mem[0x6020]=0; mem[0x6023]=10;
859 run_asm(); CHECK_MEM8("PIX write", 0x000A, 0x30);
860 setup(NULL); load_prog(p,n); mem[0x6020]=0; mem[0x6023]=10;
861 run_c(); CHECK_MEM8("PIX write", 0x000A, 0x30);
862}
863
864static void test_blwp(void)
865{
866 printf("\n=== BLWP/RTWP ===\n");
867 uint8_t p[MAX_PROG*4]; uint16_t n;
868
869 /*
870 * Layout within p[] placed at PROG:
871 * [0x00] BLWP @vec (4 bytes)
872 * [0x04] LI R0,0x1111 (4 bytes) <- return lands here
873 * [0x08] IDLE
874 * [0x10] vec_lo: new_wp (2 bytes)
875 * [0x12] vec_hi: sub_entry (2 bytes)
876 * [0x20] new workspace (32 bytes)
877 * [0x40] sub: LI R3,0xD0AE; RTWP
878 */
879 uint16_t vec_off = 0x10;
880 uint16_t new_wp_off = 0x20; /* new workspace within p[] */
881 uint16_t sub_off = 0x40;
882
883 uint16_t vec_addr = PROG + vec_off;
884 uint16_t new_wp_addr = PROG + new_wp_off;
885 uint16_t sub_addr = PROG + sub_off;
886
887 memset(p, 0, sizeof(p));
888 n = 0;
889 blwp(p, &n, vec_addr); /* BLWP @vec */
890 li(p, &n, 0, 0x1111); /* after return: R0=0x1111 */
891 emit(p, &n, IDLE);
892
893 /* vector: new_wp, then sub_entry */
894 p[vec_off] = (uint8_t)(new_wp_addr >> 8);
895 p[vec_off+1] = (uint8_t)(new_wp_addr & 0xFF);
896 p[vec_off+2] = (uint8_t)(sub_addr >> 8);
897 p[vec_off+3] = (uint8_t)(sub_addr & 0xFF);
898
899 /* subroutine at sub_off */
900 n = sub_off;
901 li(p, &n, 3, 0xD0AE); /* in new workspace: R3 = 0xD0AE */
902 rtwp(p, &n);
903
904 uint16_t prog_len = n;
905 printf(" BLWP/RTWP\n");
906
907 setup(NULL); load_prog(p, prog_len);
908 run_asm();
909 CHECK_REG("BLWP/RTWP R0 after return [ASM]", 0, 0x1111);
910
911 setup(NULL); load_prog(p, prog_len);
912 run_c();
913 CHECK_REG("BLWP/RTWP R0 after return [C]", 0, 0x1111);
914}
915
916/*
917 * The address decode above 16KB, which only the F18A personality has: a PICO9918 backs
918 * the whole 64KB with RAM, so the same programs have to reach plain memory there. The
919 * flat half runs against both cores, because agreeing with the assembly core is the
920 * point of it; the F18A half is the C core's alone, since asking an assembly core for a
921 * decode it does not have would be asserting a difference.
922 */
923static void test_decode_flat(void)
924{
925 printf("\n=== PICO9918 flat memory ===\n");
926 uint8_t p[MAX_PROG]; uint16_t n;
927
928 printf(" above the F18A's GRAM window is memory\n");
929 n=0; li(p,&n,1,0x4800); li(p,&n,2,0xBEEF); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
930 setup(NULL); load_prog(p,n); run_asm(); CHECK_MEM16(">4800 [ASM]", 0x4800, 0xBEEF);
931 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16(">4800 [C]", 0x4800, 0xBEEF);
932
933 printf(" the palette window does not mirror\n");
934 n=0; li(p,&n,1,0x5F82); li(p,&n,2,0x1234); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
935 setup(NULL); load_prog(p,n); run_asm(); CHECK_MEM16(">5F82 [ASM]", 0x5F82, 0x1234);
936 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16(">5F82 [C]", 0x5F82, 0x1234);
937
938 printf(" the scanline byte is writable, and >C000 up is there at all\n");
939 n=0; li(p,&n,1,0x7000); li(p,&n,2,0x0102); mov_ri(p,&n,2,1);
940 li(p,&n,3,0xD000); li(p,&n,4,0x0304); mov_ri(p,&n,4,3); mov_ar(p,&n,0xC010,5);
941 emit(p,&n,IDLE);
942 setup(NULL); load_prog(p,n); mem[0xC010]=0x77; mem[0xC011]=0x88;
943 run_asm(); CHECK_MEM16(">7000 [ASM]", 0x7000, 0x0102);
944 CHECK_MEM16(">D000 [ASM]", 0xD000, 0x0304); CHECK_REG(">C010 [ASM]", 5, 0x7788);
945 setup(NULL); load_prog(p,n); mem[0xC010]=0x77; mem[0xC011]=0x88;
946 run_c(); CHECK_MEM16(">7000 [C]", 0x7000, 0x0102);
947 CHECK_MEM16(">D000 [C]", 0xD000, 0x0304); CHECK_REG(">C010 [C]", 5, 0x7788);
948}
949
950static void test_decode(void)
951{
952 printf("\n=== F18A address decode (C core) ===\n");
953 uint8_t p[MAX_PROG]; uint16_t n;
954 f18aMemory = true;
955
956 printf(" GRAM is 2KB, mirrored\n");
957 n=0; li(p,&n,1,0x4800); li(p,&n,2,0xBEEF); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
958 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16("GRAM >4800", 0x4000, 0xBEEF);
959
960 printf(" palette is 128 bytes, mirrored\n");
961 n=0; li(p,&n,1,0x5F82); li(p,&n,2,0x1234); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
962 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16("palette >5F82", 0x5002, 0x1234);
963
964 printf(" registers are 64, mirrored\n");
965 n=0; li(p,&n,1,0x6F44); li(p,&n,2,0xAA55); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
966 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16("register >6F44", 0x6004, 0xAA55);
967
968 printf(" DMA ports are 16, mirrored\n");
969 n=0; li(p,&n,1,0x8F14); li(p,&n,2,0x0102); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
970 setup(NULL); load_prog(p,n); run_c(); CHECK_MEM16("DMA >8F14", 0x8004, 0x0102);
971
972 printf(" scanline and blanking are read-only\n");
973 n=0; li(p,&n,1,0x7000); clr(p,&n,2); mov_ri(p,&n,2,1); mov_ir(p,&n,1,3); emit(p,&n,IDLE);
974 setup(NULL); load_prog(p,n); mem[0x7000]=0x5A; mem[0x7001]=0xA5;
975 run_c(); CHECK_MEM16("scanline write dropped", 0x7000, 0x5AA5);
976 CHECK_REG("scanline read", 3, 0x5AA5);
977
978 printf(" version reads the byte the host reads from SR14\n");
979 n=0; mov_ar(p,&n,0xA246,2); emit(p,&n,IDLE);
980 setup(NULL); load_prog(p,n); mem[0xB00E]=0x1A; run_c(); CHECK_REG("version >A246", 2, 0x1A1A);
981
982 printf(" GPU status is the low seven bits of SR2\n");
983 n=0; li(p,&n,1,0xB000); li(p,&n,2,0x2A3F); mov_ri(p,&n,2,1); emit(p,&n,IDLE);
984 setup(NULL); load_prog(p,n); mem[0xB002]=0x80; run_c();
985 CHECK_MEM8("status >B000 -> SR2", 0xB002, 0xBF);
986 CHECK_MEM8("status left SR0 alone", 0xB000, 0x00);
987
988 printf(" nothing is there above >BFFF\n");
989 n=0; mov_ar(p,&n,0xC010,2); li(p,&n,1,0xD000); li(p,&n,3,0xFFFF); mov_ri(p,&n,3,1);
990 emit(p,&n,IDLE);
991 setup(NULL); load_prog(p,n); mem[0xC010]=0x77; mem[0xC011]=0x88;
992 run_c(); CHECK_REG("read >C010", 2, 0x0000);
993 CHECK_MEM16("write >D000 dropped", 0xD000, 0x0000);
994
995 f18aMemory = false;
996}
997
998static void test_stress(void)
999{
1000 printf("\n=== Stress: Fibonacci(10)=55 ===\n");
1001 uint8_t p[MAX_PROG]; uint16_t n;
1002
1003 /*
1004 * R0=10 (counter), R1=0 (a), R2=1 (b)
1005 * loop: R3=R1; R3+=R2; R1=R2; R2=R3; DEC R0; CI R0,0; JNE loop
1006 * Each iteration: 4 MOV/DEC * 2 bytes + ADD 2 bytes + CI 4 bytes + JNE 2 bytes = 16 bytes
1007 * JNE offset = -8 words
1008 */
1009 printf(" Fibonacci(10)\n");
1010 n=0;
1011 li(p,&n,0,10); li(p,&n,1,0); li(p,&n,2,1);
1012 /* loop start (12 bytes in) */
1013 mov_rr(p,&n,1,3); /* R3=R1 2 bytes */
1014 add_rr(p,&n,2,3); /* R3+=R2 2 bytes */
1015 mov_rr(p,&n,2,1); /* R1=R2 2 bytes */
1016 mov_rr(p,&n,3,2); /* R2=R3 2 bytes */
1017 dec(p,&n,0); /* R0-- 2 bytes */
1018 ci(p,&n,0,0); /* CI R0,0 4 bytes */
1019 jne(p,&n,(int8_t)(-8)); /* back 8 words=16 bytes to loop start */
1020 emit(p,&n,IDLE);
1021
1022 setup(NULL); load_prog(p,n); run_asm(); CHECK_REG("Fib(10) [ASM]", 1, 55);
1023 setup(NULL); load_prog(p,n); run_c(); CHECK_REG("Fib(10) [C]", 1, 55);
1024}
1025
1026/* =========================================================================
1027 * main
1028 * ====================================================================== */
1029int main(void)
1030{
1031#ifdef PICO_BUILD
1032 stdio_init_all();
1033
1034#if defined(LIB_PICO_STDIO_USB)
1035 for (int i = 0; i < 100 && !stdio_usb_connected(); i++)
1036 sleep_ms(100);
1037#else
1038 sleep_ms(2000);
1039#endif
1040#endif
1041
1042 printf("\n");
1043 printf("=========================================\n");
1044 printf(" TMS9900 Unit Tests \n");
1045#ifdef PICO_BUILD
1046 printf(" ASM core (%-4s) vs C core \n", TMS9900_ASM_CORE);
1047#else
1048 printf(" portable C core, both passes \n");
1049#endif
1050 printf("=========================================\n");
1051
1052 test_data_transfer();
1053 test_arithmetic();
1054 test_logical();
1055 test_shifts();
1056 test_branches();
1057 test_jumps();
1058 test_misc();
1059 test_pix();
1060 test_blwp();
1061 test_decode_flat();
1062 test_decode();
1063 test_stress();
1064
1065 printf("\n=========================================\n");
1066#ifdef PICO_BUILD
1067 printf(" Results: %d/%d passed (%d failed)\n", passed, total, failed);
1068#else
1069 printf(" Results: %d/%d passed (%d failed)\n", indepPassed, indepTotal, indepFailed);
1070 printf(" One core, so the %d checks run repeat every case that has an assembly half\n", total);
1071#endif
1072 printf("=========================================\n");
1073 if (failed == 0)
1074 printf(" ALL TESTS PASSED\n");
1075 else
1076 printf(" FAILURES DETECTED\n");
1077 printf("=========================================\n");
1078
1079#ifdef PICO_BUILD
1080 /* A board has nowhere to return an exit status to, so the LED carries it. */
1081 const uint LED = 25;
1082 gpio_init(LED);
1083 gpio_set_dir(LED, GPIO_OUT);
1084 while (1) {
1085 gpio_put(LED, failed == 0 ? 1 : 0);
1086 sleep_ms(500);
1087 gpio_put(LED, 0);
1088 sleep_ms(500);
1089 }
1090#endif
1091 return failed == 0 ? 0 : 1;
1092}
pico9918-core - TMS9900 CPU interpreter (portable C)