pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
gpu.c
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - GPU Implementation
4 *
5 * Copyright (c) 2021 Troy Schrapel
6 *
7 * This code is licensed under the MIT license
8 *
9 * https://github.com/visrealm/pico9918-core
10 *
11 * Purpose: TMS9900 GPU glue code (adapted from pico9918/src/gpu/gpu.c)
12 *
13 * Credits: JasonACT (AtariAge)
14 *
15 */
16
17#include "gpu.h"
18/* the private instance layout: this TU reaches TMS_REGISTER/TMS_STATUS and the
19 struct directly, and the public GPU header does not supply them */
20#include "impl/pico9918_priv.h"
21#include "pico9918_config.h" /* PICO9918_CONF_* action keys */
22
23#include <string.h> /* memcpy */
24
25/* -------------------------------------------------------------------------
26 * Platform-specific includes
27 * ---------------------------------------------------------------------- */
28#ifdef PICO_BUILD
29#include "pico/stdlib.h"
30#include "hardware/structs/mpu.h"
31#include "hardware/sync.h"
32#include <hardware/flash.h>
33#include "pico.h" /* PICO_RP2040 */
34#endif
35
36#include "tms9900.h"
37#include "impl/platform.h" /* PICO9918_HOST_TIME_US */
38
39#if PICO9918_BUILD_DEBUG_API
40#include "pico9918_debug.h" /* pico9918_debug_gpu_step_n, defined here for the hook */
41#endif
42
43#if PICO9918_BUILD_STEP_CALLBACK && !(PICO9918_BUILD_DEBUG_API && defined(TMS9900_STEP_HOOK))
44#error "PICO9918_BUILD_STEP_CALLBACK needs PICO9918_DEBUG_API and the interpreter's step hook"
45#endif
46
47#if !PICO9918_GPU_BUDGETED
48/* run9900() implemented in platform/thumb9900_{m0,m33}.S */
49extern uint16_t run9900(uint8_t* memory, uint16_t pc, uint16_t wp, uint8_t* regx38);
50#else
51#if defined(TMS9900_WATCH_WRITES)
52static void gpuDmaWatch(uint8_t* vram, uint32_t addr);
53#endif
54
55#if PICO9918_BUILD_DEBUG_API && defined(TMS9900_STEP_HOOK)
56/* What one slice runs under. It carries the instance because the two sources below do not */
57typedef struct
58{
59 pico9918_gpu_step_fn fn;
60 void* userdata;
61 pico9918_t* inst;
62} gpu_step_ctx_t;
63
64/* Live for one pico9918_debug_gpu_step_n call, which is what lets a breakpoint list be
65 an argument to a run rather than a pair of fields on every instance. */
66static struct
67{
68 pico9918_gpu_step_fn fn;
69 void* userdata;
70} gpuStep;
71
72static bool gpuStepHook(Tms9900Cpu* cpu)
73{
74 const gpu_step_ctx_t* const ctx = (const gpu_step_ctx_t*)cpu->onStepData;
75
76 return ctx->fn(ctx->inst, (uint16_t)cpu->pc, ctx->userdata);
77}
78
79/* The call's callback wins over the instance's, so a pane pacing its own slice does not
80 fight the standing one. A slice the LIBRARY paced can only ever have the instance's,
81 having no call of its own to carry one. */
82static bool gpuStepResolve(PICO9918_INST_ARG gpu_step_ctx_t* out)
83{
84 out->inst = tms9918;
85 out->fn = gpuStep.fn;
86 out->userdata = gpuStep.userdata;
87
88#if PICO9918_BUILD_STEP_CALLBACK
89 if (!out->fn)
90 {
91 out->fn = tms9918->stepFn;
92 out->userdata = tms9918->stepUserdata;
93 }
94#endif
95
96 return out->fn != NULL;
97}
98#endif
99
100static uint16_t run9900Budget(PICO9918_INST_ARG uint8_t* mem, uint16_t pc, uint16_t* wp,
101 uint8_t* r38, uint32_t budget, uint16_t* st, bool* outOfBudget,
102 bool f18aMemory)
103{
104 Tms9900Cpu cpu;
105 tms9900_init(&cpu, mem, r38, pc, *wp);
106 cpu.f18aMemory = f18aMemory;
107#if defined(TMS9900_WATCH_WRITES)
108 cpu.onWrite = gpuDmaWatch;
109 cpu.onWriteMask = ~(uint32_t)0x1F;
110 cpu.onWriteMatch = 0x8000;
111#endif
112#if PICO9918_BUILD_DEBUG_API && defined(TMS9900_STEP_HOOK)
113 /* a local, so a nested or concurrent slice cannot take this one's callback with it */
114 gpu_step_ctx_t ctx;
115 if (gpuStepResolve(PICO9918_INST &ctx))
116 {
117 cpu.onStepData = &ctx;
118 cpu.onStep = gpuStepHook;
119 }
120#endif
121 cpu.st = *st;
122 const uint16_t next = run9900_budget_c(&cpu, budget, outOfBudget);
123 *st = cpu.st;
124 *wp = cpu.wp;
125 return next;
126}
127#endif
128
129/* -------------------------------------------------------------------------
130 * Config keys used to request config actions (save / forced save / pending
131 * confirm / pending cancel). Semantics are owned by the host - the GPU loop
132 * just detects a set key, clears it and notifies the host via the config
133 * callback.
134 * ---------------------------------------------------------------------- */
135static const uint8_t configActionKeys[] = {PICO9918_CONF_SAVE_TO_FLASH, PICO9918_CONF_SAVE_FORCED, PICO9918_CONF_PENDING_CONFIRM,
136 PICO9918_CONF_PENDING_CANCEL};
137
138/* -------------------------------------------------------------------------
139 * Callbacks (registered by the host application)
140 * ---------------------------------------------------------------------- */
141/* pico9918.h carries why only the storage differs between the two builds */
142#if PICO9918_SINGLE_INSTANCE
143static struct
144{
145 pico9918_gpu_flash_fn fn;
146 void* userdata;
147} gpuFlash;
148
149static struct
150{
151 pico9918_gpu_config_save_fn fn;
152 void* userdata;
153} gpuConfigSave;
154#define GPU_FLASH_CB gpuFlash
155#define GPU_CONFIG_SAVE_CB gpuConfigSave
156#else
157#define GPU_FLASH_CB tms9918->gpuFlash
158#define GPU_CONFIG_SAVE_CB tms9918->gpuConfigSave
159#endif
160
161void pico9918_gpu_set_flash_callback(PICO9918_INST_ARG pico9918_gpu_flash_fn cb, void* userdata)
162{
163 GPU_FLASH_CB.fn = cb;
164 GPU_FLASH_CB.userdata = userdata;
165}
166
167/** \brief see the header. The request itself, which SR2 bit 7 cannot distinguish. */
169{
170 return tms9918->flash != 0;
171}
172
173void pico9918_gpu_set_config_save_callback(PICO9918_INST_ARG pico9918_gpu_config_save_fn cb, void* userdata)
174{
175 GPU_CONFIG_SAVE_CB.fn = cb;
176 GPU_CONFIG_SAVE_CB.userdata = userdata;
177}
178
179/* SR2, which the flash operation shares with the GPU:
180 * bit 7 busy
181 * bits 6-5 retry count
182 * bits 4-2 result (pico9918_flash_result_t)
183 * bits 1-0 progress */
185{
186 TMS_STATUS(tms9918, PICO9918_SR_GPU) =
187 (uint8_t)((TMS_STATUS(tms9918, PICO9918_SR_GPU) & ~0x9c) | ((result & 7) << 2));
188 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
189}
190
191static inline void gpuFlashFire(PICO9918_INST_ONLY_ARG)
192{
193 /* TRAP: taken before dispatch, not after. An erase runs for milliseconds and a
194 request arriving inside one must re-arm rather than be cleared by the completion
195 that follows it. */
196 tms9918->flash = 0;
197
198 if (GPU_FLASH_CB.fn)
199 GPU_FLASH_CB.fn(tms9918, GPU_FLASH_CB.userdata);
200 else
202}
203
204static inline void gpuConfigSaveFire(PICO9918_INST_ARG uint8_t key)
205{
206 if (GPU_CONFIG_SAVE_CB.fn)
207 GPU_CONFIG_SAVE_CB.fn(tms9918, tms9918->config, key, GPU_CONFIG_SAVE_CB.userdata);
208}
209
210/* -------------------------------------------------------------------------
211 * Hard-fault handler (triggered by MPU for GPU DMA and palette writes)
212 * ---------------------------------------------------------------------- */
213#ifdef PICO_BUILD
214static int didFault = 0;
215
216void isr_hardfault(void)
217{
218 didFault = 1;
219 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0; /* Stop the GPU */
220 mpu_hw->ctrl = 0; /* Turn off memory protection - all models */
221}
222#endif /* PICO_BUILD */
223
224/* -------------------------------------------------------------------------
225 * Run a GPU DMA job
226 * ---------------------------------------------------------------------- */
227
228/* A transfer that runs off an end of the map. The engine's address register is 16 bits so
229 it comes back at the other end, which a pointer walk cannot do and no correct program
230 asks for - so this stays out of line rather than unrolling into the caller. */
231static PICO9918_NOINLINE void dmaWrapped(uint8_t* vram, uint32_t src, uint32_t dst,
232 uint32_t width, uint32_t height, int32_t pitch,
233 int32_t srcInc, int32_t dstInc)
234{
235 uint16_t s = (uint16_t)src;
236 uint16_t d = (uint16_t)dst;
237 for (uint32_t y = 0; y < height; ++y)
238 {
239 uint16_t rs = s, rd = d;
240 for (uint32_t x = 0; x < width; ++x, rs += srcInc, rd += dstInc) vram[rd] = vram[rs];
241 if (srcInc) s += (uint16_t)pitch;
242 d += (uint16_t)pitch;
243 }
244}
245
246static void triggerGpuDma(uint8_t* vram)
247{
248 const uint32_t srcVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8000));
249 const uint32_t dstVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8002));
250
251 /* zero is 256 in both: the engine loads the register into a counter that stops at one */
252 const uint32_t width = vram[0x8004] ? vram[0x8004] : 256;
253 const uint32_t height = vram[0x8005] ? vram[0x8005] : 256;
254 const uint32_t stride = vram[0x8006];
255 const uint32_t params = vram[0x8007];
256
257 const int32_t dstInc = (params & 0x02) ? -1 : 1;
258 const int32_t srcInc = (params & 0x01) ? 0 : dstInc;
259 const uint32_t wm1 = width - 1;
260
261 /* TRAP: the row pitch is not the stride register, and zero does not mean 256 here. The
262 engine forms one eight-bit signed difference from stride and width, then adds it in
263 place of the last step of every row - so a stride that difference overflows walks the
264 transfer backwards, which caps a usable stride at (width - 1) + 127. */
265 const uint32_t diffByte = ((dstInc < 0) ? wm1 - stride : stride - wm1) & 0xff;
266 const int32_t diff = (diffByte & 0x80) ? (int32_t)diffByte - 256 : (int32_t)diffByte;
267 const int32_t pitch = (int32_t)wm1 * dstInc + diff;
268
269 /* how far a transfer reaches either side of its start, each axis counting whichever way it runs */
270 const int32_t row = (int32_t)wm1 * dstInc;
271 const int32_t col = (int32_t)(height - 1) * pitch;
272 const int32_t back = (row < 0 ? row : 0) + (col < 0 ? col : 0);
273 const int32_t fwd = (row > 0 ? row : 0) + (col > 0 ? col : 0);
274
275 const int32_t dstLo = (int32_t)dstVramAddr + back, dstHi = (int32_t)dstVramAddr + fwd;
276 const int32_t srcLo = (int32_t)srcVramAddr + back, srcHi = (int32_t)srcVramAddr + fwd;
277
278 if (dstLo < 0 || dstHi > 0xFFFF || (srcInc && (srcLo < 0 || srcHi > 0xFFFF)))
279 {
280 dmaWrapped(vram, srcVramAddr, dstVramAddr, width, height, pitch, srcInc, dstInc);
281 }
282 else if (srcInc == 0)
283 {
284 /* a row holds the same bytes from either end, so a fill runs forwards either way */
285 uint8_t* d = vram + dstVramAddr - (dstInc < 0 ? wm1 : 0);
286 const uint8_t value = vram[srcVramAddr];
287 for (uint32_t y = 0; y < height; ++y, d += pitch) memset(d, value, width);
288 }
289 else if (dstLo > srcHi || srcLo > dstHi)
290 {
291 /* nothing read is ever written, so the direction the engine took does not show */
292 uint8_t* s = vram + srcVramAddr - (dstInc < 0 ? wm1 : 0);
293 uint8_t* d = vram + dstVramAddr - (dstInc < 0 ? wm1 : 0);
294 for (uint32_t y = 0; y < height; ++y, s += pitch, d += pitch) memcpy(d, s, width);
295 }
296 else
297 {
298 uint8_t* s = vram + srcVramAddr;
299 uint8_t* d = vram + dstVramAddr;
300 for (uint32_t y = 0; y < height; ++y, s += pitch, d += pitch)
301 {
302 uint8_t* rs = s;
303 uint8_t* rd = d;
304 for (uint32_t x = 0; x < width; ++x, rs += srcInc, rd += dstInc) *rd = *rs;
305 }
306 }
307
308 *(uint16_t*)(vram + 0x8008) = 0;
309}
310
311#if defined(TMS9900_WATCH_WRITES)
312/*
313 * The MPU's job, done in software. Region 0 guards 32 bytes at 0x8000 and its
314 * handler reads the trigger, so this is the same test at the same moment - which
315 * is what lets a program start a transfer and carry straight on.
316 */
317static void gpuDmaWatch(uint8_t* vram, uint32_t addr)
318{
319 (void)addr; /* onWriteMask/Match already select the port, so only it arrives */
320 if (vram[0x8008]) triggerGpuDma(vram);
321}
322#endif
323
324/* -------------------------------------------------------------------------
325 * MPU guards (Pico only). Region 0 covers the GPU DMA port, region 1 the
326 * palette - a GPU palette write has no other way of announcing itself.
327 * ---------------------------------------------------------------------- */
328#ifdef PICO_BUILD
330
331/* Fault on writes to a range; reads still pass. The range must not cross a 256-byte
332 boundary, which the instance's own 256-byte alignment is what guarantees. */
333static void PICO9918_IN_FLASH_FUNC(guard)(uint32_t region, void* a, uint32_t bytes)
334{
335 uintptr_t addr = (uintptr_t)a;
336#if PICO_RP2040
337 uint32_t base = addr & (uint)~0xff;
338 uint32_t first = (addr - base) >> 5;
339 uint32_t last = (addr + bytes - 1 - base) >> 5;
340 uint32_t srd = ~(((1u << (last - first + 1)) - 1u) << first) & 0xffu;
341
342 mpu_hw->rbar = base | M0PLUS_MPU_RBAR_VALID_BITS | region;
343 mpu_hw->rasr = 1 | (0x07 << 1) | (srd << 8) | 0x15000000; /* 256 bytes, privileged RO, XN */
344#else
345 mpu_hw->rnr = region;
346 mpu_hw->rbar = (addr & (uint)~31u) | (2u << M33_MPU_RBAR_AP_LSB) | M33_MPU_RBAR_XN_BITS;
347 mpu_hw->rlar = ((addr + bytes - 1) & (uint)~31u) | M33_MPU_RLAR_EN_BITS;
348#endif
349}
350
351static void __not_in_flash_func(guardEnable)(uint32_t region, bool on)
352{
353 mpu_hw->rnr = region;
354#if PICO_RP2040
355 if (on)
356 mpu_hw->rasr |= M0PLUS_MPU_RASR_ENABLE_BITS;
357 else
358 mpu_hw->rasr &= ~M0PLUS_MPU_RASR_ENABLE_BITS;
359#else
360 if (on)
361 mpu_hw->rlar |= M33_MPU_RLAR_EN_BITS;
362 else
363 mpu_hw->rlar &= ~M33_MPU_RLAR_EN_BITS;
364#endif
365}
366
367/* the flag goes up before the region drops; the other order lets an interrupt
368 re-arm and clear it, leaving the guard down with nothing to notice */
369static void __not_in_flash_func(gpuPaletteFault)(PICO9918_INST_ONLY_ARG)
370{
371 tms9918->palDirty = 1;
372
373 uint32_t save = save_and_disable_interrupts();
375 guardEnable(1, false);
376 restore_interrupts(save);
377}
378
380{
381 if (tms9918->palDirty) return;
382
384 guardEnable(1, true);
385 tms9918->palDirty = 1;
386}
387#endif /* PICO_BUILD */
388
389/* -------------------------------------------------------------------------
390 * Core GPU execution (non-inlined for stack safety)
391 * ---------------------------------------------------------------------- */
392static PICO9918_NOINLINE bool volatileHack(PICO9918_INST_ARG uint32_t budget)
393{
394 bool running = false;
395 bool outOfBudget = false;
396#if PICO9918_GPU_BUDGETED
397 /* TRAP: keying this off the arming register write instead leaks a workspace into the
398 next program, because everything else that sets restart also means "start". */
399 tms9918->gpuWp = pico9918_gpu_wp(PICO9918_INST_ONLY);
400#endif
401 tms9918->restart = 0;
402 if ((tms9918->gpuAddress & 1) == 0) /* Odd addresses crash the RP2040 */
403 {
404 uint16_t lastAddress = tms9918->gpuAddress;
405
406#ifdef PICO_BUILD
407 restart:
408#endif
409 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 1;
410 TMS_STATUS(tms9918, PICO9918_SR_GPU) |= 0x80; /* Running */
411
412#ifdef PICO_BUILD
413#if PICO_RP2040
414 mpu_hw->ctrl = M0PLUS_MPU_CTRL_PRIVDEFENA_BITS | M0PLUS_MPU_CTRL_ENABLE_BITS;
415#else
416 mpu_hw->ctrl = M33_MPU_CTRL_PRIVDEFENA_BITS | M33_MPU_CTRL_ENABLE_BITS;
417#endif
418#endif /* PICO_BUILD */
419
420#if PICO9918_GPU_BUDGETED
421 lastAddress = run9900Budget(PICO9918_INST tms9918->vram.bytes, lastAddress, &tms9918->gpuWp,
422 &TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL), budget, &tms9918->gpuStatus,
423 &outOfBudget, !PICO9918_GPU_FLAT_MEM(tms9918));
424#else
425 (void)budget;
426 lastAddress =
427 run9900(tms9918->vram.bytes, lastAddress, 0xFFFE, &TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL));
428#endif
429
430#ifdef PICO_BUILD
431 mpu_hw->ctrl = 0; /* Turn off memory protection - all models */
432#endif
433
434 if (TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) & 1)
435 {
436 tms9918->gpuAddress = lastAddress;
437 tms9918->restart = 0;
438 running = outOfBudget;
439 }
440#ifdef PICO_BUILD
441 if (didFault)
442 {
443 didFault = 0;
444 if (tms9918->vram.bytes[0x8008])
445 triggerGpuDma(tms9918->vram.bytes);
447 gpuPaletteFault(PICO9918_INST_ONLY);
448 goto restart;
449 }
450#endif
451 }
452 if (running) return true;
453
454 TMS_STATUS(tms9918, PICO9918_SR_GPU) &= ~0x80; /* Stopped */
455 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
456 return false;
457}
458
459/* -------------------------------------------------------------------------
460 * Public API
461 * ---------------------------------------------------------------------- */
462
463/*
464 * Initialize the TMS9900 GPU
465 */
467{
468#ifdef PICO_BUILD
469#if !PICO_RP2040
470 mpu_hw->mair[0] = 0x44; /* normal non-cacheable, so a guarded read stays an ordinary load */
471#endif
472 guard(0, &(tms9918->vram.bytes[0x8000]), 32);
473 guard(1, tms9918->vram.map.pram, 64 * sizeof(*tms9918->vram.map.pram));
474#endif
475#if PICO9918_GPU_BUDGETED
476 tms9918->gpuWp = PICO9918_GPU_WORKSPACE;
477#endif
478}
479
480/*
481 * Cross-core, unguarded by design. Written by pico9918_gpu_loop (core 0 on Pico)
482 * and read + reset from the frame/overlay side (core 1) via pico9918_gpu_time /
483 * pico9918_gpu_reset_time. volatile so the compiler cannot cache or reorder the
484 * accesses across the core boundary; the remaining race is a lost update of one
485 * sample window, which is tolerable for a statistics readout.
486 */
487static volatile bool reportedBack = true;
488static volatile uint32_t gpuTimeUs = 0;
489
490/*
491 * Return GPU CPU time in microseconds.
492 */
493uint32_t pico9918_gpu_time(uint32_t totalTime)
494{
495 if (!reportedBack) return totalTime;
496 return gpuTimeUs;
497}
498
499/*
500 * Reset internal GPU time accumulator.
501 */
503{
504 gpuTimeUs = 0;
505}
506
507/*
508 * One service pass. Split out of pico9918_gpu_loop so a host without a core to
509 * spare can run a GPU program and get control back.
510 */
512{
513 if (tms9918->restart)
514 {
515 reportedBack = false;
516 uint32_t gpuStart = PICO9918_HOST_TIME_US();
517 volatileHack(PICO9918_INST 0);
518 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
519 }
520 reportedBack = true;
521
522 if (tms9918->flash)
523 {
524 gpuFlashFire(PICO9918_INST_ONLY);
525 }
526
527 for (int i = 0; i < (int)(sizeof(configActionKeys) / sizeof(configActionKeys[0])); ++i)
528 {
529 const uint8_t key = configActionKeys[i];
530 if (tms9918->config[key])
531 {
532 tms9918->config[key] = 0;
533 gpuConfigSaveFire(PICO9918_INST key);
534 }
535 }
536}
537
538/** \brief see the header. The address a slice resumes from; odd means not running. */
541{
542 return tms9918->gpuAddress;
543}
544
545/** \brief see the header. Where the registers are now, which LWPI can have moved. */
548{
549#if PICO9918_GPU_BUDGETED
550 const uint8_t armed = tms9918->restart;
551
552 return (armed && armed != PICO9918_GPU_RESUMING) ? PICO9918_GPU_WORKSPACE : tms9918->gpuWp;
553#else
554 return PICO9918_GPU_WORKSPACE;
555#endif
556}
557
558/** \brief see the header. The whole map the GPU addresses, workspace overflow included. */
561{
562 return (uint32_t)sizeof(((pico9918_t*)0)->vram);
563}
564
565/** \brief see the header. A byte of that map, or 0 past the end of it. */
568{
569 if (addr >= pico9918_gpu_mem_size()) return 0;
570
571 /* not vram.bytes: that array stops at 0xFFFF and the workspace overflow is past it */
572 return ((const uint8_t*)&tms9918->vram)[addr];
573}
574
575/** \brief see the header. R0-R15 as words at the workspace the program is using. */
578{
579 const uint32_t at = pico9918_gpu_wp(PICO9918_INST_ONLY) + ((uint32_t)(reg & 0x0f) << 1);
580
581 return (uint16_t)((pico9918_gpu_mem_value(PICO9918_INST at) << 8) |
583}
584
585/** \brief see the header. The status between instructions, where one paces them. */
588{
589 /* stored in the cores' low-byte layout, published where STST puts it */
590 return (uint16_t)(tms9918->gpuStatus << 8);
591}
592
593/*
594 * The same pass, but capped, for a host that has only the one thread.
595 *
596 * The cap is what lets a caller interleave: a program that waits on the scanline at
597 * >7000 cannot finish until something advances it, and nothing can while the core is
598 * inside run9900. Returning with the PC kept is what makes the next call carry on.
599 */
600bool pico9918_gpu_step_n(PICO9918_INST_ARG uint32_t instructions)
601{
602 bool running = false;
603
604 if (tms9918->restart)
605 {
606 reportedBack = false;
607 uint32_t gpuStart = PICO9918_HOST_TIME_US();
608 running = volatileHack(PICO9918_INST instructions);
609 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
610
611 /* volatileHack clears it on the way in, so put it back for the next slice */
612 if (running) tms9918->restart = PICO9918_GPU_RESUMING;
613 }
614 reportedBack = !running;
615
616 if (tms9918->flash)
617 {
618 gpuFlashFire(PICO9918_INST_ONLY);
619 }
620
621 for (int i = 0; i < (int)(sizeof(configActionKeys) / sizeof(configActionKeys[0])); ++i)
622 {
623 const uint8_t key = configActionKeys[i];
624 if (tms9918->config[key])
625 {
626 tms9918->config[key] = 0;
627 gpuConfigSaveFire(PICO9918_INST key);
628 }
629 }
630
631 return running;
632}
633
634#if PICO9918_BUILD_DEBUG_API
635
636/** \brief see pico9918_debug.h. The same slice, watched between instructions. */
638bool pico9918_debug_gpu_step_n(PICO9918_INST_ARG uint32_t instructions, pico9918_gpu_step_fn cb,
639 void* userdata)
640{
641#if defined(TMS9900_STEP_HOOK)
642 gpuStep.fn = cb;
643 gpuStep.userdata = userdata;
644
645 const bool running = pico9918_gpu_step_n(PICO9918_INST instructions);
646
647 gpuStep.fn = NULL;
648
649 return running;
650#else
651 (void)cb;
652 (void)userdata;
653
654 return pico9918_gpu_step_n(PICO9918_INST instructions);
655#endif
656}
657
658#endif
659
660/*
661 * GPU main loop - runs indefinitely, call from a dedicated core/thread.
662 */
664{
665 while (1)
666 {
668 }
669}
670
671/*
672 * Instructions a scanline, from a rate. Never zero while a rate is set: a slice of
673 * nothing would arm the GPU and never advance it, which is worse than running it too
674 * fast. Sixty fields of 240 lines until the first pico9918_frame_end says otherwise -
675 * the shape every mode is within a factor of two of.
676 */
677#define GPU_SLICE_FROM_IPS(ips, lines, hz) ((uint32_t)((ips) / ((lines) * (hz))) + 1u)
678
679void pico9918_gpu_set_clock(PICO9918_INST_ARG uint32_t instructionsPerSecond)
680{
681#if PICO9918_GPU_BUDGETED
682 tms9918->gpuIps = instructionsPerSecond;
683 tms9918->gpuSlice = instructionsPerSecond ? GPU_SLICE_FROM_IPS(instructionsPerSecond, 240u, 60u) : 0u;
684#else
685 /* refused, not honoured: a hand-written Thumb core runs to completion */
686 (void)tms9918;
687 (void)instructionsPerSecond;
688#endif
689}
690
691#if PICO9918_GPU_BUDGETED
692
693/* Re-derived per frame, because a mode change moves both the line count and, on a
694 50Hz machine, the rate. Called from pico9918_frame_end. */
695void pico9918_gpu_note_frame(PICO9918_INST_ARG uint32_t lines, float frameRateHz)
696{
697 if (!tms9918->gpuIps || !lines || frameRateHz < 1.0f) return;
698
699 tms9918->gpuSlice = GPU_SLICE_FROM_IPS(tms9918->gpuIps, lines, (uint32_t)frameRateHz);
700}
701
702/*
703 * One slice, for the library's own two service points: the register write that arms a
704 * program, and each scanline while one is still running.
705 *
706 * Gated on the unlock rather than the personality: a program can only be armed on an
707 * unlocked device, and stepping one down clears that flag - so this is also what stops
708 * a program armed as an F18A from running on as a TMS9918A, which pico9918_set_chip
709 * deliberately leaves to whoever runs the GPU.
710 */
711void pico9918_gpu_run_slice(PICO9918_INST_ONLY_ARG)
712{
713 if (PICO9918_UNLOCKED(tms9918)) pico9918_gpu_step_n(PICO9918_INST tms9918->gpuSlice);
714}
715
716#endif
uint16_t pico9918_gpu_status(pico9918_t *tms9918)
see the header.
Definition gpu.c:587
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
see the header.
Definition gpu.c:540
bool pico9918_debug_gpu_step_n(pico9918_t *tms9918, uint32_t instructions, pico9918_gpu_step_fn cb, void *userdata)
see pico9918_debug.h.
Definition gpu.c:638
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
Definition gpu.c:502
void pico9918_gpu_set_flash_callback(pico9918_t *tms9918, pico9918_gpu_flash_fn cb, void *userdata)
Register a callback that will be invoked when the GPU wants to flash a sector.
Definition gpu.c:161
volatile uint8_t pico9918_gpu_palette_guard_off
The palette guard, as much of it as the host has to see.
Definition gpu.c:329
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
Definition gpu.c:466
uint32_t pico9918_gpu_mem_size(void)
see the header.
Definition gpu.c:560
void pico9918_gpu_step(pico9918_t *tms9918)
One pass of that loop: run a pending trigger to completion, then dispatch any flash and config-action...
Definition gpu.c:511
uint16_t pico9918_gpu_wp(pico9918_t *tms9918)
see the header.
Definition gpu.c:547
void pico9918_gpu_loop(pico9918_t *tms9918)
GPU main loop - call from a dedicated core/thread.
Definition gpu.c:663
void pico9918_gpu_rearm_palette_guard(pico9918_t *tms9918)
put the palette guard back, from the core that owns the MPU
Definition gpu.c:379
uint8_t pico9918_gpu_mem_value(pico9918_t *tms9918, uint32_t addr)
see the header.
Definition gpu.c:567
bool pico9918_gpu_flash_pending(pico9918_t *tms9918)
see the header.
Definition gpu.c:168
uint16_t pico9918_gpu_reg_value(pico9918_t *tms9918, uint8_t reg)
see the header.
Definition gpu.c:577
void pico9918_gpu_flash_complete(pico9918_t *tms9918, pico9918_flash_result_t result)
End the flash operation R63 requested, with the result the guest reads back.
Definition gpu.c:184
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
Definition gpu.c:493
bool pico9918_gpu_step_n(pico9918_t *tms9918, uint32_t instructions)
The same pass, capped at instructions, returning true while the program still has work left.
Definition gpu.c:600
void pico9918_gpu_set_clock(pico9918_t *tms9918, uint32_t instructionsPerSecond)
Hand GPU execution to the library, at this many instructions a second.
Definition gpu.c:679
void pico9918_gpu_set_config_save_callback(pico9918_t *tms9918, pico9918_gpu_config_save_fn cb, void *userdata)
Register a callback that will be invoked when the GPU loop detects a config action request.
Definition gpu.c:173
pico9918-core - GPU Interface
pico9918_flash_result_t
what a flash operation finished as, reported in status register 2
Definition gpu.h:256
@ PICO9918_FLASH_ERR_UNSUPPORTED
no host is listening - see pico9918_gpu_set_flash_callback
Definition gpu.h:262
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:83
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:86
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
Definition pico9918.h:255
@ PICO9918_SR_GPU
GPU running and its status byte.
Definition pico9918.h:272
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:84
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:85
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:50
pico9918-core - config byte layout
pico9918-core - debugger access
pico9918-core - the private instance layout
pico9918-core - Platform Abstraction
pico9918-core - TMS9900 CPU interpreter (portable C)