pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
gpu.h File Reference

pico9918-core - GPU Interface More...

#include "pico9918.h"
+ Include dependency graph for gpu.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Macros

#define PICO9918_GPU_IPS_CLASSIC   7000000u /* PICO9918, RP2040 at 352MHz */
 Rough GPU throughput, in TMS9900 instructions a second, for pico9918_gpu_set_clock.
 
#define PICO9918_GPU_IPS_PRO   10000000u
 PICO9918 PRO, RP2350 at 352MHz.
 
#define PICO9918_GPU_IPS_F18A   8500000u
 the F18A itself, ie. a PRO at 302MHz
 
#define PICO9918_GPU_ST_LGT   0x8000
 ST0, logical greater than.
 
#define PICO9918_GPU_ST_AGT   0x4000
 ST1, arithmetic greater than.
 
#define PICO9918_GPU_ST_EQ   0x2000
 ST2, equal.
 
#define PICO9918_GPU_ST_C   0x1000
 ST3, carry.
 
#define PICO9918_GPU_ST_OV   0x0800
 ST4, overflow.
 
#define PICO9918_GPU_ST_P   0x0400
 ST5, odd parity.
 

Enumerations

enum  pico9918_flash_result_t {
  PICO9918_FLASH_OK = 0 , PICO9918_FLASH_ERR_HEADER = 1 , PICO9918_FLASH_ERR_SEQUENCE = 2 , PICO9918_FLASH_ERR_SIZE = 3 ,
  PICO9918_FLASH_ERR_VERIFY = 4 , PICO9918_FLASH_ERR_UNSUPPORTED = 5 , PICO9918_FLASH_ERR_FULL = 6
}
 what a flash operation finished as, reported in status register 2 More...
 

Functions

void pico9918_gpu_init (pico9918_t *tms9918)
 Initialize the TMS9900 GPU.
 
void pico9918_gpu_rearm_palette_guard (pico9918_t *tms9918)
 put the palette guard back, from the core that owns the MPU
 
void pico9918_gpu_loop (pico9918_t *tms9918)
 GPU main loop - call from a dedicated core/thread.
 
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 requests.
 
void pico9918_gpu_set_clock (pico9918_t *tms9918, uint32_t instructionsPerSecond)
 Hand GPU execution to the library, at this many instructions a second.
 
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.
 
uint16_t pico9918_gpu_pc (pico9918_t *tms9918)
 Where the GPU is: the address the next slice resumes from.
 
uint8_t pico9918_gpu_mem_value (pico9918_t *tms9918, uint32_t addr)
 a byte of the instance's memory, without disturbing anything
 
uint32_t pico9918_gpu_mem_size (void)
 the size of that space, so a memory view knows where to stop
 
uint16_t pico9918_gpu_reg_value (pico9918_t *tms9918, uint8_t reg)
 a GPU workspace register, R0-R15, without disturbing anything
 
uint16_t pico9918_gpu_status (pico9918_t *tms9918)
 the GPU's status register between instructions
 
uint32_t pico9918_gpu_time (uint32_t totalTime)
 Return the GPU's CPU time in microseconds.
 
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.
 
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.
 
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.
 

Variables

volatile uint8_t pico9918_gpu_palette_guard_off
 The palette guard, as much of it as the host has to see.
 

Detailed Description

pico9918-core - GPU Interface

Copyright (c) 2021 Troy Schrapel

This code is licensed under the MIT license

https://github.com/visrealm/pico9918-core

Purpose: Library-public interface to the TMS9900 GPU (F18A compatibility layer)

Definition in file gpu.h.

Macro Definition Documentation

◆ PICO9918_GPU_IPS_CLASSIC

#define PICO9918_GPU_IPS_CLASSIC   7000000u /* PICO9918, RP2040 at 352MHz */

Rough GPU throughput, in TMS9900 instructions a second, for pico9918_gpu_set_clock.

At the top clock preset, not the 252MHz a board boots at. The PRO and F18A figures sit where a measured comparison puts them - a PRO at 352MHz beats an F18A, which lands at a PRO's 302MHz preset within a couple of percent - and the RP2040 where cycle-counting its dispatch does. Read the GPU% row of the diagnostics overlay to do better.

