29#include "pico/stdlib.h"
30#include "hardware/structs/mpu.h"
31#include "hardware/sync.h"
32#include <hardware/flash.h>
39#if PICO9918_BUILD_DEBUG_API
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"
47#if !PICO9918_GPU_BUDGETED
49extern uint16_t run9900(uint8_t* memory, uint16_t pc, uint16_t wp, uint8_t* regx38);
51#if defined(TMS9900_WATCH_WRITES)
52static void gpuDmaWatch(uint8_t* vram, uint32_t addr);
55#if PICO9918_BUILD_DEBUG_API && defined(TMS9900_STEP_HOOK)
59 pico9918_gpu_step_fn fn;
68 pico9918_gpu_step_fn fn;
74 const gpu_step_ctx_t*
const ctx = (
const gpu_step_ctx_t*)cpu->onStepData;
76 return ctx->fn(ctx->inst, (uint16_t)cpu->pc, ctx->userdata);
86 out->userdata = gpuStep.userdata;
88#if PICO9918_BUILD_STEP_CALLBACK
91 out->fn = tms9918->stepFn;
92 out->userdata = tms9918->stepUserdata;
96 return out->fn != NULL;
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,
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;
112#if PICO9918_BUILD_DEBUG_API && defined(TMS9900_STEP_HOOK)
117 cpu.onStepData = &ctx;
118 cpu.onStep = gpuStepHook;
122 const uint16_t next = run9900_budget_c(&cpu, budget, outOfBudget);
135static const uint8_t configActionKeys[] = {PICO9918_CONF_SAVE_TO_FLASH, PICO9918_CONF_SAVE_FORCED, PICO9918_CONF_PENDING_CONFIRM,
136 PICO9918_CONF_PENDING_CANCEL};
142#if PICO9918_SINGLE_INSTANCE
145 pico9918_gpu_flash_fn fn;
151 pico9918_gpu_config_save_fn fn;
154#define GPU_FLASH_CB gpuFlash
155#define GPU_CONFIG_SAVE_CB gpuConfigSave
157#define GPU_FLASH_CB tms9918->gpuFlash
158#define GPU_CONFIG_SAVE_CB tms9918->gpuConfigSave
163 GPU_FLASH_CB.fn = cb;
164 GPU_FLASH_CB.userdata = userdata;
170 return tms9918->flash != 0;
175 GPU_CONFIG_SAVE_CB.fn = cb;
176 GPU_CONFIG_SAVE_CB.userdata = userdata;
187 (uint8_t)((TMS_STATUS(tms9918,
PICO9918_SR_GPU) & ~0x9c) | ((result & 7) << 2));
199 GPU_FLASH_CB.fn(tms9918, GPU_FLASH_CB.userdata);
206 if (GPU_CONFIG_SAVE_CB.fn)
207 GPU_CONFIG_SAVE_CB.fn(tms9918, tms9918->config, key, GPU_CONFIG_SAVE_CB.userdata);
214static int didFault = 0;
216void isr_hardfault(
void)
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)
235 uint16_t s = (uint16_t)src;
236 uint16_t d = (uint16_t)dst;
237 for (uint32_t y = 0; y < height; ++y)
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;
246static void triggerGpuDma(uint8_t* vram)
248 const uint32_t srcVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8000));
249 const uint32_t dstVramAddr = __builtin_bswap16(*(uint16_t*)(vram + 0x8002));
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];
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;
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;
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);
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;
278 if (dstLo < 0 || dstHi > 0xFFFF || (srcInc && (srcLo < 0 || srcHi > 0xFFFF)))
280 dmaWrapped(vram, srcVramAddr, dstVramAddr, width, height, pitch, srcInc, dstInc);
282 else if (srcInc == 0)
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);
289 else if (dstLo > srcHi || srcLo > dstHi)
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);
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)
304 for (uint32_t x = 0; x < width; ++x, rs += srcInc, rd += dstInc) *rd = *rs;
308 *(uint16_t*)(vram + 0x8008) = 0;
311#if defined(TMS9900_WATCH_WRITES)
317static void gpuDmaWatch(uint8_t* vram, uint32_t addr)
320 if (vram[0x8008]) triggerGpuDma(vram);
333static void PICO9918_IN_FLASH_FUNC(guard)(uint32_t region,
void* a, uint32_t bytes)
335 uintptr_t addr = (uintptr_t)a;
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;
342 mpu_hw->rbar = base | M0PLUS_MPU_RBAR_VALID_BITS | region;
343 mpu_hw->rasr = 1 | (0x07 << 1) | (srd << 8) | 0x15000000;
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;
351static void __not_in_flash_func(guardEnable)(uint32_t region,
bool on)
353 mpu_hw->rnr = region;
356 mpu_hw->rasr |= M0PLUS_MPU_RASR_ENABLE_BITS;
358 mpu_hw->rasr &= ~M0PLUS_MPU_RASR_ENABLE_BITS;
361 mpu_hw->rlar |= M33_MPU_RLAR_EN_BITS;
363 mpu_hw->rlar &= ~M33_MPU_RLAR_EN_BITS;
371 tms9918->palDirty = 1;
373 uint32_t save = save_and_disable_interrupts();
375 guardEnable(1,
false);
376 restore_interrupts(save);
381 if (tms9918->palDirty)
return;
384 guardEnable(1,
true);
385 tms9918->palDirty = 1;
394 bool running =
false;
395 bool outOfBudget =
false;
396#if PICO9918_GPU_BUDGETED
401 tms9918->restart = 0;
402 if ((tms9918->gpuAddress & 1) == 0)
404 uint16_t lastAddress = tms9918->gpuAddress;
414 mpu_hw->ctrl = M0PLUS_MPU_CTRL_PRIVDEFENA_BITS | M0PLUS_MPU_CTRL_ENABLE_BITS;
416 mpu_hw->ctrl = M33_MPU_CTRL_PRIVDEFENA_BITS | M33_MPU_CTRL_ENABLE_BITS;
420#if PICO9918_GPU_BUDGETED
421 lastAddress = run9900Budget(
PICO9918_INST tms9918->vram.bytes, lastAddress, &tms9918->gpuWp,
423 &outOfBudget, !PICO9918_GPU_FLAT_MEM(tms9918));
436 tms9918->gpuAddress = lastAddress;
437 tms9918->restart = 0;
438 running = outOfBudget;
444 if (tms9918->vram.bytes[0x8008])
445 triggerGpuDma(tms9918->vram.bytes);
452 if (running)
return true;
470 mpu_hw->mair[0] = 0x44;
472 guard(0, &(tms9918->vram.bytes[0x8000]), 32);
473 guard(1, tms9918->vram.map.pram, 64 *
sizeof(*tms9918->vram.map.pram));
475#if PICO9918_GPU_BUDGETED
476 tms9918->gpuWp = PICO9918_GPU_WORKSPACE;
487static volatile bool reportedBack =
true;
488static volatile uint32_t gpuTimeUs = 0;
495 if (!reportedBack)
return totalTime;
513 if (tms9918->restart)
515 reportedBack =
false;
516 uint32_t gpuStart = PICO9918_HOST_TIME_US();
518 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
527 for (
int i = 0; i < (int)(
sizeof(configActionKeys) /
sizeof(configActionKeys[0])); ++i)
529 const uint8_t key = configActionKeys[i];
530 if (tms9918->config[key])
532 tms9918->config[key] = 0;
542 return tms9918->gpuAddress;
549#if PICO9918_GPU_BUDGETED
550 const uint8_t armed = tms9918->restart;
552 return (armed && armed != PICO9918_GPU_RESUMING) ? PICO9918_GPU_WORKSPACE : tms9918->gpuWp;
554 return PICO9918_GPU_WORKSPACE;
562 return (uint32_t)
sizeof(((pico9918_t*)0)->vram);
572 return ((
const uint8_t*)&tms9918->vram)[addr];
590 return (uint16_t)(tms9918->gpuStatus << 8);
602 bool running =
false;
604 if (tms9918->restart)
606 reportedBack =
false;
607 uint32_t gpuStart = PICO9918_HOST_TIME_US();
609 gpuTimeUs += PICO9918_HOST_TIME_US() - gpuStart;
612 if (running) tms9918->restart = PICO9918_GPU_RESUMING;
614 reportedBack = !running;
621 for (
int i = 0; i < (int)(
sizeof(configActionKeys) /
sizeof(configActionKeys[0])); ++i)
623 const uint8_t key = configActionKeys[i];
624 if (tms9918->config[key])
626 tms9918->config[key] = 0;
634#if PICO9918_BUILD_DEBUG_API
641#if defined(TMS9900_STEP_HOOK)
643 gpuStep.userdata = userdata;
677#define GPU_SLICE_FROM_IPS(ips, lines, hz) ((uint32_t)((ips) / ((lines) * (hz))) + 1u)
681#if PICO9918_GPU_BUDGETED
682 tms9918->gpuIps = instructionsPerSecond;
683 tms9918->gpuSlice = instructionsPerSecond ? GPU_SLICE_FROM_IPS(instructionsPerSecond, 240u, 60u) : 0u;
687 (void)instructionsPerSecond;
691#if PICO9918_GPU_BUDGETED
697 if (!tms9918->gpuIps || !lines || frameRateHz < 1.0f)
return;
699 tms9918->gpuSlice = GPU_SLICE_FROM_IPS(tms9918->gpuIps, lines, (uint32_t)frameRateHz);
uint16_t pico9918_gpu_status(pico9918_t *tms9918)
see the header.
uint16_t pico9918_gpu_pc(pico9918_t *tms9918)
see the header.
bool pico9918_debug_gpu_step_n(pico9918_t *tms9918, uint32_t instructions, pico9918_gpu_step_fn cb, void *userdata)
see pico9918_debug.h.
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
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.
volatile uint8_t pico9918_gpu_palette_guard_off
The palette guard, as much of it as the host has to see.
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
uint32_t pico9918_gpu_mem_size(void)
see the header.
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...
uint16_t pico9918_gpu_wp(pico9918_t *tms9918)
see the header.
void pico9918_gpu_loop(pico9918_t *tms9918)
GPU main loop - call from a dedicated core/thread.
void pico9918_gpu_rearm_palette_guard(pico9918_t *tms9918)
put the palette guard back, from the core that owns the MPU
uint8_t pico9918_gpu_mem_value(pico9918_t *tms9918, uint32_t addr)
see the header.
bool pico9918_gpu_flash_pending(pico9918_t *tms9918)
see the header.
uint16_t pico9918_gpu_reg_value(pico9918_t *tms9918, uint8_t reg)
see the header.
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.
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
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.
void pico9918_gpu_set_clock(pico9918_t *tms9918, uint32_t instructionsPerSecond)
Hand GPU execution to the library, at this many instructions a second.
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.
pico9918-core - GPU Interface
pico9918_flash_result_t
what a flash operation finished as, reported in status register 2
@ PICO9918_FLASH_ERR_UNSUPPORTED
no host is listening - see pico9918_gpu_set_flash_callback
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
#define PICO9918_INST_ONLY
pass the instance as the only argument
@ PICO9918_REG_GPU_CONTROL
GPU load and trigger.
@ PICO9918_SR_GPU
GPU running and its status byte.
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
#define PICO9918_INST
pass the instance ahead of other arguments
#define PICO9918_DLLEXPORT
the linkage every public entry point carries - see LINKAGE MODES above
pico9918-core - config byte layout
pico9918-core - debugger access
pico9918-core - the private instance layout
pico9918-core - TMS9900 CPU interpreter (portable C)