pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
tms9900.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - TMS9900 CPU interpreter (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 *
12 * This is a full reimplementation of JasonACT's RP2040 thumb assembly core
13 * (thumb9900_m0.S / thumb9900_m33.S) intended for non-ARM targets. It aims
14 * to be functionally identical where it matters to the GPU: the status flag layout
15 * matches the assembly core (LGT=0x80, AGT=0x40, EQ=0x20, C=0x10, OV=0x08, P=0x04).
16 * CRU (LDCR, STCR, SBO, SBZ, TB) and CKON/CKOF/LREX are no-ops in both cores.
17 *
18 * Memory layout follows the existing GPU glue: a 64 KiB byte array that
19 * stores TMS9900 words in big-endian order. The workspace pointer (WP) is a
20 * byte address into that array and register access uses big-endian word
21 * loads/stores.
22 *
23 * The interpreter stops when bit0 of the control byte at regx38 is cleared,
24 * or when an IDLE instruction is executed, returning the current PC just like
25 * the assembly core. Behavior of auto-increment and index modes matches the
26 * original core (increments are 1 for byte ops, 2 for word ops).
27 */
28
29#include "tms9900.h"
30
31#include <stddef.h>
32
33
34/*
35 * Parity table for byte operations: P (bit 2) is set when the byte has an odd number
36 * of 1-bits. Matches the PARITY table in thumb9900_m0.S.
37 */
38static const uint8_t parity_tbl[256] = {
39 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
40 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
41 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
42 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
43 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
44 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
45 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
46 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0};
47
48/*
49 * Flag helpers
50 */
51static inline void set_flags_word(Tms9900Cpu* cpu, uint16_t v)
52{
53 cpu->st &= 0x1E; /* preserve C/OV/P only (assembly uses AND #0x1E) */
54 int16_t sv = (int16_t)v;
55 if (v == 0)
56 {
57 cpu->st |= TMS_ST_EQ;
58 return;
59 }
60 if (sv > 0)
61 {
62 cpu->st |= (TMS_ST_LGT | TMS_ST_AGT);
63 }
64 else
65 {
66 cpu->st |= TMS_ST_LGT;
67 }
68}
69
70static inline void set_flags_byte(Tms9900Cpu* cpu, uint8_t v)
71{
72 /* Assembly OP_COMP_B: sets LGT/AGT/EQ + parity from PARITY table */
73 cpu->st = (uint16_t)((cpu->st & ~(TMS_ST_LGT | TMS_ST_AGT | TMS_ST_EQ | TMS_ST_P)) | parity_tbl[v]);
74 int8_t sv = (int8_t)v;
75 if (v == 0)
76 {
77 cpu->st |= TMS_ST_EQ;
78 return;
79 }
80 if (sv > 0)
81 cpu->st |= (TMS_ST_LGT | TMS_ST_AGT);
82 else
83 cpu->st |= TMS_ST_LGT;
84}
85
86/*
87 * Memory helpers (big-endian words), through whichever map the personality has.
88 *
89 * A PICO9918 backs the whole 64KB with RAM, which is what the assembly cores do and what
90 * every accessor below takes first. An F18A does not: only its first 16KB is memory, and
91 * above that each nibble is a window holding a handful of real bytes, mirrored across the
92 * whole 4KB:
93 *
94 * nibble window size access
95 * 0-3 VRAM 16KB read/write
96 * 4 GRAM 2KB read/write
97 * 5 palette 128B read/write
98 * 6 VDP registers 64B read/write
99 * 7 scanline, blanking 2B read-only
100 * 8 DMA ports 16B read/write
101 * 9 MAC, never built - absent
102 * A version 1B read-only, the byte the host reads from SR14
103 * B GPU status 1B write-only, the low seven bits of SR2
104 * C-F unimplemented - absent
105 *
106 * An absent read gives zero and an absent write is dropped, both as the part does. The
107 * workspace does not come through here: on an F18A the registers are real, so the memory
108 * this core parks them in at >FFFE is a window that answers nothing.
109 */
110static const uint16_t gpu_window_base[16] = {0x0000, 0x0000, 0x0000, 0x0000, 0x4000, 0x5000, 0x6000, 0x7000,
111 0x8000, 0x0000, 0xB00E, 0xB002, 0x0000, 0x0000, 0x0000, 0x0000};
112
113static const uint16_t gpu_window_mask[16] = {0x3FFF, 0x3FFF, 0x3FFF, 0x3FFF, 0x07FF, 0x007F, 0x003F, 0x0001,
114 0x000F, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
115
116/* which windows answer, by nibble: 0-8 and A read, 0-6, 8 and B write */
117#define GPU_WINDOW_READS 0x05FFu
118#define GPU_WINDOW_WRITES 0x097Fu
119
120static inline uint32_t gpu_addr(uint16_t a)
121{
122 const uint32_t w = a >> 12;
123 return gpu_window_base[w] | (a & gpu_window_mask[w]);
124}
125
126static inline uint8_t rd8(Tms9900Cpu* cpu, uint16_t a)
127{
128 if (!cpu->f18aMemory) return cpu->mem[a];
129 if (!((GPU_WINDOW_READS >> (a >> 12)) & 1)) return 0;
130 return cpu->mem[gpu_addr(a)];
131}
132
133static inline void wr8(Tms9900Cpu* cpu, uint16_t a, uint8_t v)
134{
135 if (!cpu->f18aMemory)
136 {
137 cpu->mem[a] = v;
138 return;
139 }
140 if (!((GPU_WINDOW_WRITES >> (a >> 12)) & 1)) return;
141 const uint32_t at = gpu_addr(a);
142
143 /* bit 7 of SR2 says the GPU is running and stays ours; the program owns the other seven */
144 cpu->mem[at] = (at == 0xB002) ? (uint8_t)((v & 0x7F) | (cpu->mem[at] & 0x80)) : v;
145}
146
147static inline uint16_t rd16(Tms9900Cpu* cpu, uint16_t a)
148{
149 return (uint16_t)((rd8(cpu, a) << 8) | rd8(cpu, (uint16_t)(a + 1)));
150}
151
152static inline void wr16(Tms9900Cpu* cpu, uint16_t a, uint16_t v)
153{
154 wr8(cpu, a, (uint8_t)(v >> 8));
155 wr8(cpu, (uint16_t)(a + 1), (uint8_t)(v & 0xFF));
156}
157
158/* Report a write to an address the program chose - see Tms9900Cpu::onWrite, which
159 also carries what this deliberately does not cover. Decoded, so a watcher is
160 written against the one address a window really has. */
161#if defined(TMS9900_WATCH_WRITES)
162static inline void watch_write(Tms9900Cpu* cpu, uint32_t addr)
163{
164 if (cpu->onWrite) cpu->onWrite(cpu->mem, cpu->f18aMemory ? gpu_addr((uint16_t)addr) : addr);
165}
166#else
167#define watch_write(cpu, addr) ((void)0)
168#endif
169
170/* Workspace address: WP + r*2 as uint32_t to handle WP=0xFFFE overflow
171 * past the 64KB boundary into the workspace overflow area */
172static inline uint32_t wp_addr(Tms9900Cpu* cpu, uint8_t r)
173{
174 return (uint32_t)cpu->wp + ((uint32_t)r << 1);
175}
176
177static inline uint16_t get_reg(Tms9900Cpu* cpu, uint8_t r)
178{
179 uint32_t a = wp_addr(cpu, r);
180 return (uint16_t)((cpu->mem[a] << 8) | cpu->mem[a + 1]);
181}
182
183static inline void set_reg(Tms9900Cpu* cpu, uint8_t r, uint16_t v)
184{
185 uint32_t a = wp_addr(cpu, r);
186 cpu->mem[a] = (uint8_t)(v >> 8);
187 cpu->mem[a + 1] = (uint8_t)(v & 0xFF);
188}
189
190/* Operand addressing */
191typedef struct Operand
192{
193 uint16_t addr; /* effective address for memory targets */
194 uint16_t val; /* value loaded */
195 uint8_t reg; /* register index */
196 uint8_t mode; /* 0=reg,1=indirect,2=indexed,3=auto-inc */
197 uint8_t is_byte;
198} Operand;
199
200static inline uint16_t fetchw(Tms9900Cpu* cpu)
201{
202 uint16_t a = (uint16_t)cpu->pc;
203 cpu->pc = (a + 2) & 0xFFFF;
204 return rd16(cpu, a);
205}
206
207static Operand decode_operand(Tms9900Cpu* cpu, uint8_t field, uint8_t is_byte)
208{
209 Operand o = {0};
210 o.mode = (field >> 4) & 0x3;
211 o.reg = field & 0xF;
212 o.is_byte = is_byte;
213
214 switch (o.mode)
215 {
216 case 0: /* register direct */
217 if (is_byte) o.addr = (uint16_t)wp_addr(cpu, o.reg); /* high byte address */
218 o.val = is_byte ? cpu->mem[wp_addr(cpu, o.reg)] : get_reg(cpu, o.reg);
219 break;
220 case 1: /* indirect - assembly word-aligns effective address for word ops */
221 o.addr = get_reg(cpu, o.reg);
222 if (!is_byte) o.addr &= 0xFFFE;
223 o.val = is_byte ? rd8(cpu, o.addr) : rd16(cpu, o.addr);
224 break;
225 case 2:
226 { /* indexed - when reg==0, address is the immediate offset only (absolute) */
227 uint16_t offset = fetchw(cpu);
228 uint16_t ea;
229 if (o.reg == 0)
230 ea = offset; /* @address - no register added (assembly: CMP R5,#0; BEQ skip_add) */
231 else
232 ea = (uint16_t)(get_reg(cpu, o.reg) + offset);
233 o.addr = is_byte ? ea : (ea & 0xFFFE);
234 o.val = is_byte ? rd8(cpu, o.addr) : rd16(cpu, o.addr);
235 break;
236 }
237 case 3:
238 { /* auto-increment: effective address = old register value (word-aligned for word ops),
239 * register is updated to old+inc (assembly does this before returning the address) */
240 uint16_t raw = get_reg(cpu, o.reg);
241 uint16_t inc = is_byte ? 1u : 2u;
242 set_reg(cpu, o.reg, (uint16_t)(raw + inc));
243 o.addr = is_byte ? raw : (raw & 0xFFFE);
244 o.val = is_byte ? rd8(cpu, o.addr) : rd16(cpu, o.addr);
245 break;
246 }
247 }
248 return o;
249}
250
251static void store_operand(Tms9900Cpu* cpu, const Operand* o, uint16_t v)
252{
253 if (o->mode == 0)
254 {
255 if (o->is_byte)
256 {
257 cpu->mem[wp_addr(cpu, o->reg)] = (uint8_t)v;
258 }
259 else
260 {
261 set_reg(cpu, o->reg, v);
262 }
263 }
264 else
265 {
266 if (o->is_byte)
267 {
268 wr8(cpu, o->addr, (uint8_t)v);
269 }
270 else
271 {
272 wr16(cpu, o->addr, v);
273 }
274 watch_write(cpu, o->addr);
275 }
276}
277
278/* ALU helpers */
279static inline uint16_t add16(Tms9900Cpu* cpu, uint16_t a, uint16_t b)
280{
281 uint32_t res = (uint32_t)a + (uint32_t)b;
282 uint16_t r16 = (uint16_t)res;
283 cpu->st &= 0x06; /* preserve only parity bit; clear LGT/AGT/EQ/OV/C */
284 if (res & 0x10000) cpu->st |= TMS_ST_C;
285
286 /* overflow: sign(a)==sign(b) and sign differs from result */
287 if (((a ^ b) & 0x8000) == 0 && ((a ^ r16) & 0x8000)) cpu->st |= TMS_ST_OV;
288 set_flags_word(cpu, r16);
289 return r16;
290}
291
292static inline uint16_t sub16(Tms9900Cpu* cpu, uint16_t a, uint16_t b)
293{
294 uint32_t res = (uint32_t)a - (uint32_t)b;
295 uint16_t r16 = (uint16_t)res;
296 cpu->st &= 0x06;
297
298 /* Assembly "borrow NOT" convention: carry=1 means no borrow (src==0 or dst>=result) */
299 if (b == 0 || a >= r16) cpu->st |= TMS_ST_C;
300
301 /* overflow: sign(a)!=sign(b) and sign differs from result */
302 if (((a ^ b) & 0x8000) && ((a ^ r16) & 0x8000)) cpu->st |= TMS_ST_OV;
303 set_flags_word(cpu, r16);
304 return r16;
305}
306
307static inline uint8_t add8(Tms9900Cpu* cpu, uint8_t a, uint8_t b)
308{
309 uint16_t res = (uint16_t)a + (uint16_t)b;
310 uint8_t r8 = (uint8_t)res;
311 cpu->st &= 0x06;
312 if (res & 0x100) cpu->st |= TMS_ST_C;
313 if (((a ^ b) & 0x80) == 0 && ((a ^ r8) & 0x80)) cpu->st |= TMS_ST_OV;
314 set_flags_byte(cpu, r8);
315 return r8;
316}
317
318static inline uint8_t sub8(Tms9900Cpu* cpu, uint8_t a, uint8_t b)
319{
320 uint16_t res = (uint16_t)a - (uint16_t)b;
321 uint8_t r8 = (uint8_t)res;
322 cpu->st &= 0x06;
323
324 /* Assembly "borrow NOT" convention: carry=1 means no borrow (src==0 or dst>=result) */
325 if (b == 0 || a >= r8) cpu->st |= TMS_ST_C;
326 if (((a ^ b) & 0x80) && ((a ^ r8) & 0x80)) cpu->st |= TMS_ST_OV;
327 set_flags_byte(cpu, r8);
328 return r8;
329}
330
331/* cmp16: compare src against dst (first operand vs second operand in TMS9900 convention).
332 * Assembly I_C: CMP R5,R2 where R5=src, R2=dst. LGT set when src > dst (unsigned).
333 * Assembly I_CI: CMP R2,R5 where R2=dst, R5=imm. LGT set when dst > imm (unsigned).
334 * Both callers must pass (first_operand, second_operand) in the correct TMS9900 order. */
335static inline void cmp16(Tms9900Cpu* cpu, uint16_t first, uint16_t second)
336{
337 /* Compare sets LGT/AGT/EQ only; C and OV are preserved */
338 cpu->st &= 0x1E;
339 if (first == second)
340 {
341 cpu->st |= TMS_ST_EQ;
342 return;
343 }
344 if (first > second) cpu->st |= TMS_ST_LGT; /* unsigned greater */
345 if ((int16_t)first > (int16_t)second) cpu->st |= TMS_ST_AGT; /* signed greater */
346}
347
348static inline void cmp8(Tms9900Cpu* cpu, uint8_t src, uint8_t dst)
349{
350 /* Assembly I_CB: preserves C/OV, sets parity of SOURCE, then sets LGT/AGT/EQ */
351 cpu->st = (uint16_t)((cpu->st & 0x1A) | parity_tbl[src]);
352 if (src == dst)
353 {
354 cpu->st |= TMS_ST_EQ;
355 return;
356 }
357 if (src > dst) cpu->st |= TMS_ST_LGT;
358 if ((int8_t)src > (int8_t)dst) cpu->st |= TMS_ST_AGT;
359}
360
361static inline uint16_t slx16(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
362{
363 cpu->st &= 0x06;
364 if (count == 0) count = 16;
365 uint32_t vv = (uint32_t)v;
366 uint32_t mask_change = 0;
367
368 /* detect overflow like assembly: if sign changes during shift */
369 for (uint8_t i = 0; i < count; ++i)
370 {
371 uint32_t msb = vv & 0x8000;
372 vv <<= 1;
373 if (msb != (vv & 0x8000)) mask_change = 1;
374 }
375 if (vv & 0x10000) cpu->st |= TMS_ST_C;
376 uint16_t r = (uint16_t)vv;
377 if (mask_change) cpu->st |= TMS_ST_OV;
378 set_flags_word(cpu, r);
379 return r;
380}
381
382static inline uint16_t sra16(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
383{
384 /* Assembly: MOVS R4,#0x0E; ANDS R1,R4 - keeps OV/P/bit1, clears C (and LGT/AGT/EQ) */
385 cpu->st &= 0x0E;
386 if (count == 0) count = 16;
387 int32_t vv = (int32_t)(int16_t)v;
388 uint16_t carry = 0;
389 for (uint8_t i = 0; i < count; ++i)
390 {
391 carry = (uint16_t)((uint32_t)vv & 1u);
392 vv >>= 1; /* arithmetic right shift on signed preserves sign bit */
393 }
394 if (carry) cpu->st |= TMS_ST_C;
395 uint16_t r = (uint16_t)vv;
396 set_flags_word(cpu, r);
397 return r;
398}
399
400static inline uint16_t srl16(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
401{
402 /* Assembly: MOVS R4,#0x0E; ANDS R1,R4 - keeps OV/P/bit1, clears C */
403 cpu->st &= 0x0E;
404 if (count == 0) count = 16;
405 uint32_t vv = v;
406 uint16_t carry = 0;
407 for (uint8_t i = 0; i < count; ++i)
408 {
409 carry = (uint16_t)(vv & 1u);
410 vv >>= 1;
411 }
412 if (carry) cpu->st |= TMS_ST_C;
413 uint16_t r = (uint16_t)vv;
414 set_flags_word(cpu, r);
415 return r;
416}
417
418static inline uint16_t src16(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
419{
420 /* Assembly: MOVS R4,#0x0E; ANDS R1,R4 - keeps OV/P/bit1, clears C */
421 cpu->st &= 0x0E;
422 if (count == 0) count = 16;
423
424 /* Assembly: ORRS R0 = (v<<16)|v, then RORS by count. 32-bit avoids shift-by-16 UB */
425 uint32_t vv = v;
426 uint16_t r = (uint16_t)((vv >> count) | (vv << (16u - count)));
427 uint16_t carry = (uint16_t)((vv >> (count - 1u)) & 1u);
428 if (carry) cpu->st |= TMS_ST_C;
429 set_flags_word(cpu, r);
430 return r;
431}
432
433static inline uint16_t slc16(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
434{
435 /* Assembly: MOVS R4,#0x0E; ANDS R1,R4 - keeps OV/P/bit1, clears C */
436 cpu->st &= 0x0E;
437 if (count == 0) count = 16;
438 count &= 0x1F;
439 uint32_t vv = v;
440 uint32_t rot = (vv << count) | (vv >> (16 - count));
441 uint16_t r = (uint16_t)rot;
442
443 /* Carry = bit just before wrap (count-1) */
444 uint16_t carry = (uint16_t)((vv << (count - 1)) & 0x8000u);
445 if (carry) cpu->st |= TMS_ST_C;
446 set_flags_word(cpu, r);
447 return r;
448}
449
450static inline uint16_t src_through_c(Tms9900Cpu* cpu, uint16_t v, uint8_t count)
451{
452 cpu->st &= 0x06;
453 if (count == 0) count = 16;
454 uint32_t vv = ((uint32_t)v << 1) | ((cpu->st & TMS_ST_C) ? 1u : 0u);
455 for (uint8_t i = 0; i < count; ++i)
456 {
457 uint32_t c = vv & 1u;
458 vv >>= 1;
459 if (c) vv |= 0x8000u;
460 cpu->st = (cpu->st & ~TMS_ST_C) | (c ? TMS_ST_C : 0);
461 }
462 uint16_t r = (uint16_t)vv;
463 set_flags_word(cpu, r);
464 return r;
465}
466
467/* Forward declarations (needed for X instruction dispatch) */
468static inline void handle_two_operand(Tms9900Cpu* cpu, uint16_t inst);
469static inline void handle_format9(Tms9900Cpu* cpu, uint16_t inst);
470static inline int handle_branch_group(Tms9900Cpu* cpu, uint16_t inst);
471static inline void handle_shift_rotate(Tms9900Cpu* cpu, uint16_t inst);
472static inline void handle_f18a_stack(Tms9900Cpu* cpu, uint16_t inst);
473
474/* Function: handle_immediate_system
475 * ----------------------------------------
476 * Execute op group 0 (immediate/system). Returns 0 to stop on IDLE.
477 */
478static inline int handle_immediate_system(Tms9900Cpu* cpu, uint16_t inst)
479{
480 /* Sub-opcode is (inst >> 5) & 0x1F; register is inst & 0xF */
481 uint8_t sub = (uint8_t)((inst >> 5) & 0x1F);
482 uint8_t dest_reg = inst & 0xF;
483
484 switch (sub)
485 {
486 case 0x10: /* LI - 0x0200 */
487 {
488 uint16_t imm = fetchw(cpu);
489 set_reg(cpu, dest_reg, imm);
490 cpu->st &= 0x1E;
491 set_flags_word(cpu, imm);
492 break;
493 }
494 case 0x11: /* AI - 0x0220 */
495 {
496 uint16_t imm = fetchw(cpu);
497 uint16_t res = add16(cpu, get_reg(cpu, dest_reg), imm);
498 set_reg(cpu, dest_reg, res);
499 break;
500 }
501 case 0x12: /* ANDI - 0x0240 */
502 {
503 uint16_t imm = fetchw(cpu);
504 uint16_t res = get_reg(cpu, dest_reg) & imm;
505 set_reg(cpu, dest_reg, res);
506 cpu->st &= 0x1E;
507 set_flags_word(cpu, res);
508 break;
509 }
510 case 0x13: /* ORI - 0x0260 */
511 {
512 uint16_t imm = fetchw(cpu);
513 uint16_t res = get_reg(cpu, dest_reg) | imm;
514 set_reg(cpu, dest_reg, res);
515 cpu->st &= 0x1E;
516 set_flags_word(cpu, res);
517 break;
518 }
519 case 0x14: /* CI - 0x0280 */
520 {
521 uint16_t imm = fetchw(cpu);
522 cmp16(cpu, get_reg(cpu, dest_reg), imm);
523 break;
524 }
525 case 0x15: /* STWP - 0x02A0 - store WP into dest register */ set_reg(cpu, dest_reg, cpu->wp); break;
526 case 0x16: /* STST - 0x02C0 - store ST into dest register */
527 set_reg(cpu, dest_reg, (uint16_t)cpu->st << 8);
528 break;
529 case 0x17: /* LWPI - 0x02E0 - load WP immediate (word-aligned) */ cpu->wp = fetchw(cpu) & 0xFFFE; break;
530 case 0x18: /* LIMI - 0x0300 - load interrupt mask (skip imm word) */
531 fetchw(cpu); /* consume immediate, ignore (no interrupt mask in this core) */
532 break;
533 case 0x1A: /* IDLE - 0x0340 - stop execution */ return 0;
534 case 0x1C: /* RTWP - 0x0380 */
535 {
536 /* Restore ST/PC/WP from R15/R14/R13 (offsets 30/28/26) of current workspace */
537 uint32_t owp = (uint32_t)cpu->wp;
538 cpu->st = cpu->mem[owp + 30];
539 cpu->pc = (uint16_t)((cpu->mem[owp + 28] << 8) | cpu->mem[owp + 29]) & 0xFFFE;
540 cpu->wp = (uint16_t)((cpu->mem[owp + 26] << 8) | cpu->mem[owp + 27]) & 0xFFFE;
541 break;
542 }
543 case 0x1D: /* CKON - 0x03A0 */
544 case 0x1E: /* CKOF - 0x03C0 */
545 case 0x1F: /* LREX - 0x03E0 */ break;
546 default: break;
547 }
548 return 1;
549}
550
551/* Function: handle_jump_single
552 * ----------------------------------------
553 * Execute single-operand instructions (opcodes 0x0400-0x07FF).
554 * Sub-opcode is bits 10:6 of the instruction word.
555 */
556static inline void handle_jump_single(Tms9900Cpu* cpu, uint16_t inst)
557{
558 uint8_t sub = (inst >> 6) & 0x1F; /* bits 10:6 */
559 switch (sub)
560 {
561 case 0x10: /* BLWP */
562 {
563 Operand s = decode_operand(cpu, inst & 0x3F, 0);
564 uint32_t src_addr = wp_addr(cpu, inst & 0xF);
565 uint16_t new_wp = (s.mode == 0) ? (uint16_t)((cpu->mem[src_addr] << 8) | cpu->mem[src_addr + 1]) & 0xFFFE
566 : rd16(cpu, s.addr) & 0xFFFE;
567 uint16_t old_wp = cpu->wp;
568 uint16_t old_pc = cpu->pc;
569 uint16_t old_st = cpu->st;
570 cpu->wp = new_wp;
571
572 /* new workspace is always in normal address space (not overflow) */
573 wr16(cpu, (uint16_t)(new_wp + 26), old_wp);
574 wr16(cpu, (uint16_t)(new_wp + 28), old_pc);
575 wr16(cpu, (uint16_t)(new_wp + 30), (uint16_t)old_st << 8); /* ST→high byte, low byte=0 */
576 cpu->pc = (s.mode == 0) ? (uint16_t)((cpu->mem[src_addr + 2] << 8) | cpu->mem[src_addr + 3]) & 0xFFFE
577 : rd16(cpu, (uint16_t)(s.addr + 2)) & 0xFFFE;
578
579 /* Assembly does NOT clear ST - new context inherits caller's flags */
580 break;
581 }
582 case 0x11: /* B - branch to source operand address */
583 {
584 Operand s = decode_operand(cpu, inst & 0x3F, 0);
585 uint32_t target = (s.mode == 0) ? wp_addr(cpu, inst & 0xF) : (uint32_t)s.addr;
586 cpu->pc = target & 0xFFFE;
587 break;
588 }
589 case 0x12: /* X - execute instruction at source */
590 {
591 Operand s = decode_operand(cpu, inst & 0x3F, 0);
592 uint16_t x_inst = (s.mode == 0) ? s.val : rd16(cpu, s.addr);
593
594 /* Dispatch the fetched instruction (PC is NOT advanced by X itself) */
595 uint8_t x_hi = (uint8_t)(x_inst >> 8);
596 if (x_hi >= 0x40)
597 handle_two_operand(cpu, x_inst);
598 else if (x_hi >= 0x20)
599 handle_format9(cpu, x_inst);
600 else if (x_hi >= 0x10)
601 handle_branch_group(cpu, x_inst);
602 else if (x_hi >= 0x0C)
603 {
604 if (x_hi == 0x0E)
605 handle_shift_rotate(cpu, x_inst);
606 else
607 handle_f18a_stack(cpu, x_inst);
608 }
609 else if (x_hi >= 0x08)
610 handle_shift_rotate(cpu, x_inst);
611 else if (x_hi >= 0x04)
612 handle_jump_single(cpu, x_inst);
613 else
614 handle_immediate_system(cpu, x_inst);
615 break;
616 }
617 case 0x13: /* CLR - no flag update (assembly does not touch ST) */
618 {
619 Operand d = decode_operand(cpu, inst & 0x3F, 0);
620 store_operand(cpu, &d, 0);
621 break;
622 }
623 case 0x14: /* NEG */
624 {
625 Operand d = decode_operand(cpu, inst & 0x3F, 0);
626 cpu->st &= 0x06;
627 if (d.val == 0x8000u)
628 {
629 cpu->st |= TMS_ST_OV;
630 set_flags_word(cpu, d.val); /* flags on 0x8000 = LGT only */
631 }
632 else
633 {
634 uint16_t res = (uint16_t)(0u - d.val);
635 if (res == 0) cpu->st |= TMS_ST_C;
636 store_operand(cpu, &d, res);
637 set_flags_word(cpu, res);
638 }
639 break;
640 }
641 case 0x15: /* INV */
642 {
643 Operand d = decode_operand(cpu, inst & 0x3F, 0);
644 uint16_t res = (uint16_t)~d.val;
645 store_operand(cpu, &d, res);
646 cpu->st &= 0x1E;
647 set_flags_word(cpu, res);
648 break;
649 }
650 case 0x16: /* INC */
651 {
652 Operand d = decode_operand(cpu, inst & 0x3F, 0);
653 uint16_t res = add16(cpu, d.val, 1);
654 store_operand(cpu, &d, res);
655 break;
656 }
657 case 0x17: /* INCT */
658 {
659 Operand d = decode_operand(cpu, inst & 0x3F, 0);
660 uint16_t res = add16(cpu, d.val, 2);
661 store_operand(cpu, &d, res);
662 break;
663 }
664 case 0x18: /* DEC */
665 {
666 Operand d = decode_operand(cpu, inst & 0x3F, 0);
667 uint16_t res = sub16(cpu, d.val, 1);
668 store_operand(cpu, &d, res);
669 break;
670 }
671 case 0x19: /* DECT */
672 {
673 Operand d = decode_operand(cpu, inst & 0x3F, 0);
674 uint16_t res = sub16(cpu, d.val, 2);
675 store_operand(cpu, &d, res);
676 break;
677 }
678 case 0x1A: /* BL - branch and link, save PC to R11 */
679 {
680 Operand s = decode_operand(cpu, inst & 0x3F, 0);
681 set_reg(cpu, 11, (uint16_t)cpu->pc);
682 uint32_t target = (s.mode == 0) ? wp_addr(cpu, inst & 0xF) : (uint32_t)s.addr;
683 cpu->pc = target & 0xFFFE;
684 break;
685 }
686 case 0x1B: /* SWPB */
687 {
688 Operand d = decode_operand(cpu, inst & 0x3F, 0);
689 uint16_t res = (uint16_t)((d.val << 8) | (d.val >> 8));
690 store_operand(cpu, &d, res);
691 break;
692 }
693 case 0x1C: /* SETO - no flag update (assembly does not touch ST) */
694 {
695 Operand d = decode_operand(cpu, inst & 0x3F, 0);
696 store_operand(cpu, &d, 0xFFFF);
697 break;
698 }
699 case 0x1D: /* ABS */
700 {
701 Operand d = decode_operand(cpu, inst & 0x3F, 0);
702 int16_t sv = (int16_t)d.val;
703
704 /* Assembly: ST &= 0x06 (clears C and OV, keeps only P/bit1) */
705 cpu->st &= 0x06;
706 if (d.val == 0x8000u)
707 {
708 /* Overflow case: can't negate 0x8000; set OV, leave value unchanged */
709 cpu->st |= TMS_ST_OV;
710 set_flags_word(cpu, d.val);
711 }
712 else if (sv < 0)
713 {
714 uint16_t res = (uint16_t)(0u - d.val);
715 store_operand(cpu, &d, res);
716 set_flags_word(cpu, d.val);
717 }
718 else
719 {
720 /* Positive/zero: flags only, no store */
721 set_flags_word(cpu, d.val);
722 }
723 break;
724 }
725 case 0x1E: /* LDCR - CRU not emulated */
726 case 0x1F: /* STCR - CRU not emulated */
727 default: break;
728 }
729}
730
731/* Function: handle_branch_group
732 * ----------------------------------------
733 * Execute conditional/unconditional jumps (opcodes 0x1000-0x1FFF).
734 * Displacement is a signed byte in bits 7:0, in word units (×2).
735 * Returns 0 if JMP self-loop detected (signals stop like IDLE), else 1.
736 */
737static inline int handle_branch_group(Tms9900Cpu* cpu, uint16_t inst)
738{
739 int16_t disp = (int16_t)((int8_t)(inst & 0xFF)) * 2;
740 uint8_t cond = (inst >> 8) & 0xF; /* 0x0=JMP, 0x1=JLT, ... */
741
742 switch (cond)
743 {
744 case 0x0: /* JMP - unconditional */
745
746 /* Assembly: self-jump (disp==-2, i.e. JMP $) treated as IDLE - exit emulation */
747 if (disp == -2)
748 {
749 cpu->pc = (cpu->pc - 2) & 0xFFFF;
750 return 0;
751 }
752 cpu->pc = (cpu->pc + disp) & 0xFFFF;
753 break;
754 case 0x1: /* JLT - signed < (AGT=0 and EQ=0) */
755 if ((cpu->st & (TMS_ST_AGT | TMS_ST_EQ)) == 0) cpu->pc = (cpu->pc + disp) & 0xFFFF;
756 break;
757 case 0x2: /* JLE - unsigned ≤ (LGT=0 or EQ=1) */
758 if (!(cpu->st & TMS_ST_LGT) || (cpu->st & TMS_ST_EQ)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
759 break;
760 case 0x3: /* JEQ */
761 if (cpu->st & TMS_ST_EQ) cpu->pc = (cpu->pc + disp) & 0xFFFF;
762 break;
763 case 0x4: /* JHE - unsigned ≥ (LGT=1 or EQ=1) */
764 if (cpu->st & (TMS_ST_LGT | TMS_ST_EQ)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
765 break;
766 case 0x5: /* JGT - signed > (AGT=1) */
767 if (cpu->st & TMS_ST_AGT) cpu->pc = (cpu->pc + disp) & 0xFFFF;
768 break;
769 case 0x6: /* JNE */
770 if (!(cpu->st & TMS_ST_EQ)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
771 break;
772 case 0x7: /* JNC - no carry */
773 if (!(cpu->st & TMS_ST_C)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
774 break;
775 case 0x8: /* JOC - carry set */
776 if (cpu->st & TMS_ST_C) cpu->pc = (cpu->pc + disp) & 0xFFFF;
777 break;
778 case 0x9: /* JNO - no overflow */
779 if (!(cpu->st & TMS_ST_OV)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
780 break;
781 case 0xA: /* JL - unsigned < (LGT=0 and EQ=0) */
782 if (!(cpu->st & (TMS_ST_LGT | TMS_ST_EQ))) cpu->pc = (cpu->pc + disp) & 0xFFFF;
783 break;
784 case 0xB: /* JH - unsigned > (LGT=1 and EQ=0) */
785 if ((cpu->st & TMS_ST_LGT) && !(cpu->st & TMS_ST_EQ)) cpu->pc = (cpu->pc + disp) & 0xFFFF;
786 break;
787 case 0xC: /* JOP - parity */
788 if (cpu->st & TMS_ST_P) cpu->pc = (cpu->pc + disp) & 0xFFFF;
789 break;
790
791 /* 0xD=SBO, 0xE=SBZ: CRU ops, NOP */
792 case 0xF: /* TB - CRU test, clears EQ */ cpu->st &= ~TMS_ST_EQ; break;
793 default: break;
794 }
795 return 1;
796}
797
798/* Function: handle_cru_single_bit
799 * ----------------------------------------
800 * Execute op group 3 (CRU single-bit) - treated as NOP here.
801 */
802static inline void handle_cru_single_bit(void) {}
803
804/* Function: handle_shift_rotate
805 * ----------------------------------------
806 * Execute op group 4 (shift/rotate).
807 */
808static inline void handle_shift_rotate(Tms9900Cpu* cpu, uint16_t inst)
809{
810 uint8_t sub = (inst >> 8) & 0xF;
811 uint8_t count = (inst >> 4) & 0xF;
812 uint8_t reg = inst & 0xF;
813 if (count == 0)
814 {
815 /* count=0: take low nibble of R0; if still 0, use 16 */
816 count = get_reg(cpu, 0) & 0xF;
817 if (count == 0) count = 16;
818 }
819 uint16_t v = get_reg(cpu, reg);
820 uint16_t res = v;
821 switch (sub)
822 {
823 case 0x8: res = sra16(cpu, v, count); break; /* SRA */
824 case 0x9: res = srl16(cpu, v, count); break; /* SRL */
825 case 0xA: res = slx16(cpu, v, count); break; /* SLA */
826 case 0xB: res = src16(cpu, v, count); break; /* SRC */
827 case 0xE: res = slc16(cpu, v, count); break; /* SLC (F18A) */
828 default: break;
829 }
830 set_reg(cpu, reg, res);
831}
832
833/* Handle COC/CZC/XOR/MPY/DIV (opcodes 0x2000-0x3FFF) */
834static inline void handle_format9(Tms9900Cpu* cpu, uint16_t inst)
835{
836 /* Bits 13:10 identify the instruction group (0x2000>>10=8, 0x2400>>10=9, etc.) */
837 uint8_t opcode = (uint8_t)((inst >> 10) & 0xF);
838
839 /* Dest register in bits 9:6, source operand in bits 5:0 */
840 uint8_t dreg = (inst >> 6) & 0xF;
841 Operand src = decode_operand(cpu, inst & 0x3F, 0);
842
843 switch (opcode)
844 {
845 case 0x8: /* 0x2000 COC - EQ if (src & dst) == src */
846 {
847 uint16_t d = get_reg(cpu, dreg);
848 if ((d & src.val) == src.val)
849 cpu->st |= TMS_ST_EQ;
850 else
851 cpu->st &= (uint16_t)~TMS_ST_EQ;
852 break;
853 }
854 case 0x9: /* 0x2400 CZC - EQ if (src & dst) == 0 */
855 {
856 uint16_t d = get_reg(cpu, dreg);
857 if ((d & src.val) == 0)
858 cpu->st |= TMS_ST_EQ;
859 else
860 cpu->st &= (uint16_t)~TMS_ST_EQ;
861 break;
862 }
863 case 0xA: /* 0x2800 XOR */
864 {
865 uint16_t res = get_reg(cpu, dreg) ^ src.val;
866 set_reg(cpu, dreg, res);
867 cpu->st &= 0x1E;
868 set_flags_word(cpu, res);
869 break;
870 }
871 case 0xB: /* 0x2C00-0x2FFF: XOP/F18A PIX */
872 {
873 static const uint8_t pix_mask[] = {0xC0, 0x30, 0x0C, 0x03};
874 static const uint8_t pix_shift[] = {6, 4, 2, 0};
875
876 uint16_t flags = get_reg(cpu, dreg);
877 uint16_t xy = src.val;
878 uint8_t x = (uint8_t)(xy >> 8);
879 uint8_t y = (uint8_t)(xy & 0xFF);
880
881 if (flags & 0x8000) /* PIX_M: BM mode */
882 {
883 /* Calculate pattern name table byte offset from X,Y (E/A 335-336) */
884 uint16_t r = (uint16_t)(((uint16_t)y << 5) | y);
885 r &= (uint16_t)~0xF8;
886 r |= (uint16_t)(x & 0xF8);
887
888 uint8_t vr04 = cpu->mem[0x6004];
889 r |= (uint16_t)((vr04 & 0x04) << 11);
890
891 set_reg(cpu, dreg, r);
892 }
893 else /* BL mode */
894 {
895 uint8_t vr35 = cpu->mem[0x6023];
896 uint16_t width = (vr35 == 0) ? 256u : (uint16_t)vr35;
897
898 /* Four pixels a byte, so the row stride rounds up, and the address wraps in 16KB. */
899 uint16_t stride = (uint16_t)((width + 3) >> 2);
900 uint8_t vr32 = cpu->mem[0x6020];
901 uint16_t a = (uint16_t)((((uint16_t)vr32 << 6) + y * stride + (x >> 2)) & 0x3FFF);
902
903 if (flags & 0x4000) /* PIX_A: address only */
904 {
905 set_reg(cpu, dreg, a);
906 break;
907 }
908
909 uint8_t s = (uint8_t)(x & 0x03);
910 uint8_t b = cpu->mem[a];
911 uint8_t pixv = (uint8_t)((b & pix_mask[s]) >> pix_shift[s]);
912
913 /* Write logic */
914 int do_write = 0;
915 if (!(flags & 0x0400)) /* bit 10 clear: writes allowed */
916 {
917 if (!(flags & 0x0200)) /* bit 9 clear: unconditional write */
918 {
919 do_write = 1;
920 }
921 else /* conditional write */
922 {
923 uint8_t pp_cmp = (uint8_t)((flags >> 4) & 0x03);
924 if (flags & 0x0100) /* PIX_E set: not-equal test */
925 {
926 if (pixv == pp_cmp) do_write = 1;
927 }
928 else /* equal test */
929 {
930 if (pixv != pp_cmp) do_write = 1;
931 }
932 }
933 }
934
935 if (do_write)
936 {
937 uint8_t pp_wr = (uint8_t)(flags & 0x03);
938 b = (uint8_t)((b & ~pix_mask[s]) | (pp_wr << pix_shift[s]));
939 cpu->mem[a] = b;
940 watch_write(cpu, a);
941 }
942
943 if (flags & 0x0800) /* PIX_R: read back pixel into dest reg */
944 {
945 flags = (uint16_t)((flags & ~0x03) | pixv);
946 set_reg(cpu, dreg, flags);
947 }
948 }
949 break;
950 }
951 case 0xE: /* 0x3800 MPY */
952 {
953 uint32_t prod = (uint32_t)get_reg(cpu, dreg) * (uint32_t)src.val;
954 set_reg(cpu, dreg, (uint16_t)(prod >> 16));
955 set_reg(cpu, (uint8_t)(dreg + 1), (uint16_t)(prod & 0xFFFF));
956 break;
957 }
958 case 0xF: /* 0x3C00 DIV */
959 {
960 uint32_t dividend = ((uint32_t)get_reg(cpu, dreg) << 16) | get_reg(cpu, (uint8_t)(dreg + 1));
961 if (src.val == 0 || (dividend >> 16) >= src.val)
962 {
963 cpu->st |= TMS_ST_OV;
964 break;
965 }
966 uint16_t quo = (uint16_t)(dividend / src.val);
967 uint16_t rem = (uint16_t)(dividend % src.val);
968 set_reg(cpu, dreg, quo);
969 set_reg(cpu, (uint8_t)(dreg + 1), rem);
970 cpu->st &= (uint16_t)~TMS_ST_OV;
971 break;
972 }
973 default: break;
974 }
975}
976
977/* Handle F18A stack ops: RET/CALL/PUSH/POP (opcodes 0x0C00-0x0DFF, 0x0F00-0x0FFF) */
978static inline void handle_f18a_stack(Tms9900Cpu* cpu, uint16_t inst)
979{
980 uint8_t hi = (inst >> 8) & 0xF; /* C=RET/CALL, D=PUSH, F=POP */
981 switch (hi)
982 {
983 case 0xC:
984 {
985 /* bit 7 alone chooses: RET over >0C00->0C7F, CALL over >0C80->0CFF */
986 if (inst & 0x80)
987 { /* CALL - push PC at OLD R15, pre-decrement R15 by 2, branch to source */
988 Operand s = decode_operand(cpu, inst & 0x3F, 0);
989 uint16_t old_sp = get_reg(cpu, 15) & 0xFFFE;
990 uint16_t new_sp = (uint16_t)(old_sp - 2);
991 set_reg(cpu, 15, new_sp);
992 wr16(cpu, old_sp, (uint16_t)cpu->pc); /* write at OLD sp, not new sp */
993 uint32_t target = (s.mode == 0) ? wp_addr(cpu, inst & 0xF) : (uint32_t)s.addr;
994 cpu->pc = target & 0xFFFE;
995 }
996 else
997 { /* RET - read PC from R15+2 (OLD R15), then post-increment R15 by 2 */
998
999 /* Assembly: ADD R4,R8; LDR R5,[R4,#2]; ... R15 += 2 */
1000 uint16_t sp = get_reg(cpu, 15) & 0xFFFE;
1001 cpu->pc = rd16(cpu, (uint16_t)(sp + 2)) & 0xFFFE;
1002 set_reg(cpu, 15, (uint16_t)(sp + 2));
1003 }
1004 break;
1005 }
1006 case 0xD: /* PUSH - write value at OLD R15, decrement R15 by 2 */
1007 {
1008 /* Assembly: R4=old_sp; R2=old_sp-2; store R2 as new R15; write at R4 (old_sp) */
1009 Operand s = decode_operand(cpu, inst & 0x3F, 0);
1010 uint16_t old_sp = get_reg(cpu, 15) & 0xFFFE;
1011 set_reg(cpu, 15, (uint16_t)(old_sp - 2));
1012 wr16(cpu, old_sp, s.val);
1013 break;
1014 }
1015 case 0xF: /* POP - read from OLD R15+2, increment R15 by 2 */
1016 {
1017 /* Assembly: R4=old_sp; R4+=2 (new_sp); store new_sp as R15; read from mem[new_sp] */
1018 Operand d = decode_operand(cpu, inst & 0x3F, 0);
1019 uint16_t old_sp = get_reg(cpu, 15) & 0xFFFE;
1020 uint16_t new_sp = (uint16_t)(old_sp + 2);
1021 set_reg(cpu, 15, new_sp);
1022 uint16_t v = rd16(cpu, new_sp);
1023 store_operand(cpu, &d, v);
1024 break;
1025 }
1026 default: break;
1027 }
1028}
1029
1030/* Handle two-operand instructions (opcodes 0x4000-0xFFFF) */
1031static inline void handle_two_operand(Tms9900Cpu* cpu, uint16_t inst)
1032{
1033 uint8_t opcode = (uint8_t)((inst >> 12) & 0xF);
1034 uint8_t byte_op = opcode & 1; /* odd opcode = byte variant */
1035 Operand src = decode_operand(cpu, (uint8_t)(inst & 0x3F), byte_op);
1036 Operand dst = decode_operand(cpu, (uint8_t)((inst >> 6) & 0x3F), byte_op);
1037
1038 switch (opcode)
1039 {
1040 case 0x4: /* SZC - dst &= ~src (word) */
1041 case 0x5: /* SZCB (byte) */
1042 {
1043 if (byte_op)
1044 {
1045 uint8_t res = (uint8_t)dst.val & (uint8_t)~src.val;
1046 store_operand(cpu, &dst, res);
1047 cpu->st &= 0x1E;
1048 set_flags_byte(cpu, res);
1049 }
1050 else
1051 {
1052 uint16_t res = dst.val & (uint16_t)~src.val;
1053 store_operand(cpu, &dst, res);
1054 cpu->st &= 0x1E;
1055 set_flags_word(cpu, res);
1056 }
1057 break;
1058 }
1059 case 0x6: /* S - subtract word */
1060 {
1061 uint16_t res = sub16(cpu, dst.val, src.val);
1062 store_operand(cpu, &dst, res);
1063 break;
1064 }
1065 case 0x7: /* SB - subtract byte */
1066 {
1067 uint8_t res = sub8(cpu, (uint8_t)dst.val, (uint8_t)src.val);
1068 store_operand(cpu, &dst, res);
1069 break;
1070 }
1071 case 0x8: /* C - compare word: assembly CMP src,dst → LGT when src > dst */
1072 {
1073 cmp16(cpu, src.val, dst.val);
1074 break;
1075 }
1076 case 0x9: /* CB - compare byte: assembly CMP src,dst → LGT when src > dst */
1077 {
1078 cmp8(cpu, (uint8_t)src.val, (uint8_t)dst.val);
1079 break;
1080 }
1081 case 0xA: /* A - add word */
1082 {
1083 uint16_t res = add16(cpu, dst.val, src.val);
1084 store_operand(cpu, &dst, res);
1085 break;
1086 }
1087 case 0xB: /* AB - add byte */
1088 {
1089 uint8_t res = add8(cpu, (uint8_t)dst.val, (uint8_t)src.val);
1090 store_operand(cpu, &dst, res);
1091 break;
1092 }
1093 case 0xC: /* MOV - move word */
1094 {
1095 store_operand(cpu, &dst, src.val);
1096 cpu->st &= 0x1E;
1097 set_flags_word(cpu, src.val);
1098 break;
1099 }
1100 case 0xD: /* MOVB - move byte */
1101 {
1102 uint8_t res = (uint8_t)src.val;
1103 store_operand(cpu, &dst, res);
1104 cpu->st &= 0x1E;
1105 set_flags_byte(cpu, res);
1106 break;
1107 }
1108 case 0xE: /* SOC - dst |= src (word) */
1109 {
1110 uint16_t res = dst.val | src.val;
1111 store_operand(cpu, &dst, res);
1112 cpu->st &= 0x1E;
1113 set_flags_word(cpu, res);
1114 break;
1115 }
1116 case 0xF: /* SOCB - dst |= src (byte) */
1117 {
1118 uint8_t res = (uint8_t)dst.val | (uint8_t)src.val;
1119 store_operand(cpu, &dst, res);
1120 cpu->st &= 0x1E;
1121 set_flags_byte(cpu, res);
1122 break;
1123 }
1124 default: break;
1125 }
1126}
1127void tms9900_init(Tms9900Cpu* cpu, uint8_t* mem, uint8_t* regx38, uint16_t pc, uint16_t wp)
1128{
1129 cpu->mem = mem;
1130 cpu->regx38 = regx38;
1131 cpu->pc = pc;
1132 cpu->wp = wp;
1133 cpu->st = 0;
1134 cpu->f18aMemory = false;
1135#if defined(TMS9900_WATCH_WRITES)
1136 cpu->onWrite = NULL;
1137#endif
1138}
1139
1140uint16_t run9900_c(Tms9900Cpu* cpu)
1141{
1142 return run9900_budget_c(cpu, 0, NULL);
1143}
1144
1145uint16_t run9900_budget_c(Tms9900Cpu* cpu, uint32_t budget, bool* outOfBudget)
1146{
1147 const int limited = budget != 0;
1148 if (outOfBudget) *outOfBudget = false;
1149 while ((*cpu->regx38 & 1u) != 0)
1150 {
1151 if (limited && budget-- == 0)
1152 {
1153 if (outOfBudget) *outOfBudget = true;
1154 return cpu->pc;
1155 }
1156 uint16_t inst = fetchw(cpu);
1157 uint8_t op_hi = (uint8_t)(inst >> 8); /* top byte of instruction */
1158
1159 if (op_hi >= 0x40)
1160 {
1161 /* 0x4000-0xFFFF: two-operand instructions */
1162 handle_two_operand(cpu, inst);
1163 }
1164 else if (op_hi >= 0x20)
1165 {
1166 /* 0x2000-0x3FFF: COC/CZC/XOR/XOP/MPY/DIV */
1167 handle_format9(cpu, inst);
1168 }
1169 else if (op_hi >= 0x10)
1170 {
1171 /* 0x1000-0x1FFF: conditional jumps and JMP */
1172 if (!handle_branch_group(cpu, inst)) return cpu->pc; /* JMP self-loop acts like IDLE */
1173 }
1174 else if (op_hi >= 0x0C)
1175 {
1176 /* 0x0C00-0x0FFF: F18A stack ops (RET/CALL/PUSH/POP) + SLC */
1177 if (op_hi == 0x0E)
1178 handle_shift_rotate(cpu, inst); /* SLC at 0x0E00 */
1179 else
1180 handle_f18a_stack(cpu, inst);
1181 }
1182 else if (op_hi >= 0x08)
1183 {
1184 /* 0x0800-0x0BFF: SRA/SRL/SLA/SRC */
1185 handle_shift_rotate(cpu, inst);
1186 }
1187 else if (op_hi >= 0x04)
1188 {
1189 /* 0x0400-0x07FF: single-operand (BLWP/B/X/CLR/NEG/INV/INC/INCT/DEC/DECT/BL/SWPB/SETO/ABS) */
1190 handle_jump_single(cpu, inst);
1191 }
1192 else
1193 {
1194 /* 0x0000-0x03FF: immediate/system (LI/AI/ANDI/ORI/CI/STWP/STST/LWPI/LIMI/IDLE/RTWP) */
1195 if (!handle_immediate_system(cpu, inst)) return cpu->pc;
1196 }
1197 }
1198 return cpu->pc;
1199}
pico9918-core - TMS9900 CPU interpreter (portable C)