Definition at line 82 of file gpu.h.

◆ PICO9918_GPU_IPS_PRO

#define PICO9918_GPU_IPS_PRO   10000000u

PICO9918 PRO, RP2350 at 352MHz.

Definition at line 83 of file gpu.h.

◆ PICO9918_GPU_IPS_F18A

#define PICO9918_GPU_IPS_F18A   8500000u

the F18A itself, ie. a PRO at 302MHz

Definition at line 84 of file gpu.h.

◆ PICO9918_GPU_ST_LGT

#define PICO9918_GPU_ST_LGT   0x8000

ST0, logical greater than.

Definition at line 193 of file gpu.h.

◆ PICO9918_GPU_ST_AGT

#define PICO9918_GPU_ST_AGT   0x4000

ST1, arithmetic greater than.

Definition at line 194 of file gpu.h.

◆ PICO9918_GPU_ST_EQ

#define PICO9918_GPU_ST_EQ   0x2000

ST2, equal.

Definition at line 195 of file gpu.h.

◆ PICO9918_GPU_ST_C

#define PICO9918_GPU_ST_C   0x1000

ST3, carry.

Definition at line 196 of file gpu.h.

◆ PICO9918_GPU_ST_OV

#define PICO9918_GPU_ST_OV   0x0800

ST4, overflow.

Definition at line 197 of file gpu.h.

◆ PICO9918_GPU_ST_P

#define PICO9918_GPU_ST_P   0x0400

ST5, odd parity.

Definition at line 198 of file gpu.h.

Enumeration Type Documentation

◆ pico9918_flash_result_t

what a flash operation finished as, reported in status register 2

Enumerator
PICO9918_FLASH_OK 

the operation completed

PICO9918_FLASH_ERR_HEADER 

the staged block is not one this build takes

PICO9918_FLASH_ERR_SEQUENCE 

a block arrived without the one before it

PICO9918_FLASH_ERR_SIZE 

the target is past the region the operation may write

PICO9918_FLASH_ERR_VERIFY 

what was read back is not what was written

PICO9918_FLASH_ERR_UNSUPPORTED 

no host is listening - see pico9918_gpu_set_flash_callback

PICO9918_FLASH_ERR_FULL 

no block is free to allocate

Definition at line 231 of file gpu.h.

Function Documentation

◆ pico9918_gpu_init()

void pico9918_gpu_init ( pico9918_t *  tms9918)

Initialize the TMS9900 GPU.

Must be called after pico9918_init() / pico9918_reset().

Definition at line 388 of file gpu.c.

◆ pico9918_gpu_rearm_palette_guard()

void pico9918_gpu_rearm_palette_guard ( pico9918_t *  tms9918)

put the palette guard back, from the core that owns the MPU

Definition at line 306 of file gpu.c.

References pico9918_gpu_palette_guard_off.

◆ pico9918_gpu_loop()

void pico9918_gpu_loop ( pico9918_t *  tms9918)

GPU main loop - call from a dedicated core/thread.

Runs indefinitely; processes GPU programs, flash requests, and config saves.

Definition at line 546 of file gpu.c.

References pico9918_gpu_step(), and PICO9918_INST_ONLY.

◆ pico9918_gpu_step()

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 requests.

Returns.

It is the loop's body rather than a second copy of it, so a program run this way is run by the same code the device runs it with, and it is timed into the same accumulator pico9918_gpu_time reads.

How long it takes is the program's business: run9900 returns on IDLE or when the program clears its own run flag (TMS register 0x38 bit 0), and a program that does neither does not return.

Which makes this the wrong entry for a host with one thread, however much it looks like the right one. A program may WAIT on the display - the scanline being scanned out is readable at >7000 - and the caller that would advance the raster is the one blocked in here. Use pico9918_gpu_step_n for that, or give the GPU a thread and render on the one you have.

Definition at line 430 of file gpu.c.

References PICO9918_INST, and PICO9918_INST_ONLY.

Referenced by pico9918_gpu_loop().

◆ pico9918_gpu_set_clock()

void pico9918_gpu_set_clock ( pico9918_t *  tms9918,
uint32_t  instructionsPerSecond 
)

