70#include "pico/stdlib.h"
73#include "../pico9918.h"
74#include "../pico9918_config.h"
77#define GRAPHICS_NUM_COLS 32
78#define GRAPHICS_NUM_ROWS 24
79#define GRAPHICS_CHAR_WIDTH 8
81#define TEXT_NUM_COLS 40
82#define TEXT_NUM_ROWS 24
83#define TEXT_CHAR_WIDTH 6
84#define TEXT_PADDING_PX 8
85#define TEXT80_NUM_COLS 80
93#ifndef PICO9918_TEXT80_8BPP
94#define PICO9918_TEXT80_8BPP PICO9918_BUILD_TEXT80_8BPP
95#elif (PICO9918_TEXT80_8BPP != 0) != (PICO9918_BUILD_TEXT80_8BPP != 0)
96#error "PICO9918_TEXT80_8BPP disagrees with the archive - drop it and let pico9918_build_config.h supply it"
101#if PICO9918_TEXT80_8BPP
102#define SCANLINE_BYTES_MAX (TMS9918_PIXELS_X * 2)
105#define TEXT80_PADDING_PX (TEXT_PADDING_PX * 2)
107#define SCANLINE_BYTES_MAX TMS9918_PIXELS_X
108#define TEXT80_PADDING_PX TEXT_PADDING_PX
112#define SCANLINE_BUFFER_BYTES (SCANLINE_BYTES_MAX + 8)
113#define SCANLINE_MASK_WORDS ((SCANLINE_BUFFER_BYTES + 31) / 32)
115#define PATTERN_BYTES 8
116#define GFXI_COLOR_GROUP_SIZE 8
118#define MAX_SPRITES 32
120#define SPRITE_ATTR_Y 0
121#define SPRITE_ATTR_X 1
122#define SPRITE_ATTR_NAME 2
123#define SPRITE_ATTR_COLOR 3
124#define SPRITE_ATTR_BYTES 4
125#define LAST_SPRITE_YPOS 0xD0
126#define MAX_SCANLINE_SPRITES 4
128#define BASE_VRAM_SIZE (1 << 14)
129#define VRAM_SIZE (1 << 16)
133#define TMS_REGISTERS 64
134#define TMS_STATUS_REGISTERS 16
136#define VRAM_MASK (BASE_VRAM_SIZE - 1)
140#define PICO9918_CPU_VRAM_MASK(tms) VRAM_MASK
144#if PICO9918_BUILD_RUNTIME_CHIP
145#define PICO9918_VRAM_4K_CHIP(T) PICO9918_HAS(T, PICO9918_FEAT_VRAM_4K)
147#define PICO9918_VRAM_4K_CHIP(T) false
153 uint8_t base[BASE_VRAM_SIZE];
155 uint8_t gram1[0x1000];
156 uint16_t pram[0x0800];
159 uint8_t registers[TMS_REGISTERS];
161 uint8_t gram2[0x1000 - TMS_REGISTERS];
164 uint8_t gram3[0x4000 - 2];
167 uint8_t status[TMS_STATUS_REGISTERS];
169 uint8_t gram4[0x5000 - TMS_STATUS_REGISTERS];
177#if defined(PICO_BUILD) && !defined(PICO9918_GPU_C_CORE)
178#define PICO9918_GPU_BUDGETED 0
180#define PICO9918_GPU_BUDGETED 1
183#define PICO9918_GPU_WORKSPACE 0xFFFEu
185#define PICO9918_GPU_RESUMING 2u
189#define PICO9918_UNLOCKED(T) ((T)->isUnlocked)
190#define PICO9918_UNLOCK_REG(R) ((R) == (0x80 | PICO9918_REG_UNLOCK))
191#define PICO9918_UNLOCK_VALUE(V) (((V) & 0xfc) == PICO9918_R57_UNLOCK)
201#define PICO9918_FEAT_UNLOCK 0x01
202#define PICO9918_FEAT_CONFIG 0x02
203#define PICO9918_FEAT_OVERLAY 0x04
204#define PICO9918_FEAT_BITMAP 0x08
205#define PICO9918_FEAT_VRAM_4K 0x10
206#define PICO9918_FEAT_WIDE_T80 0x20
207#define PICO9918_FEAT_GPU_RAM 0x40
209#if PICO9918_BUILD_RUNTIME_CHIP
210#define PICO9918_HAS(T, F) (((T)->features & (F)) != 0)
219#define PICO9918_SR1_ID(T) (((T)->chip >= PICO9918_CHIP_PICO9918) ? 0xE8 : 0xE0)
221#define PICO9918_HAS(T, F) true
222#define PICO9918_SR1_ID(T) 0xE8
225#define TMS_REGISTER(T, R) (T->vram.map.registers[R])
226#define TMS_STATUS(T, R) (T->vram.map.status[R])
230#define PICO9918_GM2(T) PICO9918_HAS(T, PICO9918_FEAT_BITMAP)
234#define PICO9918_WIDE_T80(T) PICO9918_HAS(T, PICO9918_FEAT_WIDE_T80)
238#define PICO9918_GPU_FLAT_MEM(T) PICO9918_HAS(T, PICO9918_FEAT_GPU_RAM)
241#define PICO9918_CAN_UNLOCK(T) PICO9918_HAS(T, PICO9918_FEAT_UNLOCK)
242#define PICO9918_M4(T) \
243 (PICO9918_CAN_UNLOCK(T) && (TMS_REGISTER(T, TMS_REG_0) & TMS_R0_MODE_TEXT_80))
248#define PICO9918_SCAN_SPRITE_LIMIT(T) \
249 (PICO9918_CAN_UNLOCK(T) ? (MAX_SPRITES - 1) : MAX_SCANLINE_SPRITES)
251#if PICO9918_BUILD_LAYER_MASK
252#define PICO9918_DRAWS(T, BIT) (((T)->suppress & (BIT)) == 0)
253#define PICO9918_SUPPRESSED(T, BIT) (((T)->suppress & (BIT)) != 0)
254#define PICO9918_LAYER_SUB(T, BIT, SUB, V) (((T)->suppress & (BIT)) ? (uint32_t)(SUB) : (uint32_t)(V))
256#define PICO9918_DRAWS(T, BIT) 1
257#define PICO9918_SUPPRESSED(T, BIT) 0
258#define PICO9918_LAYER_SUB(T, BIT, SUB, V) ((uint32_t)(V))
271 uint8_t bytes[VRAM_SIZE];
276 uint32_t currentAddress;
288 uint8_t regWriteStage;
291 uint8_t regWriteStage0Value;
294 uint8_t readAheadBuffer;
300 volatile uint8_t restart;
301 volatile uint8_t flash;
303#if PICO9918_GPU_BUDGETED
311 uint8_t palWriteStage;
312 uint8_t palWriteStage0Value;
318#if PICO9918_BUILD_RUNTIME_CHIP
326 bool scanlineHasSprites;
328#if PICO9918_BUILD_LAYER_MASK
332#if PICO9918_BUILD_STEP_CALLBACK
334 pico9918_gpu_step_fn stepFn;
340 uint32_t currentTime;
360 uint8_t __aligned(4) tileLayer2Buffer[SCANLINE_BUFFER_BYTES];
361 uint8_t __aligned(4) tileLayer1Buffer[SCANLINE_BUFFER_BYTES];
362 uint32_t __aligned(4) layerSelectionMask[SCANLINE_MASK_WORDS];
363 uint32_t __aligned(4) finalMask[SCANLINE_MASK_WORDS];
372#if !PICO9918_SINGLE_INSTANCE
378 pico9918_config_applied_fn fn;
384 pico9918_config_reload_fn fn;
390 pico9918_gpu_flash_fn fn;
396 pico9918_gpu_config_save_fn fn;
412_Static_assert(offsetof(
struct pico9918_s, vram) == 0,
413 "vram offset moved - PICO9918_MAP_* and the GPU MPU guard ranges both break");
415#if PICO9918_SINGLE_INSTANCE
416extern pico9918_t*
const tms9918;
430extern const uint8_t* pico9918_cached_line_source;
437#if PICO9918_TEXT80_8BPP
438#define TEXT80_WIDE_ROW \
439 (pico9918_cached_mode == TMS_MODE_TEXT80 && PICO9918_UNLOCKED(tms9918) && PICO9918_WIDE_T80(tms9918))
441#define TEXT80_WIDE_ROW false
447 return pico9918_cached_mode;
459 return pico9918_cached_line_source;
476 addr &= PICO9918_CPU_VRAM_MASK(tms9918);
478 if (PICO9918_VRAM_4K_CHIP(tms9918) && !(TMS_REGISTER(tms9918, TMS_REG_1) &
TMS_R1_RAM_16K))
481 addr = (addr & 0x203f) | ((addr & 0x0fc0) << 1) | ((addr & 0x1000) >> 6);
507 if (tms9918->regWriteStage == 0)
511 tms9918->regWriteStage0Value = data;
512 tms9918->regWriteStage = 1;
520 if ((data & 0x40) == 0)
527 tms9918->currentAddress = tms9918->regWriteStage0Value | ((data & 0x3f) << 8);
528 if ((data & 0x40) == 0)
530 tms9918->readAheadBuffer =
535 tms9918->regWriteStage = 0;
576PICO9918_INLINE_HOT
void pico9918_status_read_core(
PICO9918_INST_ARG uint8_t readReg, uint8_t readVal)
581 tms9918->frameStatus &= ~readVal;
583 tms9918->frameStatus |= 0x1f;
606 tms9918->regWriteStage = 0;
642 tms9918->regWriteStage = 0;
644 tms9918->palWriteStage = 0;
649 const uint8_t readVal = TMS_STATUS(tms9918, readReg);
672 if (tms9918->palWriteStage == 0)
674 tms9918->palWriteStage0Value = data & 0x0f;
675 ++tms9918->palWriteStage;
679 tms9918->palWriteStage = 0;
683 (tms9918->palWriteStage0Value) | (data << 8);
684 tms9918->palDirty = 1;
699 tms9918->regWriteStage = 0;
700 tms9918->readAheadBuffer = data;
710 tms9918->regWriteStage = 0;
711 uint8_t currentValue = tms9918->readAheadBuffer;
712 tms9918->readAheadBuffer =
721 tms9918->regWriteStage = 0;
722 tms9918->readAheadBuffer =
725 return tms9918->readAheadBuffer;
731 return tms9918->readAheadBuffer;
770 tms9918->frameStatus = status;
784 return tms9918->frameStatus;
790 return tms9918->frameInt;
796 return tms9918->frameDoneInt;
799PICO9918_INLINE
void pico9918_set_frame_done_int_impl(
PICO9918_INST_ARG bool done)
801 tms9918->frameDoneInt = done;
824extern int pico9918_frame_count;
825extern int pico9918_dropped_frames_count;
827#if PICO9918_DIAG_GPU_FRAME_COUNTER
831extern uint32_t pico9918_gpu_frame_count;
840 return pico9918_frame_count;
850 pico9918_frame_count = 0;
851#if PICO9918_DIAG_GPU_FRAME_COUNTER
852 pico9918_gpu_frame_count = 0;
871extern int pico9918_v_pixels;
872extern uint32_t pico9918_v_border;
873extern bool pico9918_valid_writes;
874extern uint8_t pico9918_v_scale;
875extern uint16_t pico9918_v_virtual;
883extern uint32_t pico9918_border_bg;
893 return pico9918_v_pixels;
898 return pico9918_v_border;
907 return pico9918_valid_writes;
919 return pico9918_dropped_frames_count;
950PICO9918_INLINE uint16_t pico9918_frame_map_line_impl(
PICO9918_INST_ARG uint16_t y, uint8_t field,
951 bool interlaced, uint8_t fieldOrder)
955 tmsY = y * 2 + (field ^ fieldOrder);
968 if (newInt != tms9918->frameInt)
970 tms9918->frameInt = newInt;
971 PICO9918_HOST_SET_INT(newInt);
1002 tms9918->frameInt =
false;
1003 tms9918->frameDoneInt =
true;
1024extern PICO9918_PALETTE_LUT_T pico9918_palette_lut[256];
1026#if !PICO9918_SINGLE_INSTANCE
1027extern const pico9918_t* pico9918_palette_owner;
1051#if PICO9918_BUILD_DEBUG_API
1067#if !PICO9918_SINGLE_INSTANCE
1070 if (pico9918_palette_owner != tms9918)
return true;
1073 return tms9918->palDirty || (TMS_STATUS(tms9918,
PICO9918_SR_GPU) & 0x80);
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
#define PICO9918_R47_DATA_PORT
register 47 bits: the palette data port
#define TMS_R1_RAM_16K
register 1 bits: VRAM size, blanking, interrupt, mode and sprite size
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
#define PICO9918_INST_ONLY
pass the instance as the only argument
void(* pico9918_interrupt_fn)(pico9918_t *instance, bool active, void *userdata)
the host's /INT hook: called whenever the library drives the line
#define PICO9918_R47_INDEX
first palette index to write
#define PICO9918_R15_STATUS_NUM
which status register S1 reads back
#define PICO9918_SR0_INT
status register 0 bits.
@ PICO9918_REG_VRAM_INC
signed VRAM address increment per access
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
@ PICO9918_REG_PALETTE_CONTROL
palette data port mode, auto-increment and index
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
@ PICO9918_SR_GPU
GPU running and its status byte.
#define PICO9918_R47_AUTO_INC
step the palette index after each entry
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
#define TMS9918_PIXELS_X
active display width, every mode
#define TMS_R0_DOUBLE_ROWS
PICO9918 only: twice the rows, drawn interlaced.
#define TMS_R0_INT_SCANLINE
assert /INT when the raster reaches the line in R19.
#define PICO9918_SR1_HF
status register 1 bits.
#define TMS_R1_INT_ENABLE
assert /INT at end of frame
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
#define PICO9918_INST
pass the instance ahead of other arguments
pico9918_mode_t
the display modes the VDP can be in, TMS9918A modes and F18A alike
#define PICO9918_INTERNAL
cross-TU linkage for what is not public API, so a DLL or wasm build exports none of it
#define PICO9918_BASE_V9938
the V9938 base
PICO9918_INLINE_HOT uint32_t pico9918_cpu_vram_addr_impl(pico9918_t *tms9918, uint32_t addr)
where a CPU-side VRAM access lands
void pico9918_config_apply(pico9918_t *tms9918)
apply the config block's VDP-side effects: registers 50 and 30, the palette unpack,...
PICO9918_INLINE void pico9918_frame_sync_int_impl(pico9918_t *tms9918)
THE single implementation of "a status register was just read" - shared by the public read (pico9918_...
PICO9918_INLINE_HOT void pico9918_write_addr_impl(pico9918_t *tms9918, uint8_t data)
write an address (mode = 1) to the tms9918
PICO9918_INTERNAL void pico9918_write_reg_value_impl(pico9918_t *tms9918, uint8_t regSelect, uint8_t value)
set a register from the second byte of a host register write
PICO9918_INLINE uint8_t pico9918_read_status_impl(pico9918_t *tms9918)
read from the status register
PICO9918_INLINE void pico9918_write_reconcile_int_impl(pico9918_t *tms9918)
bring /INT into agreement after a write that can change the predicate.
PICO9918_INLINE_HOT void pico9918_set_status_impl(pico9918_t *tms9918, uint8_t status)
set status flag
PICO9918_INLINE void pico9918_frame_reset_int_impl(pico9918_t *tms9918)
console-reset entry for the interrupt/status state.
PICO9918_INLINE_HOT void pico9918_interrupt_set_impl(pico9918_t *tms9918)
raise the interrupt flag in SR0 and the frame shadow
PICO9918_INLINE_HOT uint8_t pico9918_peek_status_impl(pico9918_t *tms9918)
read from the status register without resetting it
void pico9918_debug_sync_mode_impl(pico9918_t *tms9918)
see impl/pico9918_priv.h.
void pico9918_interrupt_dispatch(pico9918_t *tms9918, bool active)
see impl.
PICO9918_INLINE bool pico9918_status_select_active(pico9918_t *tms9918)
is R#15's status-register select live?
PICO9918_INLINE_HOT uint8_t pico9918_read_ahead_data_impl(pico9918_t *tms9918)
refill the read-ahead buffer from the current address and return the new value
PICO9918_INLINE_HOT void pico9918_status_read_reconcile_impl(pico9918_t *tms9918, uint8_t readReg, uint8_t readVal)
CPU-interface entry: the host's read-ahead already handed the CPU a value, so this only applies the r...
PICO9918_INLINE_HOT uint8_t pico9918_read_data_no_inc_impl(pico9918_t *tms9918)
return the buffered value without reading VRAM or advancing the address
PICO9918_INLINE_HOT bool pico9918_interrupt_status_impl(pico9918_t *tms9918)
whether /INT should be asserted
PICO9918_INLINE_HOT uint8_t pico9918_read_data_impl(pico9918_t *tms9918)
read data (mode = 0) from the tms9918
PICO9918_INLINE_HOT void pico9918_write_data_impl(pico9918_t *tms9918, uint8_t data)
write data (mode = 0) to the tms9918