pico9918-core
1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
tms9900.h
Go to the documentation of this file.
1
/**
2
* \file
3
* \brief pico9918-core - TMS9900 CPU interpreter (portable C)
4
*
5
* Copyright (c) 2026 Troy Schrapel
6
*
7
* This code is licensed under the MIT license
8
*
9
* https://github.com/visrealm/pico9918-core
10
*
11
* This is a full reimplementation of JasonACT's RP2040 thumb assembly core
12
* found in thumb9900_m0.S / thumb9900_m33.S. It is not cycle-accurate and
13
* intentionally mirrors the status-flag encoding used in the assembly
14
* (bits: LG=0x80, AG=0x40, EQ=0x20, C=0x10, OV=0x08, P=0x04) so existing
15
* GPU glue can remain unchanged. run9900_c takes a Tms9900Cpu the caller has
16
* initialised with tms9900_init; gpu.c adapts it to the assembly core's
17
* four-argument run9900 signature. PICO9918_GPU_C_CORE selects between them.
18
*/
19
20
#pragma once
21
22
#include <stdbool.h>
23
#include <stdint.h>
24
25
#ifdef __cplusplus
26
extern
"C"
27
{
28
#endif
29
30
/* Status flag bits (mirrors the assembly core's layout) */
31
#define TMS_ST_LGT 0x80
/* Logic greater-than */
32
#define TMS_ST_AGT 0x40
/* Arithmetic greater-than */
33
#define TMS_ST_EQ 0x20
/* Equal */
34
#define TMS_ST_C 0x10
/* Carry */
35
#define TMS_ST_OV 0x08
/* Overflow */
36
#define TMS_ST_P 0x04
/* Parity (odd) */
37
38
/*
39
* Off-target, nothing watches memory for the library the way a Pico's MPU does, so
40
* a write to the GPU's DMA port is not seen until the run returns - which is far
41
* too late for a program that triggers a transfer and then keeps going. The
42
* interpreter reports writes instead. On a Pico the hardware does it and none of
43
* this is compiled.
44
*/
45
#if !defined(PICO_BUILD)
46
#define TMS9900_WATCH_WRITES 1
47
#endif
48
49
/* TMS9900_STEP_HOOK is the build's to define, not this header's: it is there for
50
pico9918_debug.h's stepping entry and nothing else, and it widens this struct, so a
51
build without a debugger must not carry it. src/CMakeLists.txt sets it. */
52
53
typedef
struct
Tms9900Cpu
54
{
55
uint8_t* mem;
/* Pointer to memory backing the CPU */
56
uint8_t* regx38;
/* Pointer to the GPU control byte (TMS register 0x38) */
57
uint32_t pc;
/* Program counter (uint32_t to handle WP=0xFFFE overflow) */
58
uint16_t wp;
/* Workspace pointer */
59
uint16_t st;
/* Status register (flag layout matches assembly core) */
60
61
/* Decode memory the way an F18A does: a few small windows above 16KB, each mirrored
62
across its 4KB and most of the space absent. False, which is what tms9900_init
63
leaves, is the PICO9918's own map - 64KB of memory, and what the assembly cores
64
see. Only a build that can answer as a plain F18A ever sets it. */
65
bool
f18aMemory;
66
#if defined(TMS9900_WATCH_WRITES)
67
/*
68
* Called after a write to an address the running program chose, or null.
69
*
70
* LIMITATION: workspace-relative writes are not reported - register stores and
71
* the context saves BLWP and XOP make. Those land at WP+n, and WP is >FFFE, so
72
* they can only reach a watched address if a program moves its workspace onto
73
* one with LWPI. Nothing does. Widening this means routing set_reg and the
74
* context saves through the same watch, which costs the CPU core's hot path a
75
* call per register write.
76
*/
77
void (*onWrite)(uint8_t* mem, uint32_t addr);
78
79
/* Addresses the watcher wants: it is called only where
80
(addr & onWriteMask) == onWriteMatch, tested on the decoded address. Both
81
zero, which tms9900_init leaves, is every address - a watcher that cares
82
about a few narrows it here rather than being called to say no. */
83
uint32_t onWriteMask;
84
uint32_t onWriteMatch;
85
#endif
86
#if defined(TMS9900_STEP_HOOK)
87
/*
88
* Called before each instruction is fetched, with the PC it will come from, or
89
* null. Returning false stops the run there and is indistinguishable to the core
90
* from an exhausted budget: the PC is kept and outOfBudget is set, so the caller
91
* resumes where it left off.
92
*/
93
bool (*onStep)(
struct
Tms9900Cpu
* cpu);
94
95
/* Handed back to onStep untouched. The glue puts this run's context here. */
96
void
* onStepData;
97
#endif
98
}
Tms9900Cpu
;
99
100
/* Initialize a CPU context */
101
void
tms9900_init(
Tms9900Cpu
* cpu, uint8_t* mem, uint8_t* regx38, uint16_t pc, uint16_t wp);
102
103
/* Execute until regx38 bit0 is cleared or an IDLE occurs. Returns final PC. */
104
uint16_t run9900_c(
Tms9900Cpu
* cpu);
105
106
/* The same, giving up after at most `budget` instructions - zero means no limit.
107
Returns the PC either way, which is what makes it resumable: a host with one
108
thread interleaves this with its renderer, and a program that waits on the
109
raster gets a raster that moves.
110
111
A budget that runs out and a program that parks on an IDLE or a self-jump both
112
leave the run flag set, so the flag cannot tell them apart. `outOfBudget`, if
113
given, does: only that one has work still to do.
114
115
The PC is not the whole of what a resume needs. A budget expires BETWEEN
116
instructions, which includes between a compare and the jump that reads what it
117
set, so `cpu->st` has to come back too: build the next call's Tms9900Cpu from the
118
one this returned rather than from tms9900_init, which starts the status at zero
119
and would have that jump decide on flags nothing set. */
120
uint16_t run9900_budget_c(
Tms9900Cpu
* cpu, uint32_t budget,
bool
* outOfBudget);
121
122
#ifdef __cplusplus
123
}
124
#endif
Tms9900Cpu
Definition
tms9900.h:54
src
gpu
tms9900.h
Generated by
1.9.8