Hand GPU execution to the library, at this many instructions a second.

Zero - the default - leaves the GPU to whoever else drives it: a board's second core, or a host thread running pico9918_gpu_loop(). Set a rate and the library runs it instead, from the register write that arms a program and once per scanline after, and a host that sets one calls no other GPU entry point. Arming matters: software probing for an F18A reads its result back a few cycles later, so a GPU serviced once a scanline has not run yet and the probe intermittently sees no F18A at all.

The rate becomes a per-scanline slice, re-derived each frame, so a mode change needs nothing from the host. Ignored where pico9918_gpu_step_n's cap is - a hand-written Thumb core runs to completion - and it charges GPU time to the calling thread.

Definition at line 562 of file gpu.c.

◆ pico9918_gpu_step_n()

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.

Zero means no cap, which is pico9918_gpu_step().

This is the entry for a host with one thread. pico9918_gpu_step() cannot come back until the program stops itself, so a program that waits on the scanline at >7000 - to page a bitmap in the vertical blank, say - would wait forever: the caller that would advance the raster is the one blocked inside it. Capped, the caller gets control back with the PC kept, renders, and calls again:

while (pico9918_gpu_step_n(PICO9918_INST 20000))
  renderOneScanline();

A host with a thread to spare wants pico9918_gpu_loop() on it instead, which is what the firmware does. Both shapes are real; this one asks nothing of the host but a loop.

Only the portable C core counts instructions. On a board built with the hand-written Thumb core the cap is ignored and this runs to completion - which costs that build nothing, because it has a core to give the GPU.

Definition at line 509 of file gpu.c.

References PICO9918_INST, and PICO9918_INST_ONLY.

◆ pico9918_gpu_pc()

uint16_t pico9918_gpu_pc ( pico9918_t *  tms9918)

Where the GPU is: the address the next slice resumes from.

The arming address before it first runs, and the point it reached after a capped slice returns true.

ODD MEANS NOTHING IS ARMED. A reset parks 0xFFFF here and the engine refuses to start from an odd address, so a caller polling this reads odd as "no program", not as a position. An even value is a real address whether or not a program is still running - use pico9918_gpu_step_n()'s return for that.

Where the GPU is: the address the next slice resumes from.

The address a slice resumes from; odd means not running.

Definition at line 459 of file gpu.c.

◆ pico9918_gpu_mem_value()

uint8_t pico9918_gpu_mem_value ( pico9918_t *  tms9918,
uint32_t  addr 
)

a byte of the instance's memory, without disturbing anything

The BACKING STATE, not the map a GPU program observes. Those differ: a running personality mirrors 0x4xxx, 0x5xxx, 0x6xxx and 0x7xxx across 4KB each and answers 0 in the holes, where this is every byte exactly once. It is the view a debugger wants, because it re-lays-out nothing when the chip personality changes, and the decoded one is derivable from it.

Not what the host data port sees either: pico9918_vram_value is the guest's view and stops at 0x3FFF, so it cannot reach GRAM at 0x4000, the palette at 0x5000, the register and status windows, or the workspace. Disassembly and memory views want this one, and pico9918_debug.h has the span form of it where a build asks for that.

The space runs past 0xFFFF. The GPU's workspace pointer is 0xFFFE, so R0 is the last word of the 64KB map and R1-R15 spill into an overflow above it. Anything beyond the space reads 0, so a view that walks off the end sees zeroes rather than the instance.

a byte of the instance's memory, without disturbing anything

A byte of that map, or 0 past the end of it.

Definition at line 476 of file gpu.c.

References pico9918_gpu_mem_size().

Referenced by pico9918_gpu_reg_value().

◆ pico9918_gpu_mem_size()

uint32_t pico9918_gpu_mem_size ( void  )

the size of that space, so a memory view knows where to stop

the size of that space, so a memory view knows where to stop

The whole map the GPU addresses, workspace overflow included.

Definition at line 469 of file gpu.c.

Referenced by pico9918_debug_read(), and pico9918_gpu_mem_value().

◆ pico9918_gpu_reg_value()

uint16_t pico9918_gpu_reg_value ( pico9918_t *  tms9918,
uint8_t  reg 
)

