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