pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_priv.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - the private instance layout
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 * The instance struct, the register and status accessors, and the privileged inline
12 * surface: everything a host needs that a plain consumer must not reach for. An
13 * emulator includes pico9918.h and its platform header; a host that drives the chip
14 * from an interrupt includes this, and takes on the ordering rules each entry states.
15 *
16 * The Impl entries are inline rather than calls because their callers are the host bus
17 * handlers and the per-scanline path, where a `bl` is not free.
18 *
19 * A DEBUGGER IS THE SECOND AUDIENCE, and the rule for it is which surface, not which
20 * operation.
21 *
22 * Reads are public because reads are safe, and the published set is meant to be
23 * complete. pico9918.h has the chip - pico9918_peek_status, pico9918_status_value,
24 * pico9918_read_data_no_inc, pico9918_reg_value, pico9918_vram_value - and gpu/gpu.h
25 * has the GPU: pico9918_gpu_pc, pico9918_gpu_mem_value, pico9918_gpu_mem_size,
26 * pico9918_gpu_reg_value, pico9918_gpu_status. A bridge that finds itself in this
27 * header for a READ has taken a wrong turn rather than made a judgement call, and if
28 * something genuinely has no public read then the gap is the bug.
29 *
30 * pico9918_debug.h is the rest of that set where a build asks for it - the span read and
31 * write, the map itself, the register file's own byte, and the register STORE that used
32 * to be the crossing described below.
33 *
34 * Two of those pairs look alike and are not:
35 *
36 * pico9918_vram_value | the guest's view, so it stops at 0x3FFF
37 * pico9918_gpu_mem_value | the BACKING STATE, not the map a GPU program observes: it
38 * | reaches GRAM, the palette, the register and status windows
39 * | and the workspace above 0xFFFF, each byte exactly once,
40 * | where a running personality mirrors four windows across
41 * | 4KB and answers 0 in the holes
42 * pico9918_reg_value | VR0-VR63, and the guest's view of them: on a locked device
43 * | it decodes three address bits, so reg 30 reads R6. The
44 * | physical register behind that is TMS_REGISTER, below
45 * pico9918_gpu_reg_value | the GPU's own R0-R15, out of its workspace
46 *
47 * WRITES that must not behave like the guest were the reason to be here, and the one
48 * that mattered has since been published. Every public register write but that one goes
49 * through the bus and so takes the unlock gate and the locked-mask aliasing with it - a
50 * locked device redirects VR30 to R6 rather than refusing it. A register editor writing
51 * what the operator typed wants pico9918_debug_reg_write, which is `TMS_REGISTER(tms9918,
52 * reg) = value` plus the four things a store still owes the instance.
53 *
54 * And the invariant that removal established: a PUBLIC entry must not silently write
55 * somewhere other than where its parameter names. The engine below takes the raw select
56 * byte, `0x80 | reg`, and is here rather than published for exactly that reason - typed
57 * as a register enum it turned PICO9918_REG_UNLOCK into a write of R1. Anything moved
58 * out to the public surface has to be honest about its own argument first.
59 *
60 * Nothing here is stable. It is versioned with the library and moves when the library
61 * does, so a tool that reaches in is pinned to a commit. That is the trade, and it is
62 * why anything on the guest's normal path belongs on the public surface instead.
63 */
64
65#pragma once
66
67#include "platform.h"
68
69#ifdef PICO_BUILD
70#include "pico/stdlib.h"
71#endif
72
73#include "../pico9918.h"
74#include "../pico9918_config.h"
75
76
77#define GRAPHICS_NUM_COLS 32
78#define GRAPHICS_NUM_ROWS 24
79#define GRAPHICS_CHAR_WIDTH 8
80
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
86
87/* 80 columns at eight bits a pixel. It buys the tile palette select, ECM, the bitmap layer and
88 the shared composite in 80-column text, none of which a four-bit line can represent at all.
89 This is the scanline buffer's width, so it is settled at build time, and it is what decides
90 whether PICO9918_CHIP_PICO9918_PRO is a personality this build can be at all. The host default
91 is wide; firmware selects it from the board. The tier then chooses within that - see
92 PICO9918_WIDE_T80. */
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"
97#endif
98
99/* The widest line any mode on this build renders. 80 columns show 480 pixels inside a 512-pixel
100 line, which is 256 bytes at four bits a pixel and 512 at eight. */
101#if PICO9918_TEXT80_8BPP
102#define SCANLINE_BYTES_MAX (TMS9918_PIXELS_X * 2)
103/* the picture begins at sprite pixel 8 in either depth, so its byte offset doubles with the depth
104 while the sprite grid it is measured in does not */
105#define TEXT80_PADDING_PX (TEXT_PADDING_PX * 2)
106#else
107#define SCANLINE_BYTES_MAX TMS9918_PIXELS_X
108#define TEXT80_PADDING_PX TEXT_PADDING_PX
109#endif
110
111/* room for the cell a fine scroll uncovers past the picture's far edge */
112#define SCANLINE_BUFFER_BYTES (SCANLINE_BYTES_MAX + 8)
113#define SCANLINE_MASK_WORDS ((SCANLINE_BUFFER_BYTES + 31) / 32)
114
115#define PATTERN_BYTES 8
116#define GFXI_COLOR_GROUP_SIZE 8
117
118#define MAX_SPRITES 32
119
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
127
128#define BASE_VRAM_SIZE (1 << 14) /* 16kB */
129#define VRAM_SIZE (1 << 16) /* 64kB */
130
131/* Enhanced registers are read outside the unlock gate - the backdrop's R24, the frame
132 module's R19 - so the mapped register file keeps the full F18A width. */
133#define TMS_REGISTERS 64
134#define TMS_STATUS_REGISTERS 16
135
136#define VRAM_MASK (BASE_VRAM_SIZE - 1) /* 0x3fff */
137
138/* CPU-side VRAM mask. TODO: base-dependent, for 16K TMS9918A/F18A mirroring
139 against the V9938's 128K */
140#define PICO9918_CPU_VRAM_MASK(tms) VRAM_MASK
141
142/* R1 bit 7 is the 4K/16K DRAM select, and only a part that drives DRAM has one. A
143 fixed PICO9918 build has SRAM, so the transform folds away entirely there. */
144#if PICO9918_BUILD_RUNTIME_CHIP
145#define PICO9918_VRAM_4K_CHIP(T) PICO9918_HAS(T, PICO9918_FEAT_VRAM_4K)
146#else
147#define PICO9918_VRAM_4K_CHIP(T) false
148#endif
149
150
151typedef struct
152{
153 uint8_t base[BASE_VRAM_SIZE]; // 0x0000-0x3FFF (16KB)
154 /* video ram */
155 uint8_t gram1[0x1000]; // 0x4000-0x4fff (4KB) 2x repeated 2KB
156 uint16_t pram[0x0800]; // 0x5000-0x5fff (4KB) 32x repeated 128B
157
158 /* 64 write-only registers */
159 uint8_t registers[TMS_REGISTERS]; // 0x6000-0x6040
160
161 uint8_t gram2[0x1000 - TMS_REGISTERS]; // 0x6040-0x6FFF (~4KB)
162 uint8_t scanline; // 0x7000
163 uint8_t blanking; // 0x7001
164 uint8_t gram3[0x4000 - 2]; // 0x7002-0xAFFF (~16KB)
165
166 /* status registers (read-only) */
167 uint8_t status[TMS_STATUS_REGISTERS]; // 0xB000
168
169 uint8_t gram4[0x5000 - TMS_STATUS_REGISTERS]; // 0xB010-0xFFFF (~20KB)
170 uint8_t wrksp[36]; // 0x10000 overflow for hidden workspace
172
173/* Whether a GPU pass can be capped, and so whether the library can pace one itself. The
174 hand-written Thumb cores run a program to completion and ignore a cap; the builds that
175 have them are boards, which run the GPU on a core of their own. Here rather than in
176 gpu.c because the scanline path has to fold the pacing away, not test for it. */
177#if defined(PICO_BUILD) && !defined(PICO9918_GPU_C_CORE)
178#define PICO9918_GPU_BUDGETED 0
179#else
180#define PICO9918_GPU_BUDGETED 1
181#endif
182
183#define PICO9918_GPU_WORKSPACE 0xFFFEu
184
185#define PICO9918_GPU_RESUMING 2u
186
187/* Has the F18A been unlocked, and is this a write to the register that decides it? The
188 runtime personality gate decides whether the write is honoured. */
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)
192
193/* What a personality answers to. Derived once, in pico9918_set_chip, so each site reads
194 one bit rather than re-deriving the ladder. Only what a personality can be asked to do
195 differently is a bit: the GPU is not one, because a program can only be started
196 through registers the unlock gate already covers.
197
198 Without the runtime switch there is nothing to gate - the build is one chip, and what
199 that chip can do it already does - so every one of them folds to a literal true and
200 the gates cost a board exactly what they cost it before they existed. */
201#define PICO9918_FEAT_UNLOCK 0x01 /* the F18A unlock write is honoured */
202#define PICO9918_FEAT_CONFIG 0x02 /* the VR58/59 config port and R63 firmware update */
203#define PICO9918_FEAT_OVERLAY 0x04 /* the splash and diagnostics overlays */
204#define PICO9918_FEAT_BITMAP 0x08 /* R0 M3 is decoded, so Graphics II exists */
205#define PICO9918_FEAT_VRAM_4K 0x10 /* R1 bit 7 is decoded, so 4K DRAM addressing exists */
206#define PICO9918_FEAT_WIDE_T80 0x20 /* enhanced colour reaches 80-column text, which needs a byte a pixel */
207#define PICO9918_FEAT_GPU_RAM 0x40 /* the GPU's whole 64KB is memory, not the F18A's windows */
208
209#if PICO9918_BUILD_RUNTIME_CHIP
210#define PICO9918_HAS(T, F) (((T)->features & (F)) != 0)
211/* SR1 is what software probing for an F18A reads: 0xE0 is the F18A ID, and the PICO9918
212 sets 0x08 for anyone who cares that it is not a real one. The base personality shares
213 the F18A value and never shows it: reaching SR1 needs a write to R15, which is above
214 the eight a locked device admits.
215
216 TRAP: >=, not ==. A PRO is a PICO9918 to software probing for one, so every
217 personality at or above PICO9918 answers 0xE8 and a tier added above must not fall
218 through to the real-F18A value by being numbered past the test. */
219#define PICO9918_SR1_ID(T) (((T)->chip >= PICO9918_CHIP_PICO9918) ? 0xE8 : 0xE0)
220#else
221#define PICO9918_HAS(T, F) true
222#define PICO9918_SR1_ID(T) 0xE8
223#endif
224
225#define TMS_REGISTER(T, R) (T->vram.map.registers[R])
226#define TMS_STATUS(T, R) (T->vram.map.status[R])
227
228/* Graphics II is the A in TMS9918A: the pre-A personality does not decode R0 M3.
229 PICO9918_HAS folds to a literal true without the runtime switch. */
230#define PICO9918_GM2(T) PICO9918_HAS(T, PICO9918_FEAT_BITMAP)
231
232/* The PRO tier's wide 80-column line. Only asked where the build has the buffer for it,
233 so a narrow build never reaches this and a board folds it to a literal true. */
234#define PICO9918_WIDE_T80(T) PICO9918_HAS(T, PICO9918_FEAT_WIDE_T80)
235
236/* An F18A's GPU reaches a few small windows above 16KB and mirrors each across its 4KB;
237 a PICO9918 backs the whole address space with RAM, which is what the assembly cores do. */
238#define PICO9918_GPU_FLAT_MEM(T) PICO9918_HAS(T, PICO9918_FEAT_GPU_RAM)
239
240/* A TMS9918A does not decode R0 bit 2. */
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))
244
245/* What VR30 resets to. A chip that cannot unlock has no VR30 to raise it with, so four a
246 line is the hardware's own limit; MAX_SPRITES - 1 is the F18A's "no limit" value, which
247 its jumper also selects. Folds to that in a fixed-chip build. */
248#define PICO9918_SCAN_SPRITE_LIMIT(T) \
249 (PICO9918_CAN_UNLOCK(T) ? (MAX_SPRITES - 1) : MAX_SCANLINE_SPRITES)
250
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))
255#else
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))
259#endif
260
261
262/* PRIVATE DATA STRUCTURE
263 * ---------------------- */
265{
266 /* First: every VRAM, register and status access goes through this union, so at offset
267 zero the instance pointer doubles as its base, and the GPU MPU guard's two ranges
268 are page-aligned by construction rather than by an offset-derived mask. */
269 union
270 {
271 uint8_t bytes[VRAM_SIZE];
273 } vram;
274
275 /* current address for cpu access (auto-increments) */
276 uint32_t currentAddress;
277
278 uint16_t gpuAddress;
279
280 /* The GPU's status register, across a bounded step and nothing else. run9900 keeps
281 it in a local, which is all a run to completion needs; a budget can expire between
282 the compare that sets a flag and the jump that reads it, so a resume that started
283 from zero would take the wrong branch. Only the portable core takes a budget, so
284 only the portable core reads this. */
285 uint16_t gpuStatus;
286
287 /* address or register write stage (0 or 1) */
288 uint8_t regWriteStage;
289
290 /* holds first stage of write to address/register port */
291 uint8_t regWriteStage0Value;
292
293 /* buffered value */
294 uint8_t readAheadBuffer;
295
296 uint8_t lockedMask; // 0x07 when locked, 0x3F when unlocked
297 uint8_t unlockCount; // number of unlock steps taken
298 bool isUnlocked; // boolean version of lockedMask - read through PICO9918_UNLOCKED
299
300 volatile uint8_t restart;
301 volatile uint8_t flash;
302
303#if PICO9918_GPU_BUDGETED
304 /* Zero leaves an armed program to whoever else runs it. See pico9918_gpu_set_clock. */
305 uint32_t gpuIps;
306 uint32_t gpuSlice;
307 uint16_t gpuWp;
308#endif
309
310 /* palette writes are done in two stages too */
311 uint8_t palWriteStage;
312 uint8_t palWriteStage0Value;
313 uint8_t palDirty;
314
315 /* runtime base VDP selection (independent of F18A unlock) */
316 uint8_t vdpBase; /* PICO9918_BASE_TMS9918 or PICO9918_BASE_V9938 */
317
318#if PICO9918_BUILD_RUNTIME_CHIP
319 /* which chip this instance answers as, and the same thing as the bit per feature the
320 gates read. Both survive a reset - see pico9918_set_chip. Absent from a board's
321 build, which is one chip and gates nothing. */
322 uint8_t chip; /* pico9918_chip_t */
323 uint8_t features; /* PICO9918_FEAT_* - read through PICO9918_HAS */
324#endif
325
326 bool scanlineHasSprites;
327
328#if PICO9918_BUILD_LAYER_MASK
329 uint32_t suppress; /* PICO9918_SUPPRESS_* - read through PICO9918_DRAWS */
330#endif
331
332#if PICO9918_BUILD_STEP_CALLBACK
333 /* Survives a reset - a guest resetting the VDP must not disarm the debugger. */
334 pico9918_gpu_step_fn stepFn;
335 void* stepUserdata;
336#endif
337
338 uint32_t startTime;
339 uint32_t stopTime;
340 uint32_t currentTime;
341
342 struct
343 {
344 uint16_t y; /* raw scanline */
345 uint16_t y1; /* T1 layer Y after scroll (= y when locked or scroll=0) */
346 uint16_t y2; /* T2 layer Y after scroll (= y when T2 disabled or locked) */
347 bool swapY1Page; /* T1 name-table page swap flag */
348 bool swapY2Page; /* T2 name-table page swap flag */
349 uint8_t* pixels; /* pointer to caller-owned output pixel buffer */
350 } scanCtx;
351
352 uint8_t config[256];
353 bool configDirty;
354
355 /* the VDP state the configuration seeds - palette, sprite limit, scanlines - is
356 owed. Not palDirty above, which is the converted palette copy owing PRAM. */
357 bool configVdpDirty;
358
359 /* Aligned tile rendering optimization buffers - one extra tile for the scroll offset */
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]; // 1 bit per pixel: 0=T1, 1=T2
363 uint32_t __aligned(4) finalMask[SCANLINE_MASK_WORDS]; // 1 bit per pixel: 0=T1, 1=T2
364
365 /* Frame interrupt / status state. Not volatile, and touched from both the CPU-interface
366 handlers and the frame path; the critical section around updateInterrupts is what
367 orders them. */
368 bool frameInt; /* current /INT pin state (true = asserted) */
369 uint8_t frameStatus; /* SR0 shadow - the latch the frame path merges into */
370 bool frameDoneInt; /* interrupt already raised this frame? */
371
372#if !PICO9918_SINGLE_INSTANCE
373 /* The integration layer's host callbacks, per instance. Absent from the single-instance
374 struct entirely: that build keeps them in file statics, so a board's layout is what it
375 was and the MPU guard's offset assertion above still holds. */
376 struct
377 {
378 pico9918_config_applied_fn fn;
379 void* userdata;
380 } configApplied;
381
382 struct
383 {
384 pico9918_config_reload_fn fn;
385 void* userdata;
386 } configReload;
387
388 struct
389 {
390 pico9918_gpu_flash_fn fn;
391 void* userdata;
392 } gpuFlash;
393
394 struct
395 {
396 pico9918_gpu_config_save_fn fn;
397 void* userdata;
398 } gpuConfigSave;
399
400 struct
401 {
403 void* userdata;
404 } interrupt;
405#endif
406};
407
408/* Every platform, because two things rest on it: the public PICO9918_MAP_* offsets are
409 offsets from the instance pointer, and the MPU guard anchor (see guard() in gpu/gpu.c)
410 derives its page-aligned windows from vram's address. Restore the layout, never shift
411 either. */
412_Static_assert(offsetof(struct pico9918_s, vram) == 0,
413 "vram offset moved - PICO9918_MAP_* and the GPU MPU guard ranges both break");
414
415#if PICO9918_SINGLE_INSTANCE
416extern pico9918_t* const tms9918;
417#endif
418
419/* The line's own metadata (pico9918.c), on the impl surface because pico9918_frame.c reads all
420 * of it every active line and each public accessor is a real `bl` across the TU boundary.
421 *
422 * Shared between instances, like the palette LUT: pico9918_scan_line sets the mode from the
423 * instance's own registers on entry, and a mismatch there is what marks that LUT dirty.
424 *
425 * TRAP: this block must stay below the tms9918 declaration above. Placed before it, a
426 * single-instance build fails on an identifier that has not been declared yet, and the error
427 * names the inline body rather than the ordering.
428 */
429extern pico9918_mode_t pico9918_cached_mode;
430extern const uint8_t* pico9918_cached_line_source;
431
432/* Is this row 80 columns at one byte a pixel - twice as wide a line, on two pixel grids?
433 Without the tier it is a literal false, so every count and shift below it folds away.
434 Unlocked only, and that is not a restriction: all four things the tier buys are F18A features
435 that need the unlock anyway, so locked 80-column text keeps the packed line and its own
436 emitter. */
437#if PICO9918_TEXT80_8BPP
438#define TEXT80_WIDE_ROW \
439 (pico9918_cached_mode == TMS_MODE_TEXT80 && PICO9918_UNLOCKED(tms9918) && PICO9918_WIDE_T80(tms9918))
440#else
441#define TEXT80_WIDE_ROW false
442#endif
443
444PICO9918_INLINE pico9918_mode_t pico9918_display_mode_impl(PICO9918_INST_ONLY_ARG)
445{
446 (void)tms9918;
447 return pico9918_cached_mode;
448}
449
450PICO9918_INLINE uint32_t pico9918_line_bytes_impl(PICO9918_INST_ONLY_ARG)
451{
452 (void)tms9918;
453 return TEXT80_WIDE_ROW ? SCANLINE_BYTES_MAX : TMS9918_PIXELS_X;
454}
455
456PICO9918_INLINE const uint8_t* pico9918_line_source_impl(PICO9918_INST_ONLY_ARG)
457{
458 (void)tms9918;
459 return pico9918_cached_line_source;
460}
461
462/**
463 * \brief where a CPU-side VRAM access lands
464 *
465 * A part that drives DRAM multiplexes the address as a row and a column, and R1 bit 7
466 * says how wide each half is. At 16K it is seven bits of each and the address is used
467 * raw. At 4K it is six of each, driven into the seven that the 16K DRAMs on the board
468 * still want, which rotates the middle seven bits up one place and leaves the low six
469 * and the top one where they were.
470 *
471 * Only the CPU side. Display fetches take the address the tables name, because an F18A
472 * has no such bit at all and nothing drives a picture out of 4K on a 16K machine.
473 */
474PICO9918_INLINE_HOT uint32_t pico9918_cpu_vram_addr_impl(PICO9918_INST_ARG uint32_t addr)
475{
476 addr &= PICO9918_CPU_VRAM_MASK(tms9918);
477
478 if (PICO9918_VRAM_4K_CHIP(tms9918) && !(TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_RAM_16K))
479 {
480 /* static bits | shifted bits | rotated bit */
481 addr = (addr & 0x203f) | ((addr & 0x0fc0) << 1) | ((addr & 0x1000) >> 6);
482 }
483
484 return addr;
485}
486
487/**
488 * \brief set a register from the second byte of a host register write
489 *
490 * \p regSelect is that byte, not a register number: bit 7 set, the register in the low
491 * six. The locked-mask aliasing and the M4 rule are both defined on it, which is why it
492 * is not a pico9918_register_t. Carries the unlock sequence, the GPU arming writes, the
493 * palette rebuild, and the /INT reconcile on the three writes that can change the pin -
494 * R0, R1 and the unlock latch. Out of line, unlike its neighbours here: it is large, and
495 * the inline entry below is its only hot caller.
496 */
498void pico9918_write_reg_value_impl(PICO9918_INST_ARG uint8_t regSelect, uint8_t value);
499
500/**
501 * \brief write an address (mode = 1) to the tms9918
502 *
503 * data: the data (DB0 -> DB7) to send
504 */
505PICO9918_INLINE_HOT void pico9918_write_addr_impl(PICO9918_INST_ARG uint8_t data)
506{
507 if (tms9918->regWriteStage == 0)
508 {
509 /* first stage byte - either an address LSB or a register value */
510
511 tms9918->regWriteStage0Value = data;
512 tms9918->regWriteStage = 1;
513 }
514 else
515 {
516 /* second byte - either a register number or an address MSB */
517
518 if (data & 0x80) /* register */
519 {
520 if ((data & 0x40) == 0) // 64 registers, so only bit 6 is reserved
521 {
522 pico9918_write_reg_value_impl(PICO9918_INST data, tms9918->regWriteStage0Value);
523 }
524 }
525 else /* address */
526 {
527 tms9918->currentAddress = tms9918->regWriteStage0Value | ((data & 0x3f) << 8);
528 if ((data & 0x40) == 0)
529 {
530 tms9918->readAheadBuffer =
531 tms9918->vram.bytes[pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress)];
532 tms9918->currentAddress += (int8_t)TMS_REGISTER(tms9918, PICO9918_REG_VRAM_INC); // increment register
533 }
534 }
535 tms9918->regWriteStage = 0;
536 }
537}
538
539/**
540 * \brief is R#15's status-register select live?
541 *
542 * True on an F18A-unlocked device, and also on a locked V9938-base one - the V9938 has
543 * R#15 in its base register set, so status select is not an unlock privilege there.
544 *
545 * The second disjunct is dead today and folds away: only the unlock sequence widens
546 * `lockedMask` past 0x07, so a locked device cannot have had R#15 written whatever its
547 * base. It is written base-aware so that V9938 support swaps a base rather than a
548 * scattered condition.
549 */
551{
552 return PICO9918_UNLOCKED(tms9918) || (tms9918->vdpBase == PICO9918_BASE_V9938);
553}
554
555/**
556 * \brief THE single implementation of "a status register was just read" - shared by the
557 * public read (pico9918_read_status_impl) and the CPU-interface read reconcile
558 * (pico9918_status_read_reconcile_impl); see the entry shims for which side
559 * effects each one owns.
560 *
561 * readReg: the selected status register (0..15)
562 * readVal: the value the reader received
563 *
564 * SR0: clear only the flags that were actually seen set, out of the SR0 shadow
565 * the frame path latches into. A seen 5S additionally restores the sprite
566 * number field to 31 (the reset value) - the flag and its ID clear together.
567 * SR1: bit 0 is the R#19 line-interrupt flag, clear-on-read.
568 */
569/* Defined below, and needed here: each read clears one of the two /INT sources, and the
570 pin has to come back from the one that is left rather than be cleared. */
572
573/* What the desktop PICO9918_HOST_SET_INT expands to, so it precedes every pin-write site. */
575
576PICO9918_INLINE_HOT void pico9918_status_read_core(PICO9918_INST_ARG uint8_t readReg, uint8_t readVal)
577{
578 if (readReg == PICO9918_SR_STATUS)
579 {
581 tms9918->frameStatus &= ~readVal; // Clear only the flags that were set
582 if (readVal & PICO9918_SR0_5S) // Was 5th Sprite flag set?
583 tms9918->frameStatus |= 0x1f; // Set sprite number to 31
584 TMS_STATUS(tms9918, PICO9918_SR_STATUS) = tms9918->frameStatus;
585 if ((readVal & PICO9918_SR0_INT) == 0) return; // frame source untouched
586 }
587 else if (readReg == PICO9918_SR_IDENT && (readVal & PICO9918_SR1_HF))
588 {
589 TMS_STATUS(tms9918, PICO9918_SR_IDENT) &= (uint8_t)~PICO9918_SR1_HF;
590 }
591 else
592 {
593 return;
594 }
595
597}
598
599/**
600 * \brief CPU-interface entry: the host's read-ahead already handed the CPU a value, so
601 * this only applies the read's side effects. The host supplies both the value the
602 * CPU saw and the register it came from (zero while status select is inactive).
603 */
604PICO9918_INLINE_HOT void pico9918_status_read_reconcile_impl(PICO9918_INST_ARG uint8_t readReg, uint8_t readVal)
605{
606 tms9918->regWriteStage = 0;
607
609
610 pico9918_status_read_core(PICO9918_INST readReg, readVal);
611}
612
613/**
614 * \brief read from the status register
615 *
616 * Emulator-facing entry: fetches the value itself, then runs the same core. It
617 * additionally resets the data-port palette staging, which the CPU-interface
618 * path never did (that port is written through a different host seam).
619 *
620 * PICO9918_INLINE, not PICO9918_INLINE_HOT. The core it calls reaches
621 * PICO9918_HOST_SET_INT, which on Pico is an SDK `static inline` gpio_put, and a
622 * non-static inline may not call a static one - which is why every entry on this
623 * surface is static. Its only caller is pico9918_read_status() in pico9918.c, the
624 * external definition consumers link against.
625 *
626 * TWO BEHAVIOURS worth stating outright, because this is a public entry point and
627 * no gate makes either visible:
628 *
629 * 1. Reading SR0 does NOT scrub the sprite-number field. It clears only the
630 * (INT|5S|COL) flags actually seen set, restoring the number to 31 only when 5S
631 * was among them. That follows the documented register layout: F is
632 * clear-on-read, but SP4-SP0 is a data field, not a flag.
633 * 2. Reading either status register clears its own /INT source and then RE-DERIVES the
634 * pin from what is left, so the line is released only when the other source is not
635 * asserting either, and it is written only when the derived state differs from the
636 * shadow. A host running its own /INT plumbing alongside this call will see an
637 * unrequested pin write; such a host should drive the line from the library's state
638 * rather than in parallel with it.
639 */
641{
642 tms9918->regWriteStage = 0;
643
644 tms9918->palWriteStage = 0;
645 TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL) &=
646 (uint8_t)~PICO9918_R47_DATA_PORT; // reset data port palette mode
647
648 const uint8_t readReg = TMS_REGISTER(tms9918, PICO9918_REG_STATUS_SELECT) & PICO9918_R15_STATUS_NUM;
649 const uint8_t readVal = TMS_STATUS(tms9918, readReg);
650
651 pico9918_status_read_core(PICO9918_INST readReg, readVal);
652
653 return readVal;
654}
655
656/** \brief read from the status register without resetting it */
658{
659 return TMS_STATUS(tms9918, PICO9918_SR_STATUS);
660}
661
662/**
663 * \brief write data (mode = 0) to the tms9918
664 *
665 * data: the data (DB0 -> DB7) to send
666 */
667PICO9918_INLINE_HOT void pico9918_write_data_impl(PICO9918_INST_ARG uint8_t data)
668{
669 if (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL) &
670 PICO9918_R47_DATA_PORT) // data port is in palette mode
671 {
672 if (tms9918->palWriteStage == 0)
673 {
674 tms9918->palWriteStage0Value = data & 0x0f;
675 ++tms9918->palWriteStage;
676 }
677 else
678 {
679 tms9918->palWriteStage = 0;
680
681 // this looks backwards because ARM is little-endian, TMS9900 is big-endian.
682 tms9918->vram.map.pram[TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL) & PICO9918_R47_INDEX] =
683 (tms9918->palWriteStage0Value) | (data << 8);
684 tms9918->palDirty = 1;
685
686 // reset data port palette mode
687 if (TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL) & PICO9918_R47_AUTO_INC)
688 {
689 ++TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL);
690 }
691 else
692 {
693 TMS_REGISTER(tms9918, PICO9918_REG_PALETTE_CONTROL) &= (uint8_t)~PICO9918_R47_DATA_PORT;
694 }
695 }
696 }
697 else
698 {
699 tms9918->regWriteStage = 0;
700 tms9918->readAheadBuffer = data;
701 tms9918->vram.bytes[pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress)] = data;
702 tms9918->currentAddress += (int8_t)TMS_REGISTER(tms9918, PICO9918_REG_VRAM_INC); // increment register
703 }
704}
705
706
707/** \brief read data (mode = 0) from the tms9918 */
709{
710 tms9918->regWriteStage = 0;
711 uint8_t currentValue = tms9918->readAheadBuffer;
712 tms9918->readAheadBuffer =
713 tms9918->vram.bytes[pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress)];
714 tms9918->currentAddress += (int8_t)TMS_REGISTER(tms9918, PICO9918_REG_VRAM_INC); // increment register
715 return currentValue;
716}
717
718/** \brief refill the read-ahead buffer from the current address and return the new value */
720{
721 tms9918->regWriteStage = 0;
722 tms9918->readAheadBuffer =
723 tms9918->vram.bytes[pico9918_cpu_vram_addr_impl(PICO9918_INST tms9918->currentAddress)];
724 tms9918->currentAddress += (int8_t)TMS_REGISTER(tms9918, PICO9918_REG_VRAM_INC); // increment register
725 return tms9918->readAheadBuffer;
726}
727
728/** \brief return the buffered value without reading VRAM or advancing the address */
730{
731 return tms9918->readAheadBuffer;
732}
733
734/**
735 * \brief whether /INT should be asserted
736 *
737 * Two independent sources, as on the F18A: the end-of-frame flag under R1's enable, and
738 * the scanline flag under R0's. Neither gates the other - a program that wants only the
739 * scanline interrupt turns R1's off - so the horizontal source cannot be folded into SR0.
740 *
741 * Neither is gated on being unlocked, which is the hardware's own shape. A device that
742 * has never unlocked cannot arm the scanline source anyway: its locked mask sends a write
743 * to register 19 to R3, R19 stays 0, and the line compare treats 0 as off. One that
744 * unlocked and relocked keeps what it armed, and keeps interrupting on it.
745 */
747{
748 return ((TMS_REGISTER(tms9918, TMS_REG_1) & TMS_R1_INT_ENABLE) &&
749 (TMS_STATUS(tms9918, PICO9918_SR_STATUS) & PICO9918_SR0_INT)) ||
750 ((TMS_STATUS(tms9918, PICO9918_SR_IDENT) & PICO9918_SR1_HF) &&
751 (TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_INT_SCANLINE));
752}
753
754/** \brief raise the interrupt flag in SR0 and the frame shadow */
756{
757 tms9918->frameStatus |= PICO9918_SR0_INT;
758 TMS_STATUS(tms9918, PICO9918_SR_STATUS) |= PICO9918_SR0_INT;
759}
760
761/**
762 * \brief set status flag
763 *
764 * Writes the SR0 shadow too. SR0 has exactly one authoritative value: the frame
765 * path latches into the shadow and publishes it, so a setter that moved only the
766 * register would leave the next latch merging into a stale value.
767 */
768PICO9918_INLINE_HOT void pico9918_set_status_impl(PICO9918_INST_ARG uint8_t status)
769{
770 tms9918->frameStatus = status;
771 TMS_STATUS(tms9918, PICO9918_SR_STATUS) = status;
772}
773
774/* Frame interrupt / status state accessors
775 * ----------------------------------------
776 * frameInt / frameStatus / frameDoneInt are NOT exposed as extern variables: both
777 * the CPU-interface handlers and the frame path mutate them, so going through the
778 * Impl layer keeps ownership in one place.
779 */
780
781/* the SR0 latch the frame path merges into */
782PICO9918_INLINE uint8_t pico9918_frame_status_impl(PICO9918_INST_ONLY_ARG)
783{
784 return tms9918->frameStatus;
785}
786
787/* current /INT pin state */
788PICO9918_INLINE bool pico9918_frame_int_impl(PICO9918_INST_ONLY_ARG)
789{
790 return tms9918->frameInt;
791}
792
793/* has an interrupt been raised this frame? */
794PICO9918_INLINE bool pico9918_frame_done_int_impl(PICO9918_INST_ONLY_ARG)
795{
796 return tms9918->frameDoneInt;
797}
798
799PICO9918_INLINE void pico9918_set_frame_done_int_impl(PICO9918_INST_ARG bool done)
800{
801 tms9918->frameDoneInt = done;
802}
803
804/* Frame counter and dropped-frame accounting
805 * ------------------------------------------
806 * Defined in pico9918_frame.c, which owns every write. The host only READS them,
807 * which is what makes plain module globals behind inline accessors the right shape
808 * here, rather than the instance fields the interrupt/status state uses: that state
809 * is mutated by BOTH the bus-interface handlers and the frame path, so ownership has
810 * to be forced through one place. Nothing outside this module writes these, so there
811 * is no such split to arbitrate, and `pico9918_palette_lut` below is the precedent
812 * for a library-owned global on the privileged Impl surface.
813 *
814 * The choice is measured, not stylistic. The scanline path reads the count three
815 * times per border scanline and must not gain work. As instance fields the offsets
816 * land past the 64KB vram union, so each read needs a literal-pool offset AND the
817 * instance base - two instructions more in the scanline path and two in the GPIO IRQ
818 * handler, verified by building it both ways. As globals the pool holds the address
819 * directly.
820 *
821 * Not volatile and not guarded: all writes are on core 1 (the frame path and the
822 * reset IRQ, which is also core 1), and every reader is on core 1 too.
823 */
824extern int pico9918_frame_count;
825extern int pico9918_dropped_frames_count;
826
827#if PICO9918_DIAG_GPU_FRAME_COUNTER
828/* GPU frames observed at end of frame, read by the diag overlay's GPU-frames row -
829 which is why it is on this surface rather than a file static: the frame module
830 writes it and the overlay reads it directly. */
831extern uint32_t pico9918_gpu_frame_count;
832#endif
833
834/* Read-only from the host's point of view: the frame module owns the increment and
835 the reset, and advances the global directly - the end-of-frame sequence is
836 internal to it. The host's scanline reads this as the splash animation clock and
837 the startup-diagnostics threshold. */
838PICO9918_INLINE int pico9918_frame_count_impl(PICO9918_INST_ONLY_ARG)
839{
840 return pico9918_frame_count;
841}
842
843/* console-reset entry for the frame counter. Deliberately does NOT clear the
844 dropped-frame window, and does NOT clear pico9918_valid_writes: the dropped-frame
845 count is a rolling 16-frame average that is allowed to span a reset, and
846 validWrites is a once-per-run latch whose consumers (the splash hand-off, the
847 startup diagnostics screen) must not be re-armed by a console reset. */
848PICO9918_INLINE void pico9918_frame_reset_count_impl(PICO9918_INST_ONLY_ARG)
849{
850 pico9918_frame_count = 0;
851#if PICO9918_DIAG_GPU_FRAME_COUNTER
852 pico9918_gpu_frame_count = 0;
853#endif
854}
855
856/* Vertical geometry and the display-enable latch
857 * ----------------------------------------------
858 * Defined in pico9918_frame.c alongside the frame counter, for consistency of
859 * ownership. Unlike the frame counter, globals are NOT cheaper here: MEASURED both
860 * ways, instance fields make the scanline path two instructions SHORTER, and
861 * inspecting the two disassemblies that difference is register-allocation churn in
862 * the prologue rather than address arithmetic at the read sites. A codegen
863 * coin-flip, not a structural cost - so do not assume either form is free without
864 * looking.
865 *
866 * Globals are used because they match the frame counter these sit beside: one module
867 * owns one kind of state one way.
868 *
869 * Not volatile and not guarded: the frame module is the only writer and the host's
870 * scanline the only reader, both on core 1. */
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;
876
877/* The border colour word the border-fill DMA instance reads. Declared here only so
878 pico9918.c's initLookups() can point the fill
879 instance at it: the frame module owns the storage, the placement and every write,
880 and the init site owns every fill instance. Not an accessor, because there is
881 nothing to accessorise - the one out-of-module user needs its ADDRESS, once, at
882 init. Placement is .scratch_y; see the definition. */
883extern uint32_t pico9918_border_bg;
884
885/* Active VDP display lines, and the top border offset in virtual lines. Written
886 once per frame by pico9918_frame_geometry; read by the host's scanline for its
887 border test and for the overlay geometry it forwards.
888
889 vBorder is UNSIGNED, and that is load-bearing rather than incidental - see the
890 narrowing note on pico9918_frame_geometry_t in pico9918_frame.h. */
891PICO9918_INLINE int pico9918_v_pixels_impl(PICO9918_INST_ONLY_ARG)
892{
893 return pico9918_v_pixels;
894}
895
896PICO9918_INLINE uint32_t pico9918_v_border_impl(PICO9918_INST_ONLY_ARG)
897{
898 return pico9918_v_border;
899}
900
901/* Has the VDP display been enabled at all since power-on or the last console
902 reset? Latched by pico9918_frame_end on the first frame that sees R1 bit 6 set,
903 and never cleared - the splash hand-off and the startup diagnostics screen both
904 hang off it and are once-per-run. */
905PICO9918_INLINE bool pico9918_valid_writes_impl(PICO9918_INST_ONLY_ARG)
906{
907 return pico9918_valid_writes;
908}
909
910/* Dropped frames over the trailing 16-frame window.
911 *
912 * RETAINED with no in-tree caller, deliberately: the frame module owns the counter
913 * and runs the diagnostics refresh, so it reads its own global directly. The
914 * accessor stays because the counter is a documented part of what this module
915 * accounts for, and an integrator driving frames has no other way to read it.
916 * Compare pico9918_frame_count_impl, which the host does read. */
917PICO9918_INLINE int pico9918_dropped_frames_impl(PICO9918_INST_ONLY_ARG)
918{
919 return pico9918_dropped_frames_count;
920}
921
922/* Function: pico9918_frame_map_line_impl
923 * ---------------------------------------
924 * the interlace field mapping: which VDP line a given display line renders.
925 *
926 * y: the line WITHIN THE DISPLAY REGION, i.e. after the top border has been
927 * subtracted. Not the raw VGA-encoded line: the scanline path applies the
928 * mapping only on its active arm, where y is already border-relative, and the
929 * mapping must see the same value it saw before the move.
930 * field: the field number, which the caller has already separated out of the raw y's
931 * bit 12 - it needs it on the border arm too.
932 * interlaced / fieldOrder: the mode's interlace parameters.
933 *
934 * Applied ONLY under interlace AND double-rows; otherwise the VDP line is y unchanged
935 * and the field number is DISCARDED. The two fields interleave the doubled VDP line
936 * space, and fieldOrder selects which field takes the even lines.
937 *
938 * An inline function rather than four lines inside pico9918_frame_scanline, and the
939 * reason is the gate, not tidiness. The golden frame surface's mapping group calls
940 * THIS, so it compares the shipping code against its own independent model. The
941 * scanline as a whole cannot serve that purpose - it does DMA and renders a line.
942 * This is the smallest thing that can.
943 *
944 * PICO9918_INLINE, so its one library caller inlines it and the per-scanline path is
945 * unchanged.
946 *
947 * The `y * 2 + (field ^ fieldOrder)` FUSION IS DELIBERATE and must not be decomposed
948 * into explicit even/odd cases. The harness's independent reference does exactly that
949 * decomposition, and its whole value is that it does not share this algebra. */
950PICO9918_INLINE uint16_t pico9918_frame_map_line_impl(PICO9918_INST_ARG uint16_t y, uint8_t field,
951 bool interlaced, uint8_t fieldOrder)
952{
953 uint16_t tmsY = y;
954 if (interlaced && (TMS_REGISTER(tms9918, TMS_REG_0) & TMS_R0_DOUBLE_ROWS))
955 tmsY = y * 2 + (field ^ fieldOrder);
956 return tmsY;
957}
958
959/**
960 * \brief recompute the interrupt state and, only if it changed, drive the pin.
961 *
962 * The single place the /INT pin is asserted or released from a recomputation.
963 * Both the post-write reconcile and updateInterrupts' tail need exactly this.
964 */
966{
968 if (newInt != tms9918->frameInt)
969 {
970 tms9918->frameInt = newInt;
971 PICO9918_HOST_SET_INT(newInt);
972 }
973}
974
975/**
976 * \brief bring /INT into agreement after a write that can change the predicate. An R1
977 * interrupt enable/disable must take effect at once - updateInterrupts only runs on
978 * active scanlines and at the trigger line, so without this a border-time R1 mask would
979 * leave /INT stuck asserted.
980 *
981 * Called from pico9918_write_reg_value_impl, which is the one place that knows which
982 * register a write actually landed on. A staged first byte and an address set cannot
983 * reach the predicate, so neither pays for this.
984 */
989
990/**
991 * \brief console-reset entry for the interrupt/status state. frameDoneInt resets to
992 * TRUE, not false: it suppresses the end-of-frame fallback interrupt until the
993 * next frame starts cleanly.
994 *
995 * Does NOT drive the pin - the reset handler's tail order is load-bearing (host
996 * read-ahead push and other non-VDP work run between this and the pin write), so
997 * the caller issues PICO9918_HOST_SET_INT at its own point.
998 */
1000{
1002 tms9918->frameInt = false;
1003 tms9918->frameDoneInt = true;
1004}
1005
1006
1007/* Palette LUT (pico9918_palette.c)
1008 * -----------------------------------
1009 * 256 entries consumed by PICO9918_EXPAND_INDEXED. Regular SRAM, not a scratch
1010 * bank - placement preserved from the firmware.
1011 *
1012 * They are here, on the library-internal impl surface, because that is where
1013 * library-internal state belongs and they have in-library consumers: the frame module's
1014 * scanline reads the LUT and drives both rebuild triggers, and the golden harness drives
1015 * the same rebuild decision directly (test/golden/golden.c, the post-palette scene
1016 * surface - which is what makes palDirty observable at all).
1017 *
1018 * HOST CODE MAY REFERENCE THE LUT FOR EXACTLY ONE THING: pointing an RP2040's
1019 * interpolators at it, which PICO9918_EXPAND_INIT does. They are per-core state and the
1020 * library does not know which core will expand lines, so the call belongs to whoever
1021 * starts that core - pico9918's src/palette.c. A library entry point for it would put a
1022 * boot-time call in this RAM-resident TU to save a host two lines, and the scanline path
1023 * pays for what lands here. */
1024extern PICO9918_PALETTE_LUT_T pico9918_palette_lut[256];
1025
1026#if !PICO9918_SINGLE_INSTANCE
1027extern const pico9918_t* pico9918_palette_owner;
1028#endif
1029
1030void pico9918_palette_regenerate(PICO9918_INST_ONLY_ARG);
1031
1032/**
1033 * \brief apply the config block's VDP-side effects: registers 50 and 30, the
1034 * palette unpack, and the derived PICO9918_CONF_DIAG summary byte
1035 *
1036 * A settings block is a PICO9918 thing, so the effects land only on a personality that
1037 * has the config port. On an F18A those registers and that palette are the guest's
1038 * alone, and a block read from host storage must not touch them.
1039 *
1040 * What it writes is a power-on default, not an owner: it runs when the block is loaded
1041 * and after a reset has cleared the register file, and a later write to register 50 or
1042 * 30 stands on every personality.
1043 *
1044 * Host-side effects stay with the host.
1045 *
1046 * Internal: it leaves configDirty set, so a host reaching it directly gets the block
1047 * applied again at the next end of frame. Hosts want apply_now or schedule_apply.
1048 */
1050
1051#if PICO9918_BUILD_DEBUG_API
1052/* The mode cache is refreshed on entry to a scanline and read before one by
1053 * pico9918_frame.c, so a debug register write landing between the two would leave
1054 * pico9918_display_mode answering with the old geometry for a line. In pico9918.c
1055 * because the cache and the decode are both file-static there. */
1057#endif
1058
1059/* Does the LUT need rebuilding?
1060 *
1061 * SR2 bit 7 is F18A-SPECIFIC (the GPU busy flag): the GPU may have written
1062 * palette registers behind our back, so a rebuild is forced while it runs.
1063 * V9938's S#2 bit 7 is TR, which idles at 1 - under the V9938 base this must
1064 * be gated per base (step 6) or the palette would rebuild every scanline. */
1065PICO9918_INLINE bool pico9918_palette_dirty(PICO9918_INST_ONLY_ARG)
1066{
1067#if !PICO9918_SINGLE_INSTANCE
1068 /* the converted palette is one module-level LUT, so it belongs to whoever rebuilt it
1069 last: a second instance in the same mode would otherwise draw the first one's colours */
1070 if (pico9918_palette_owner != tms9918) return true;
1071#endif
1072#ifdef PICO_BUILD
1073 return tms9918->palDirty || (TMS_STATUS(tms9918, PICO9918_SR_GPU) & 0x80);
1074#else
1075 /* No MPU palette guard off a board (gpu.h), so a GPU write announces itself nowhere. */
1076 return true;
1077#endif
1078}
#define PICO9918_SR0_5S
more sprites on a line than the limit allows
Definition pico9918.h:290
#define PICO9918_R47_DATA_PORT
register 47 bits: the palette data port
Definition pico9918.h:351
#define TMS_R1_RAM_16K
register 1 bits: VRAM size, blanking, interrupt, mode and sprite size
Definition pico9918.h:311
#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
void(* pico9918_interrupt_fn)(pico9918_t *instance, bool active, void *userdata)
the host's /INT hook: called whenever the library drives the line
Definition pico9918.h:563
#define PICO9918_R47_INDEX
first palette index to write
Definition pico9918.h:353
#define PICO9918_R15_STATUS_NUM
which status register S1 reads back
Definition pico9918.h:388
#define PICO9918_SR0_INT
status register 0 bits.
Definition pico9918.h:289
@ PICO9918_REG_VRAM_INC
signed VRAM address increment per access
Definition pico9918.h:249
@ PICO9918_REG_STATUS_SELECT
which status register S1 reads back, and the counter controls
Definition pico9918.h:233
@ PICO9918_REG_PALETTE_CONTROL
palette data port mode, auto-increment and index
Definition pico9918.h:248
@ PICO9918_SR_STATUS
the TMS9918A status: interrupt, 5th sprite, collision, sprite number
Definition pico9918.h:270
@ PICO9918_SR_IDENT
chip identity, blanking, and the scanline interrupt flag
Definition pico9918.h:271
@ PICO9918_SR_GPU
GPU running and its status byte.
Definition pico9918.h:272
#define PICO9918_R47_AUTO_INC
step the palette index after each entry
Definition pico9918.h:352
#define PICO9918_INST_ONLY_ARG
declare the instance as the only parameter
Definition pico9918.h:84
#define TMS9918_PIXELS_X
active display width, every mode
Definition pico9918.h:390
#define TMS_R0_DOUBLE_ROWS
PICO9918 only: twice the rows, drawn interlaced.
Definition pico9918.h:307
#define TMS_R0_INT_SCANLINE
assert /INT when the raster reaches the line in R19.
Definition pico9918.h:308
#define PICO9918_SR1_HF
status register 1 bits.
Definition pico9918.h:295
#define TMS_R1_INT_ENABLE
assert /INT at end of frame
Definition pico9918.h:315
#define PICO9918_SR0_COLLISION
two sprites overlapped on an opaque pixel
Definition pico9918.h:291
#define PICO9918_INST
pass the instance ahead of other arguments
Definition pico9918.h:85
pico9918_mode_t
the display modes the VDP can be in, TMS9918A modes and F18A alike
Definition pico9918.h:118
#define PICO9918_INTERNAL
cross-TU linkage for what is not public API, so a DLL or wasm build exports none of it
Definition pico9918.h:54
#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
Definition pico9918.c:3433
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.
Definition pico9918.c:3594
void pico9918_interrupt_dispatch(pico9918_t *tms9918, bool active)
see impl.
Definition pico9918.c:149
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
pico9918-core - Platform Abstraction