a GPU workspace register, R0-R15, without disturbing anything

The workspace is fixed at 0xFFFE and a TMS9900 register is a word there, so this is the two bytes at 0xFFFE + 2n read big-endian. Only the low four bits of reg are used. Reachable through pico9918_gpu_mem_value() as well; this is here because the wrap past 0xFFFF is the library's business, not a debugger's.

a GPU workspace register, R0-R15, without disturbing anything

R0-R15 as words at the fixed workspace.

Definition at line 486 of file gpu.c.

References pico9918_gpu_mem_value(), and PICO9918_INST.

◆ pico9918_gpu_status()

uint16_t pico9918_gpu_status ( pico9918_t *  tms9918)

the GPU's status register between instructions

In the architectural bit positions, which is where STST stores them and where a disassembler or a flag display expects them - use the PICO9918_GPU_ST_* masks. The cores keep the flags in the low byte internally; this is the only published view and it agrees with the instruction.

TRAP: maintained only where the library paces the GPU itself. The hand-written Thumb cores a board builds run a program to completion and keep the status in a local, so there is no point between instructions for this to describe and it reads whatever it last held. Where pico9918_gpu_step_n() honours its cap - every desktop build - this is the status at the point the slice stopped, which is what a single step wants.

the GPU's status register between instructions

The status between instructions, where one paces them.

Definition at line 496 of file gpu.c.

◆ pico9918_gpu_time()

uint32_t pico9918_gpu_time ( uint32_t  totalTime)

Return the GPU's CPU time in microseconds.

If the GPU is still running (hasn't reported back), returns totalTime.

CROSS-CORE: the accumulator and its reported-back flag are written by pico9918_gpu_loop - core 0 on Pico - while these two calls are made from the frame/overlay side on core 1. Both are volatile and neither call is guarded: the worst case is one sample window's update being lost, which is acceptable for a statistics readout and cheaper than a critical section per frame.

Definition at line 412 of file gpu.c.

Referenced by pico9918_diag_update().

◆ pico9918_gpu_reset_time()

void pico9918_gpu_reset_time ( void  )

Reset the internal GPU time accumulator to 0.

Cross-core, unguarded - see pico9918_gpu_time.

Definition at line 421 of file gpu.c.

Referenced by pico9918_diag_update().

◆ pico9918_gpu_set_flash_callback()

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.

Pass NULL to disable.

Registered per instance in a multi-instance build - see pico9918.h for why the two builds take different shapes.

Definition at line 95 of file gpu.c.

◆ pico9918_gpu_flash_complete()

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.

The busy flag is the engine's and a host has no other way to clear it: the palette rebuild is forced while SR2 bit 7 is set, so an operation left un-ended rebuilds the lookup table on every active scanline. Call this once per request, from the callback or later - a host that hands the work to another thread ends it when that finishes, and the guest polls SR2 until then.

The pending request itself is already taken before the callback is entered, so a request arriving during a long erase re-arms rather than being lost here.

Bits 6-5 (the retry count) and 1-0 (the progress code) are left as they were found.

Definition at line 112 of file gpu.c.

References PICO9918_REG_GPU_CONTROL, and PICO9918_SR_GPU.

◆ pico9918_gpu_set_config_save_callback()

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.

The callback receives the config array pointer and the config key that fired (save / forced save / pending confirm / pending cancel - semantics are owned by the host). The key is cleared before the callback is invoked. Pass NULL to disable.

Registered per instance in a multi-instance build - see pico9918.h for why the two builds take different shapes.

Definition at line 101 of file gpu.c.

Variable Documentation

◆ pico9918_gpu_palette_guard_off

volatile uint8_t pico9918_gpu_palette_guard_off
extern

The palette guard, as much of it as the host has to see.

A GPU palette write has no other way of announcing itself, so an MPU region faults on it, marks the palette dirty and takes itself out of the way until the renderer has taken the flag. Putting it back is the host's to schedule, because the MPU belongs to the core running the GPU, and the only place that core is reliably idle is its scanline interrupt: read the flag from there, and call the re-arm.

Definition at line 256 of file gpu.c.

Referenced by pico9918_gpu_rearm_palette_guard().