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_GPU_BUDGETED
40/* run9900() implemented in platform/thumb9900_{m0,m33}.S */
41extern uint16_t run9900(uint8_t* memory, uint16_t pc, uint16_t wp, uint8_t* regx38);
42#else
43#if defined(TMS9900_WATCH_WRITES)
44static void gpuDmaWatch(uint8_t* vram, uint32_t addr);
45#endif
46
47static uint16_t run9900Budget(uint8_t* mem, uint16_t pc, uint16_t wp, uint8_t* r38,
48 uint32_t budget, uint16_t* st, bool* outOfBudget, bool f18aMemory)
49{
50 Tms9900Cpu cpu;
51 tms9900_init(&cpu, mem, r38, pc, wp);
52 cpu.f18aMemory = f18aMemory;
53#if defined(TMS9900_WATCH_WRITES)
54 cpu.onWrite = gpuDmaWatch;
55#endif
56 cpu.st = *st;
57 const uint16_t next = run9900_budget_c(&cpu, budget, outOfBudget);
58 *st = cpu.st;
59 return next;
60}
61#endif
62
63/* -------------------------------------------------------------------------
64 * Config keys used to request config actions (save / forced save / pending
65 * confirm / pending cancel). Semantics are owned by the host - the GPU loop
66 * just detects a set key, clears it and notifies the host via the config
67 * callback.
68 * ---------------------------------------------------------------------- */
69static const uint8_t configActionKeys[] = {PICO9918_CONF_SAVE_TO_FLASH, PICO9918_CONF_SAVE_FORCED, PICO9918_CONF_PENDING_CONFIRM,
70 PICO9918_CONF_PENDING_CANCEL};
71
72/* -------------------------------------------------------------------------
73 * Callbacks (registered by the host application)
74 * ---------------------------------------------------------------------- */
75/* pico9918.h carries why only the storage differs between the two builds */
76#if PICO9918_SINGLE_INSTANCE
77static struct
78{
79 pico9918_gpu_flash_fn fn;
80 void* userdata;
81} gpuFlash;
82
83static struct
84{
85 pico9918_gpu_config_save_fn fn;
86 void* userdata;
87} gpuConfigSave;
88#define GPU_FLASH_CB gpuFlash
89#define GPU_CONFIG_SAVE_CB gpuConfigSave
90#else
91#define GPU_FLASH_CB tms9918->gpuFlash
92#define GPU_CONFIG_SAVE_CB tms9918->gpuConfigSave
93#endif
94
95void pico9918_gpu_set_flash_callback(PICO9918_INST_ARG pico9918_gpu_flash_fn cb, void* userdata)
96{
97 GPU_FLASH_CB.fn = cb;
98 GPU_FLASH_CB.userdata = userdata;
99}
100
101void pico9918_gpu_set_config_save_callback(PICO9918_INST_ARG pico9918_gpu_config_save_fn cb, void* userdata)
102{
103 GPU_CONFIG_SAVE_CB.fn = cb;
104 GPU_CONFIG_SAVE_CB.userdata = userdata;
105}
106
107/* SR2, which the flash operation shares with the GPU:
108 * bit 7 busy
109 * bits 6-5 retry count
110 * bits 4-2 result (pico9918_flash_result_t)
111 * bits 1-0 progress */
113{
114 TMS_STATUS(tms9918, PICO9918_SR_GPU) =
115 (uint8_t)((TMS_STATUS(tms9918, PICO9918_SR_GPU) & ~0x9c) | ((result & 7) << 2));
116 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
117}
118
119static inline void gpuFlashFire(PICO9918_INST_ONLY_ARG)
120{
121 /* TRAP: taken before dispatch, not after. An erase runs for milliseconds and a
122 request arriving inside one must re-arm rather than be cleared by the completion
123 that follows it. */
124 tms9918->flash = 0;
125
126 if (GPU_FLASH_CB.fn)
127 GPU_FLASH_CB.fn(tms9918, GPU_FLASH_CB.userdata);
128 else
130}
131
132static inline void gpuConfigSaveFire(PICO9918_INST_ARG uint8_t key)
133{
134 if (GPU_CONFIG_SAVE_CB.fn)
135 GPU_CONFIG_SAVE_CB.fn(tms9918, tms9918->config, key, GPU_CONFIG_SAVE_CB.userdata);
136}
137
138/* -------------------------------------------------------------------------
139 * Hard-fault handler (triggered by MPU for GPU DMA and palette writes)
140 * ---------------------------------------------------------------------- */
141#ifdef PICO_BUILD
142static int didFault = 0;
143
144void isr_hardfault(void)
145{
146 didFault = 1;
147 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0; /* Stop the GPU */
148 mpu_hw->ctrl = 0; /* Turn off memory protection - all models */
149}
150#endif /* PICO_BUILD */
151
152/* -------------------------------------------------------------------------
153 * Run a GPU DMA job
154 * ---------------------------------------------------------------------- */
155
156/* A transfer that runs off an end of the map. The engine's address register is 16 bits so
157 it comes back at the other end, which a pointer walk cannot do and no correct program
158 asks for - so this stays out of line rather than unrolling into the caller. */
159static PICO9918_NOINLINE void dmaWrapped(uint8_t* vram, uint32_t src, uint32_t dst,
160 uint32_t width, uint32_t height, int32_t pitch,
161 int32_t srcInc, int32_t dstInc)
162{
163 uint16_t s = (uint16_t)src;
164 uint16_t d = (uint16_t)dst;
165 for (uint32_t y = 0; y < height; ++y)
166 {
167 uint16_t rs = s, rd = d;
168 for (uint32_t x = 0; x < width; ++x, rs += srcInc, rd += dstInc) vram[rd] = vram[rs];
169 if (srcInc) s += (uint16_t)pitch;
170 d += (uint16_t)pitch;
171 }
172}
173
174static void triggerGpuDma(uint8_t* vram)
175{
176 const uint32_t srcVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8000));
177 const uint32_t dstVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8002));
178
179 /* zero is 256 in both: the engine loads the register into a counter that stops at one */
180 const uint32_t width = vram[0x8004] ? vram[0x8004] : 256;
181 const uint32_t height = vram[0x8005] ? vram[0x8005] : 256;
182 const uint32_t stride = vram[0x8006];
183 const uint32_t params = vram[0x8007];
184
185 const int32_t dstInc = (params & 0x02) ? -1 : 1;
186 const int32_t srcInc = (params & 0x01) ? 0 : dstInc;
187 const uint32_t wm1 = width - 1;
188
189 /* TRAP: the row pitch is not the stride register, and zero does not mean 256 here. The
190 engine forms one eight-bit signed difference from stride and width, then adds it in
191 place of the last step of every row - so a stride that difference overflows walks the
192 transfer backwards, which caps a usable stride at (width - 1) + 127. */
193 const uint32_t diffByte = ((dstInc < 0) ? wm1 - stride : stride - wm1) & 0xff;
194 const int32_t diff = (diffByte & 0x80) ? (int32_t)diffByte - 256 : (int32_t)diffByte;
195 const int32_t pitch = (int32_t)wm1 * dstInc + diff;
196
197 /* how far a transfer reaches either side of its start, each axis counting whichever way it runs */
198 const int32_t row = (int32_t)wm1 * dstInc;
199 const int32_t col = (int32_t)(height - 1) * pitch;
200 const int32_t back = (row < 0 ? row : 0) + (col < 0 ? col : 0);
201 const int32_t fwd = (row > 0 ? row : 0) + (col > 0 ? col : 0);
202
203 const int32_t dstLo = (int32_t)dstVramAddr + back, dstHi = (int32_t)dstVramAddr + fwd;
204 const int32_t srcLo = (int32_t)srcVramAddr + back, srcHi = (int32_t)srcVramAddr + fwd;
205
206 if (dstLo < 0 || dstHi > 0xFFFF || (srcInc && (srcLo < 0 || srcHi > 0xFFFF)))
207 {
208 dmaWrapped(vram, srcVramAddr, dstVramAddr, width, height, pitch, srcInc, dstInc);
209 }
210 else if (srcInc == 0)
211 {
212 /* a row holds the same bytes from either end, so a fill runs forwards either way */
213 uint8_t* d = vram + dstVramAddr - (dstInc < 0 ? wm1 : 0);
214 const uint8_t value = vram[srcVramAddr];
215 for (uint32_t y = 0; y < height; ++y, d += pitch) memset(d, value, width);
216 }
217 else if (dstLo > srcHi || srcLo > dstHi)
218 {
219 /* nothing read is ever written, so the direction the engine took does not show */
220 uint8_t* s = vram + srcVramAddr - (dstInc < 0 ? wm1 : 0);
221 uint8_t* d = vram + dstVramAddr - (dstInc < 0 ? wm1 : 0);
222 for (uint32_t y = 0; y < height; ++y, s += pitch, d += pitch) memcpy(d, s, width);
223 }
224 else
225 {
226 uint8_t* s = vram + srcVramAddr;
227 uint8_t* d = vram + dstVramAddr;
228 for (uint32_t y = 0; y < height; ++y, s += pitch, d += pitch)
229 {
230 uint8_t* rs = s;
231 uint8_t* rd = d;
232 for (uint32_t x = 0; x < width; ++x, rs += srcInc, rd += dstInc) *rd = *rs;
233 }
234 }
235
236 *(uint16_t*)(vram + 0x8008) = 0;
237}
238
239#if defined(TMS9900_WATCH_WRITES)
240/*
241 * The MPU's job, done in software. Region 0 guards 32 bytes at 0x8000 and its
242 * handler reads the trigger, so this is the same test at the same moment - which
243 * is what lets a program start a transfer and carry straight on.
244 */
245static void gpuDmaWatch(uint8_t* vram, uint32_t addr)
246{
247 if ((addr & ~(uint32_t)0x1F) == 0x8000 && vram[0x8008]) triggerGpuDma(vram);
248}
249#endif
250
251/* -------------------------------------------------------------------------
252 * MPU guards (Pico only). Region 0 covers the GPU DMA port, region 1 the
253 * palette - a GPU palette write has no other way of announcing itself.
254 * ---------------------------------------------------------------------- */
255#ifdef PICO_BUILD
257
258/* Fault on writes to a range; reads still pass. The range must not cross a 256-byte
259 boundary, which the instance's own 256-byte alignment is what guarantees. */
260static void PICO9918_IN_FLASH_FUNC(guard)(uint32_t region, void* a, uint32_t bytes)
261{
262 uintptr_t addr = (uintptr_t)a;
263#if PICO_RP2040
264 uint32_t base = addr & (uint)~0xff;
265 uint32_t first = (addr - base) >> 5;
266 uint32_t last = (addr + bytes - 1 - base) >> 5;
267 uint32_t srd = ~(((1u << (last - first + 1)) - 1u) << first) & 0xffu;
268
269 mpu_hw->rbar = base | M0PLUS_MPU_RBAR_VALID_BITS | region;
270 mpu_hw->rasr = 1 | (0x07 << 1) | (srd << 8) | 0x15000000; /* 256 bytes, privileged RO, XN */
271#else
272 mpu_hw->rnr = region;
273 mpu_hw->rbar = (addr & (uint)~31u) | (2u << M33_MPU_RBAR_AP_LSB) | M33_MPU_RBAR_XN_BITS;
274 mpu_hw->rlar = ((addr + bytes - 1) & (uint)~31u) | M33_MPU_RLAR_EN_BITS;
275#endif
276}
277
278static void __not_in_flash_func(guardEnable)(uint32_t region, bool on)
279{
280 mpu_hw->rnr = region;
281#if PICO_RP2040
282 if (on)
283 mpu_hw->rasr |= M0PLUS_MPU_RASR_ENABLE_BITS;
284 else
285 mpu_hw->rasr &= ~M0PLUS_MPU_RASR_ENABLE_BITS;
286#else
287 if (on)
288 mpu_hw->rlar |= M33_MPU_RLAR_EN_BITS;
289 else
290 mpu_hw->rlar &= ~M33_MPU_RLAR_EN_BITS;
291#endif
292}
293
294/* the flag goes up before the region drops; the other order lets an interrupt
295 re-arm and clear it, leaving the guard down with nothing to notice */
296static void __not_in_flash_func(gpuPaletteFault)(PICO9918_INST_ONLY_ARG)
297{
298 tms9918->palDirty = 1;
299
300 uint32_t save = save_and_disable_interrupts();
302 guardEnable(1, false);
303 restore_interrupts(save);
304}
305
307{
308 if (tms9918->palDirty) return;
309
311 guardEnable(1, true);
312 tms9918->palDirty = 1;
313}
314#endif /* PICO_BUILD */
315
316/* -------------------------------------------------------------------------
317 * Core GPU execution (non-inlined for stack safety)
318 * ---------------------------------------------------------------------- */
319static PICO9918_NOINLINE bool volatileHack(PICO9918_INST_ARG uint32_t budget)
320{
321 bool running = false;
322 bool outOfBudget = false;
323 tms9918->restart = 0;
324 if ((tms9918->gpuAddress & 1) == 0) /* Odd addresses crash the RP2040 */
325 {
326 uint16_t lastAddress = tms9918->gpuAddress;
327
328#ifdef PICO_BUILD
329 restart:
330#endif
331 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 1;
332 TMS_STATUS(tms9918, PICO9918_SR_GPU) |= 0x80; /* Running */
333
334#ifdef PICO_BUILD
335#if PICO_RP2040
336 mpu_hw->ctrl = M0PLUS_MPU_CTRL_PRIVDEFENA_BITS | M0PLUS_MPU_CTRL_ENABLE_BITS;
337#else
338 mpu_hw->ctrl = M33_MPU_CTRL_PRIVDEFENA_BITS | M33_MPU_CTRL_ENABLE_BITS;
339#endif
340#endif /* PICO_BUILD */
341
342#if PICO9918_GPU_BUDGETED
343 lastAddress = run9900Budget(tms9918->vram.bytes, lastAddress, 0xFFFE,
344 &TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL), budget, &tms9918->gpuStatus,
345 &outOfBudget, !PICO9918_GPU_FLAT_MEM(tms9918));
346#else
347 (void)budget;
348 lastAddress =
349 run9900(tms9918->vram.bytes, lastAddress, 0xFFFE, &TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL));
350#endif
351
352#ifdef PICO_BUILD
353 mpu_hw->ctrl = 0; /* Turn off memory protection - all models */
354#endif
355
356 if (TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) & 1)
357 {
358 tms9918->gpuAddress = lastAddress;
359 tms9918->restart = 0;
360 running = outOfBudget;
361 }
362#ifdef PICO_BUILD
363 if (didFault)
364 {
365 didFault = 0;
366 if (tms9918->vram.bytes[0x8008])
367 triggerGpuDma(tms9918->vram.bytes);
369 gpuPaletteFault(PICO9918_INST_ONLY);
370 goto restart;
371 }
372#endif
373 }
374 if (running) return true;
375
376 TMS_STATUS(tms9918, PICO9918_SR_GPU) &= ~0x80; /* Stopped */
377 TMS_REGISTER(tms9918, PICO9918_REG_GPU_CONTROL) = 0;
378 return false;
379}
380
381/* -------------------------------------------------------------------------
382 * Public API
383 * ---------------------------------------------------------------------- */
384
385/*
386 * Initialize the TMS9900 GPU
387 */
389{
390#ifdef PICO_BUILD
391#if !PICO_RP2040
392 mpu_hw->mair[0] = 0x44; /* normal non-cacheable, so a guarded read stays an ordinary load */
393#endif
394 guard(0, &(tms9918->vram.bytes[0x8000]), 32);
395 guard(1, tms9918->vram.map.pram, 64 * sizeof(*tms9918->vram.map.pram));
396#endif
397}
398
399/*
400 * Cross-core, unguarded by design. Written by pico9918_gpu_loop (core 0 on Pico)
401 * and read + reset from the frame/overlay side (core 1) via pico9918_gpu_time /
402 * pico9918_gpu_reset_time. volatile so the compiler cannot cache or reorder the
403 * accesses across the core boundary; the remaining race is a lost update of one
404 * sample window, which is tolerable for a statistics readout.
405 */
406static volatile bool reportedBack = true;
407static volatile uint32_t gpuTimeUs = 0;
408
409/*
410 * Return GPU CPU time in microseconds.
411 */
412uint32_t pico9918_gpu_time(uint32_t totalTime)
413{
414 if (!reportedBack) return totalTime;
415 return gpuTimeUs;
416}
417
418/*
419 * Reset internal GPU time accumulator.
420 */
422{
423 gpuTimeUs = 0;
424}
425
426/*
427 * One service pass. Split out of pico9918_gpu_loop so a host without a core to
428 * spare can run a GPU program and get control back.
429 */
431{
432 if (tms9918->restart)
433 {
434 reportedBack = false;
435 uint32_t gpuStart = PICO9918_HOST_TIME_US();
436 volatileHack(PICO9918_INST 0);
437 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
438 }
439 reportedBack = true;
440
441 if (tms9918->flash)
442 {
443 gpuFlashFire(PICO9918_INST_ONLY);
444 }
445
446 for (int i = 0; i < (int)(sizeof(configActionKeys) / sizeof(configActionKeys[0])); ++i)
447 {
448 const uint8_t key = configActionKeys[i];
449 if (tms9918->config[key])
450 {
451 tms9918->config[key] = 0;
452 gpuConfigSaveFire(PICO9918_INST key);
453 }
454 }
455}
456
457/** \brief see the header. The address a slice resumes from; odd means not running. */
460{
461 return tms9918->gpuAddress;
462}
463
464/* The GPU's workspace pointer, which gpuRun passes run9900 and nothing changes. */
465#define GPU_WORKSPACE 0xFFFEu
466
467/** \brief see the header. The whole map the GPU addresses, workspace overflow included. */
470{
471 return (uint32_t)sizeof(((pico9918_t*)0)->vram);
472}
473
474/** \brief see the header. A byte of that map, or 0 past the end of it. */
477{
478 if (addr >= pico9918_gpu_mem_size()) return 0;
479
480 /* not vram.bytes: that array stops at 0xFFFF and the workspace overflow is past it */
481 return ((const uint8_t*)&tms9918->vram)[addr];
482}
483
484/** \brief see the header. R0-R15 as words at the fixed workspace. */
487{
488 const uint32_t at = GPU_WORKSPACE + ((uint32_t)(reg & 0x0f) << 1);
489
490 return (uint16_t)((pico9918_gpu_mem_value(PICO9918_INST at) << 8) |
492}
493
494/** \brief see the header. The status between instructions, where one paces them. */
497{
498 /* stored in the cores' low-byte layout, published where STST puts it */
499 return (uint16_t)(tms9918->gpuStatus << 8);
500}
501
502/*
503 * The same pass, but capped, for a host that has only the one thread.
504 *
505 * The cap is what lets a caller interleave: a program that waits on the scanline at
506 * >7000 cannot finish until something advances it, and nothing can while the core is
507 * inside run9900. Returning with the PC kept is what makes the next call carry on.
508 */
509bool pico9918_gpu_step_n(PICO9918_INST_ARG uint32_t instructions)
510{
511 bool running = false;
512
513 if (tms9918->restart)
514 {
515 reportedBack = false;
516 uint32_t gpuStart = PICO9918_HOST_TIME_US();
517 running = volatileHack(PICO9918_INST instructions);
518 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
519
520 /* volatileHack clears it on the way in, so put it back for the next slice */
521 if (running) tms9918->restart = 1;
522 }
523 reportedBack = !running;
524
525 if (tms9918->flash)
526 {
527 gpuFlashFire(PICO9918_INST_ONLY);
528 }
529
530 for (int i = 0; i < (int)(sizeof(configActionKeys) / sizeof(configActionKeys[0])); ++i)
531 {
532 const uint8_t key = configActionKeys[i];
533 if (tms9918->config[key])
534 {
535 tms9918->config[key] = 0;
536 gpuConfigSaveFire(PICO9918_INST key);
537 }
538 }
539
540 return running;
541}
542
543/*
544 * GPU main loop - runs indefinitely, call from a dedicated core/thread.
545 */
547{
548 while (1)
549 {
551 }
552}
553
554/*
555 * Instructions a scanline, from a rate. Never zero while a rate is set: a slice of
556 * nothing would arm the GPU and never advance it, which is worse than running it too
557 * fast. Sixty fields of 240 lines until the first pico9918_frame_end says otherwise -
558 * the shape every mode is within a factor of two of.
559 */
560#define GPU_SLICE_FROM_IPS(ips, lines, hz) ((uint32_t)((ips) / ((lines) * (hz))) + 1u)
561
562void pico9918_gpu_set_clock(PICO9918_INST_ARG uint32_t instructionsPerSecond)
563{
564#if PICO9918_GPU_BUDGETED
565 tms9918->gpuIps = instructionsPerSecond;
566 tms9918->gpuSlice = instructionsPerSecond ? GPU_SLICE_FROM_IPS(instructionsPerSecond, 240u, 60u) : 0u;
567#else
568 /* refused, not honoured: a hand-written Thumb core runs to completion */
569 (void)tms9918;
570 (void)instructionsPerSecond;
571#endif
572}
573
574#if PICO9918_GPU_BUDGETED
575
576/* Re-derived per frame, because a mode change moves both the line count and, on a
577 50Hz machine, the rate. Called from pico9918_frame_end. */
578void pico9918_gpu_note_frame(PICO9918_INST_ARG uint32_t lines, float frameRateHz)
579{
580 if (!tms9918->gpuIps || !lines || frameRateHz < 1.0f) return;
581
582 tms9918->gpuSlice = GPU_SLICE_FROM_IPS(tms9918->gpuIps, lines, (uint32_t)frameRateHz);
583}
584
585/*
586 * One slice, for the library's own two service points: the register write that arms a
587 * program, and each scanline while one is still running.
588 *
589 * Gated on the unlock rather than the personality: a program can only be armed on an
590 * unlocked device, and stepping one down clears that flag - so this is also what stops
591 * a program armed as an F18A from running on as a TMS9918A, which pico9918_set_chip
592 * deliberately leaves to whoever runs the GPU.
593 */
594void pico9918_gpu_run_slice(PICO9918_INST_ONLY_ARG)
595{
596 if (PICO9918_UNLOCKED(tms9918)) pico9918_gpu_step_n(PICO9918_INST tms9918->gpuSlice);
597}
598
599#endif
uint16_t pico9918_gpu_status(pico9918_t *tms9918)
see the header.
Definition gpu.c:496
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
see the header.
Definition gpu.c:459
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
Definition gpu.c:421
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:95
volatile uint8_t pico9918_gpu_palette_guard_off
The palette guard, as much of it as the host has to see.
Definition gpu.c:256
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
Definition gpu.c:388
uint32_t pico9918_gpu_mem_size(void)
see the header.
Definition gpu.c:469
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:430
void pico9918_gpu_loop(pico9918_t *tms9918)
GPU main loop - call from a dedicated core/thread.
Definition gpu.c:546
void pico9918_gpu_rearm_palette_guard(pico9918_t *tms9918)
put the palette guard back, from the core that owns the MPU
Definition gpu.c:306
uint8_t pico9918_gpu_mem_value(pico9918_t *tms9918, uint32_t addr)
see the header.
Definition gpu.c:476
uint16_t pico9918_gpu_reg_value(pico9918_t *tms9918, uint8_t reg)
see the header.
Definition gpu.c:486
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:112
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
Definition gpu.c:412
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:509
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:562
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:101
pico9918-core - GPU Interface
pico9918_flash_result_t
what a flash operation finished as, reported in status register 2
Definition gpu.h:232
@ PICO9918_FLASH_ERR_UNSUPPORTED
no host is listening - see pico9918_gpu_set_flash_callback
Definition gpu.h:238
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
Definition pico9918.h:71
#define PICO9918_INST_ONLY
pass the instance as the only argument
Definition pico9918.h:74
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
Definition pico9918.h:241
@ PICO9918_SR_GPU
GPU running and its status byte.
Definition pico9918.h:258
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:72
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:73
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
Definition pico9918.h:41
pico9918-core - config byte layout
pico9918-core - the private instance layout
pico9918-core - Platform Abstraction
pico9918-core - TMS9900 CPU interpreter (portable C)