pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
platform_std.h
Go to the documentation of this file.
1/**
2 * \file
3 * \brief pico9918-core - Platform Abstraction (portable C)
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 * Included by impl/platform.h when PICO_BUILD is not defined.
12 * Do not include directly.
13 *
14 * Every macro here is overridable: a host may #define any PICO9918_* symbol
15 * before including pico9918.h to substitute its own (a real lock, a BGRA
16 * pixel type, a different fill). Defaults are guarded accordingly.
17 */
18
19#pragma once
20
21#include <stdint.h>
22#include <string.h>
23
24/* No fast SRAM banks off-target. */
25#define PICO9918_SECTION_SCRATCH_X(name)
26#define PICO9918_SECTION_SCRATCH_Y(name)
27
28/* No crt0 zero-fill to opt out of either - the declarator stands as written. */
29#define PICO9918_UNINITIALIZED(decl) decl
30
31/* Nothing is copied to RAM off-target, so nothing has to be held back from it. */
32#define PICO9918_IN_FLASH_FUNC(fn) fn
33
34
35/*
36 * Tier-1 host op: drive the /INT pin.
37 *
38 * No pin off-target. A host that wants the edge (an emulator raising a CPU IRQ
39 * line) pre-defines this before including the library - the same override rule
40 * as every other macro here. pico9918_interrupt_status() remains available for
41 * hosts that prefer to poll.
42 *
43 * The Pico platform header instead requires PICO9918_INT_GPIO and honours an
44 * optional PICO9918_INT_ACTIVE_HIGH; neither has meaning off-target.
45 */
46#ifndef PICO9918_HOST_SET_INT
47#define PICO9918_HOST_SET_INT(active) ((void)(active))
48#endif
49
50
51
52
53/*
54 * 32-bit fill instances
55 *
56 * Mirrors the Pico DMA instances (see platform/pico) with independent
57 * src/count slots, so two fills can be configured at once. Fills execute
58 * synchronously, which makes WAIT a no-op.
59 */
60typedef struct
61{
62 const void* src;
63 unsigned count;
65
66extern pico9918_fill32_t pico9918_fill_border;
67
68#define PICO9918_FILL_BORDER pico9918_fill_border
69
70#define PICO9918_FILL32_INIT(inst, srcPtr) \
71 do \
72 { \
73 (inst).src = (srcPtr); \
74 (inst).count = 0; \
75 } while (0)
76#define PICO9918_FILL32_SET_COUNT(inst, n) \
77 do \
78 { \
79 (inst).count = (unsigned)(n); \
80 } while (0)
81
82#define PICO9918_FILL32_TRIGGER(inst, dstPtr) \
83 do \
84 { \
85 uint32_t _v = *(const uint32_t*)(inst).src; \
86 uint32_t* _d = (uint32_t*)(dstPtr); \
87 for (unsigned _i = 0; _i < (inst).count; ++_i) _d[_i] = _v; \
88 } while (0)
89
90#define PICO9918_FILL32_WAIT(inst) ((void)0) /* TRIGGER is synchronous */
91
92/* No DMA hardware to reserve off-target - the instances above are plain structs. */
93#define PICO9918_DMA_CLAIM() ((void)0)
94
95/*
96 * Two more fill instances and a copy channel, matching platform/pico. Off-target the
97 * fills are plain structs and the copy is memcpy, so the width hint is ignored: it
98 * exists only so the Pico side can pick its transfer size from source alignment.
99 */
100extern pico9918_fill32_t pico9918_fill_masks;
101extern pico9918_fill32_t pico9918_fill_line;
102
103#define PICO9918_FILL_MASKS pico9918_fill_masks
104#define PICO9918_FILL_LINE pico9918_fill_line
105
106/* `shift` carries the transfer width, because the count TRIGGER takes is a transfer
107 count on the Pico side, not a byte count: word-wide runs move four bytes each. */
108typedef struct
109{
110 const void* src;
111 void* dst;
112 unsigned shift;
114
115extern pico9918_copy32_t pico9918_copy;
116
117#define PICO9918_COPY pico9918_copy
118
119/* No configs to cache off-target. */
120#define PICO9918_COPY_STATE()
121#define PICO9918_COPY_INIT(inst) \
122 do \
123 { \
124 (inst).shift = 0u; \
125 } while (0)
126#define PICO9918_COPY_SET_WIDTH(inst, wordAligned) \
127 do \
128 { \
129 (inst).shift = (wordAligned) ? 2u : 0u; \
130 } while (0)
131#define PICO9918_COPY_SET_SRC(inst, srcPtr) \
132 do \
133 { \
134 (inst).src = (srcPtr); \
135 } while (0)
136#define PICO9918_COPY_SET_DST(inst, dstPtr) \
137 do \
138 { \
139 (inst).dst = (dstPtr); \
140 } while (0)
141#define PICO9918_COPY_TRIGGER(inst, n) memcpy((inst).dst, (inst).src, (size_t)(n) << (inst).shift)
142#define PICO9918_COPY_WAIT(inst) ((void)0) /* TRIGGER is synchronous */
143
144/* Plain division off-target; the Pico arm goes through the SDK's divider. */
145#define PICO9918_DIVMOD_U32(n, d, q, r) \
146 do \
147 { \
148 uint32_t _n = (uint32_t)(n), _d = (uint32_t)(d); \
149 (q) = _n / _d; \
150 (r) = _n % _d; \
151 } while (0)
152
153
154/*
155 * Pixel output policy - the board's, off-target: BGR12 in a 16-bit pixel, red's LSB
156 * at bit 0, green's at 4, blue's at 8, bits 15-12 dead at the pins. Identical to
157 * platform/pico/platform_pico.h, which cannot be included here - it pulls in
158 * hardware/dma.h - so test/golden/CMakeLists.txt fails the configure if the two drift.
159 *
160 * ONE WIDTH, deliberately. The scanline is laid out in 32-bit words holding two
161 * pixels: the half-border arithmetic and the fill counts in pico9918_frame.c, and the
162 * pair-packed LUT below. A wider pixel halves the picture, and pico9918_palette.c's
163 * paired build stages through uint16_t, so it cannot build an 80-column LUT at all. A
164 * host converts to its display format on its own side of the line.
165 */
166#ifndef PICO9918_PIXEL_T
167typedef uint16_t PICO9918_PIXEL_T;
168#define PICO9918_PIXEL_T PICO9918_PIXEL_T
169#endif
170
171/*
172 * Expand a palette entry. The input is a pram entry: byte-swapped RGB444, 0xGB0R,
173 * so 15-12 is green, 11-8 blue and 3-0 red. Green is replicated into 7-4, and the
174 * 0xFF0F mask is what keeps the top nibble's copy out of blue's MSB on the RP2040
175 * CRT-dim path, which shifts the whole word right unmasked.
176 */
177#ifndef PICO9918_PIXEL_FROM_RGB12
178#define PICO9918_PIXEL_FROM_RGB12(rgb) \
179 ((PICO9918_PIXEL_T)(((rgb) & 0xFF0F) | ((((rgb) & 0xFF0F) >> 12) << 4)))
180#endif
181
182/* Both entries of a word at once. The transform is applied to each half rather than
183 assumed, so a host overriding only PICO9918_PIXEL_FROM_RGB12 gets this for free. */
184#ifndef PICO9918_PIXEL_FROM_RGB12_PAIR
185#define PICO9918_PIXEL_FROM_RGB12_PAIR(packed) \
186 ((((uint32_t)PICO9918_PIXEL_FROM_RGB12((packed) >> 16)) << 16) | \
187 (uint16_t)PICO9918_PIXEL_FROM_RGB12((uint16_t)(packed)))
188#endif
189
190#ifndef PICO9918_PIXEL_PAIR
191#define PICO9918_PIXEL_PAIR(p) ((uint32_t)(p) * 0x10001u)
192#endif
193
194#ifndef PICO9918_LOW16
195#define PICO9918_LOW16(x) ((uint32_t)(uint16_t)(x))
196#endif
197
198/* Dim an existing pixel (diagnostics overlay) - two stops down, per channel. */
199#ifndef PICO9918_PIXEL_DARKEN
200#define PICO9918_PIXEL_DARKEN(p) ((PICO9918_PIXEL_T)(((p) >> 2) & 0x333))
201#endif
202
203/* One stop down, both pixels of a word at once - the CRT-scanline effect. The mask is
204 what keeps each channel's LSB out of the channel below it, and out of the next
205 pixel's MSB. The board's VGA layer can skip it on RP2040, where the strays are dead
206 at the pins; a host has no pin boundary to hide them behind. */
207#ifndef PICO9918_PIXEL_PAIR_DIM
208#define PICO9918_PIXEL_PAIR_DIM(w) (((w) >> 1) & 0x07770777u)
209#endif
210
211/* The unit the overlay's glyph blit works on. Two pixels to a word, so a glyph cell
212 is three stores rather than six, and the darkened background falls out of the same
213 masked expression as the ink. */
214#ifndef PICO9918_INK_T
215typedef uint32_t PICO9918_INK_T;
216#define PICO9918_INK_T PICO9918_INK_T
217#define PICO9918_INK_PIXELS 2
218#define PICO9918_INK_FILL(fg) PICO9918_PIXEL_PAIR(fg)
219#define PICO9918_INK_DARKEN(w) (((w) >> 2) & 0x03330333u)
220#define PICO9918_INK_ONE(k) (0xffffu << ((k) * 16))
221#endif
222
223/*
224 * Palette LUT - 256 entries of a packed pixel *pair* (two 16-bit pixels in one
225 * 32-bit word), so the expansion loop emits one store per two output pixels.
226 */
227#ifndef PICO9918_PALETTE_LUT_T
228typedef uint32_t PICO9918_PALETTE_LUT_T;
229#define PICO9918_PALETTE_LUT_T PICO9918_PALETTE_LUT_T
230#endif
231
232/* Nothing per-core to set up off-target. */
233#ifndef PICO9918_EXPAND_INIT
234#define PICO9918_EXPAND_INIT(lut) ((void)0)
235#endif
236
237/*
238 * Indexed bytes -> pixel pairs. 8-way unrolled; n is a multiple of 8
239 * (TMS9918_PIXELS_X and its V9938 multiples all are).
240 */
241#ifndef PICO9918_EXPAND_INDEXED
242#define PICO9918_EXPAND_INDEXED(dst, src, n, lut) \
243 do \
244 { \
245 const uint8_t* _s = (const uint8_t*)(src); \
246 const uint8_t* _e = _s + (n); \
247 uint32_t* _d = (uint32_t*)(dst); \
248 const PICO9918_PALETTE_LUT_T* _l = (lut); \
249 while (_s < _e) \
250 { \
251 _d[0] = _l[_s[0]]; \
252 _d[1] = _l[_s[1]]; \
253 _d[2] = _l[_s[2]]; \
254 _d[3] = _l[_s[3]]; \
255 _d[4] = _l[_s[4]]; \
256 _d[5] = _l[_s[5]]; \
257 _d[6] = _l[_s[6]]; \
258 _d[7] = _l[_s[7]]; \
259 _d += 8; \
260 _s += 8; \
261 } \
262 } while (0)
263#endif
264
265/*
266 * The 80-column 8bpp line, which is already at full pixel width: a LUT entry holds the
267 * colour in both halves, so a destination word is two indices and two lookups rather
268 * than one index doubled.
269 */
270#ifndef PICO9918_EXPAND_INDEXED_WIDE
271#define PICO9918_EXPAND_INDEXED_WIDE(dst, src, n, lut) \
272 do \
273 { \
274 const uint8_t* _s = (const uint8_t*)(src); \
275 uint32_t* _d = (uint32_t*)(dst); \
276 const PICO9918_PALETTE_LUT_T* _l = (lut); \
277 for (unsigned _i = 0; _i < (unsigned)(n); _i += 2) \
278 _d[_i / 2] = (_l[_s[_i]] & 0xffff) | (_l[_s[_i + 1]] << 16); \
279 } while (0)
280#endif
281
282
283/*
284 * Timer abstraction
285 * time_us_32() returns a 32-bit microsecond counter.
286 */
287#ifdef _WIN32
288#include <windows.h>
289static inline uint32_t time_us_32(void)
290{
291 LARGE_INTEGER freq, cnt;
292 QueryPerformanceFrequency(&freq);
293 QueryPerformanceCounter(&cnt);
294 return (uint32_t)((cnt.QuadPart * 1000000ULL) / freq.QuadPart);
295}
296#else
297#include <time.h>
298static inline uint32_t time_us_32(void)
299{
300 struct timespec ts;
301 clock_gettime(CLOCK_MONOTONIC, &ts);
302 return (uint32_t)(ts.tv_sec * 1000000UL + ts.tv_nsec / 1000UL);
303}
304#endif
305
306
307/*
308 * __time_critical_func / __not_in_flash_func
309 * Provided by pico/stdlib.h on Pico; no-ops here.
310 */
311#ifndef __time_critical_func
312#define __time_critical_func(fn) fn
313#endif
314#ifndef __not_in_flash_func
315#define __not_in_flash_func(fn) fn
316#endif
317#ifndef __force_inline
318#define __force_inline inline
319#endif
320#ifndef __aligned
321#if defined(_MSC_VER) && !defined(__clang__)
322#define __aligned(n) __declspec(align(n))
323#else
324#define __aligned(n) __attribute__((aligned(n)))
325#endif
326#endif
327
328
329/*
330 * __builtin_bswap16
331 * GCC/Clang provide this intrinsic; MSVC does not.
332 */
333#if defined(_MSC_VER) && !defined(__clang__)
334#include <stdlib.h>
335#define __builtin_bswap16(x) _byteswap_ushort(x)
336#endif