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