pico9918-core 1.3.0
TMS9918A / F18A video display processor emulation in C99
Loading...
Searching...
No Matches
pico9918_config.h File Reference

pico9918-core - config byte layout More...

#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include "pico9918.h"
+ Include dependency graph for pico9918_config.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  pico9918_config_field_t
 one config field's descriptor More...
 
struct  pico9918_config_host_id_t
 the identity bytes at 0-3, which only the host knows More...
 

Macros

#define CONFIG_BYTES   256
 size of the config block, in bytes
 
#define PICO9918_BASE_TMS9918   0x00
 vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE
 
#define PICO9918_BASE_V9938   0x01
 the V9938 base
 
#define PICO9918_PENDING_RECORD_BYTES    (PICO9918_CONF_PENDING_CLOCK_PRESET - PICO9918_CONF_PENDING_STATE + 1)
 bytes in a pending record: the state, then one slot per tracked field
 
#define PENDING_MIRROR_NONE   0xFF
 pendingMirror value for a field outside the confirmation flow
 

Enumerations

enum  pico9918_config_option_t {
  PICO9918_CONF_PICO_MODEL = 0 , PICO9918_CONF_HW_VERSION = 1 , PICO9918_CONF_SW_VERSION = 2 , PICO9918_CONF_SW_PATCH_VERSION = 3 ,
  PICO9918_CONF_CLOCK_TESTED = 4 , PICO9918_CONF_DISP_DRIVER = 5 , PICO9918_CONF_FLASH_STATUS = 6 , PICO9918_CONF_CRT_SCANLINES = 8 ,
  PICO9918_CONF_SCANLINE_SPRITES = 9 , PICO9918_CONF_CLOCK_PRESET_ID = 10 , PICO9918_CONF_SCART_MODE = 11 , PICO9918_CONF_VDP_DEVICE = 12 ,
  PICO9918_CONF_DISP_DRIVER_PREF = 13 , PICO9918_CONF_VGA_MODE = 14 , PICO9918_CONF_VDP_BASE = 15 , PICO9918_CONF_DIAG = 16 ,
  PICO9918_CONF_DIAG_REGISTERS = 17 , PICO9918_CONF_DIAG_PERFORMANCE = 18 , PICO9918_CONF_DIAG_PALETTE = 19 , PICO9918_CONF_DIAG_ADDRESS = 20 ,
  PICO9918_CONF_PALETTE_IDX_0 = 128 , PICO9918_CONF_PALETTE_IDX_15 = PICO9918_CONF_PALETTE_IDX_0 + 32 , PICO9918_CONF_PENDING_STATE = 200 , PICO9918_CONF_PENDING_DRIVER_PREF = 201 ,
  PICO9918_CONF_PENDING_VGA_MODE = 202 , PICO9918_CONF_PENDING_SCART_MODE = 203 , PICO9918_CONF_PENDING_CLOCK_PRESET = 204 , PICO9918_CONF_SAVE_FORCED = 252 ,
  PICO9918_CONF_PENDING_CANCEL = 253 , PICO9918_CONF_PENDING_CONFIRM = 254 , PICO9918_CONF_SAVE_TO_FLASH = 255
}
 every claimed config byte, by index. More...
 
enum  pico9918_pending_state_t { PICO9918_PENDING_STATE_CONFIRMED = 0xC0 , PICO9918_PENDING_STATE_PENDING = 0x9E , PICO9918_PENDING_STATE_ARMED = 0xA0 }
 PICO9918_CONF_PENDING_STATE values, and the state byte of a host's stored pending record. More...
 

Functions

uint8_t * pico9918_config (pico9918_t *tms9918)
 the instance's CONFIG_BYTES settings block
 
void pico9918_config_defaults (uint8_t config[CONFIG_BYTES])
 write a complete, valid settings block: every field at its default
 
bool pico9918_config_validate (uint8_t config[CONFIG_BYTES], pico9918_config_host_id_t id)
 validate a config block just read from host storage, and stamp id into it
 
void pico9918_config_prepare_save (uint8_t config[CONFIG_BYTES], pico9918_config_host_id_t id)
 stamp id and the initialised marker into a block about to be persisted
 
void pico9918_config_refresh_pending_mirror (uint8_t config[CONFIG_BYTES], uint8_t state)
 copy live tracked fields into the in-RAM pending mirror with the given state
 
void pico9918_config_pending_capture (const uint8_t config[CONFIG_BYTES], uint8_t *record)
 copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record
 
void pico9918_config_pending_restore (uint8_t config[CONFIG_BYTES], const uint8_t *record)
 copy a pending record's field slots back over the live config
 
void pico9918_config_apply (pico9918_t *tms9918)
 apply the config block's VDP-side effects: registers 50 and 30, the palette unpack, and the derived PICO9918_CONF_DIAG summary byte
 
void pico9918_config_schedule_apply (pico9918_t *tms9918, bool applyVdpEffects)
 ask for the block to be applied at the next end of frame
 
void pico9918_config_apply_now (pico9918_t *tms9918, bool applyVdpEffects)
 apply the block now, and cancel any apply already owed
 
void pico9918_config_set_applied_callback (pico9918_t *tms9918, pico9918_config_applied_fn cb, void *userdata)
 register the host's config-applied hook
 

Variables

const pico9918_config_field_t pico9918_config_fields []
 the descriptor table.
 
const size_t pico9918_config_field_count
 how many rows pico9918_config_fields has
 

Detailed Description

pico9918-core - config byte layout

Copyright (c) 2021 Troy Schrapel

This code is licensed under the MIT license

https://github.com/visrealm/pico9918-core

Purpose: THE single authoritative layout of the 256-byte config block, plus the portable semantics over it (field descriptors, validation, defaults, per-version migration, and the VDP-side apply).

This header owns the config-byte ABI. It is shared by:

  • the library (core reset/write paths, GPU config-action keys)
  • the PICO9918 firmware (flash storage, apply, validation)
  • the configurator (generated from this header)

Nothing else may declare a PICO9918_CONF_* byte index. If a new byte is needed, it is claimed here and nowhere else.

This header must stay free of host dependencies - no PICO9918_* macros, no board headers, no SDK includes. Version numbers are host-owned and always arrive as parameters, never as compile-time macros.


ABI FREEZE

The following bytes are deployed in flash on real units. Their indices are frozen and must NEVER be reassigned to a different meaning - doing so makes existing units come up with corrupted settings:

0-6, 8-14, 16-20, 128-160, 200-204, 252-255

Free for future claims: 7, 15 (claimed below), 21-127, 161-199, 205-245.

Byte 7 is deliberately left unused: it falls in the "not settable via registers" identity/version band (0-6) that the configurator may treat as reserved, and the firmware's version-match load path does not clear it.


CONFIGURATOR MENU-SENTINEL BAND: 246-255

The configurator uses 246-255 as menu-sentinel IDs in the SAME numeric space as these config indices (CONF_MENU_OUTPUT = 246 .. CONF_MENU_EMPTY = 255). The overlap at 252-255 is deliberate and already shipping. Do not claim 246-251 for a real config byte without first checking the configurator's sentinel list - a collision there breaks menu dispatch.

Definition in file pico9918_config.h.

Macro Definition Documentation

◆ CONFIG_BYTES

#define CONFIG_BYTES   256

size of the config block, in bytes

Definition at line 63 of file pico9918_config.h.

◆ PICO9918_BASE_TMS9918

#define PICO9918_BASE_TMS9918   0x00

vdpBase values - the render base selected by PICO9918_CONF_VDP_BASE

the TMS9918A base, which the F18A unlock extends

Definition at line 66 of file pico9918_config.h.

◆ PICO9918_BASE_V9938

#define PICO9918_BASE_V9938   0x01

the V9938 base

Definition at line 67 of file pico9918_config.h.

◆ PICO9918_PENDING_RECORD_BYTES

#define PICO9918_PENDING_RECORD_BYTES    (PICO9918_CONF_PENDING_CLOCK_PRESET - PICO9918_CONF_PENDING_STATE + 1)

bytes in a pending record: the state, then one slot per tracked field

LOAD-BEARING: a tracked field's slot is its pendingMirror less PICO9918_CONF_PENDING_STATE, so a host's stored record and the in-RAM mirror band are the same layout. pico9918_config_pending_capture() and _restore() are built on it, as is every tool that writes the record, so claiming a new mirror byte means naming it here.

Definition at line 145 of file pico9918_config.h.

◆ PENDING_MIRROR_NONE

#define PENDING_MIRROR_NONE   0xFF

pendingMirror value for a field outside the confirmation flow

Definition at line 158 of file pico9918_config.h.

Enumeration Type Documentation

◆ pico9918_config_option_t

every claimed config byte, by index.

The values are a frozen ABI

Definition at line 70 of file pico9918_config.h.

◆ pico9918_pending_state_t

PICO9918_CONF_PENDING_STATE values, and the state byte of a host's stored pending record.

A frozen ABI - the configurator reads it out of byte 200

CONFIRMED -> PENDING (host saves) -> ARMED (host boots with it) -> CONFIRMED (the user accepts, or the next boot reverts)

Definition at line 129 of file pico9918_config.h.

Function Documentation

◆ pico9918_config()

uint8_t * pico9918_config ( pico9918_t *  tms9918)

the instance's CONFIG_BYTES settings block

The same bytes pico9918_config_validate() checks and pico9918_config_apply() acts on, so a host reads its stored block into this and applies it. Persistence stays the host's - the library never reaches storage - and so does the decision to write, since these are settings a user chose rather than VDP state.

Definition at line 166 of file pico9918_config.c.

◆ pico9918_config_defaults()

void pico9918_config_defaults ( uint8_t  config[CONFIG_BYTES])

write a complete, valid settings block: every field at its default

What a host wants when it has nothing stored, and the reason it should not simply zero the block: the field defaults happen to be zero today, but the palette's are not, and pico9918_config_apply() unpacks those bytes into the live palette. A zeroed block therefore renders black. This also sets the initialised marker that pico9918_config_validate() looks for, so a block from here survives it untouched.

The identity bytes at 0-3 are cleared with the rest; pico9918_config_validate() and pico9918_config_prepare_save() are where a host's own identity is stamped in.

Definition at line 85 of file pico9918_config.c.

References CONFIG_BYTES, pico9918_config_field_t::defaultValue, pico9918_config_field_t::offset, and pico9918_default_palette().

Referenced by pico9918_config_validate().

◆ pico9918_config_validate()

bool pico9918_config_validate ( uint8_t  config[CONFIG_BYTES],
pico9918_config_host_id_t  id 
)

validate a config block just read from host storage, and stamp id into it

Resets the block to defaults if it is not this host's, is uninitialised, or holds an out-of-range field; then defaults the fields introduced since the stored version. Either way the identity bytes end up at id and the command bytes a host persisted are cleared.

Returns true if the block changed in a way the host should persist. A host running the configurator protocol can ignore that: PICO9918_CONF_SAVE_FORCED is set on the same path, which is the save request its GPU loop already dispatches.

Definition at line 116 of file pico9918_config.c.

References pico9918_config_defaults().

◆ pico9918_config_prepare_save()

void pico9918_config_prepare_save ( uint8_t  config[CONFIG_BYTES],
pico9918_config_host_id_t  id 
)

stamp id and the initialised marker into a block about to be persisted

The marker is how pico9918_config_validate() tells a stored block from an erased one, so a host that persists a block without this gets a factory reset on its next boot. Host storage is untouched - this only prepares the bytes.

Definition at line 150 of file pico9918_config.c.

◆ pico9918_config_refresh_pending_mirror()

void pico9918_config_refresh_pending_mirror ( uint8_t  config[CONFIG_BYTES],
uint8_t  state 
)

copy live tracked fields into the in-RAM pending mirror with the given state

Definition at line 41 of file pico9918_config.c.

References pico9918_config_field_t::offset, PENDING_MIRROR_NONE, and pico9918_config_field_t::pendingMirror.

◆ pico9918_config_pending_capture()

void pico9918_config_pending_capture ( const uint8_t  config[CONFIG_BYTES],
uint8_t *  record 
)

copy the live tracked fields into a PICO9918_PENDING_RECORD_BYTES record

Note
record[0], the state, is the caller's - only the field slots are written

Definition at line 51 of file pico9918_config.c.

References pico9918_config_field_t::offset, PENDING_MIRROR_NONE, and pico9918_config_field_t::pendingMirror.

◆ pico9918_config_pending_restore()

void pico9918_config_pending_restore ( uint8_t  config[CONFIG_BYTES],
const uint8_t *  record 
)

copy a pending record's field slots back over the live config

Definition at line 61 of file pico9918_config.c.

References pico9918_config_field_t::offset, PENDING_MIRROR_NONE, and pico9918_config_field_t::pendingMirror.

◆ pico9918_config_apply()

void pico9918_config_apply ( pico9918_t *  tms9918)

apply the config block's VDP-side effects: registers 50 and 30, the palette unpack, and the derived PICO9918_CONF_DIAG summary byte

A settings block is a PICO9918 thing, so the effects land only on a personality that has the config port. On an F18A those registers and that palette are the guest's alone, and a block read from host storage must not touch them.

What it writes is a power-on default, not an owner: it runs when the block is loaded and after a reset has cleared the register file, and a later write to register 50 or 30 stands on every personality.

Host-side effects stay with the host.

Definition at line 197 of file pico9918_config.c.

References PICO9918_BASE_TMS9918, PICO9918_BASE_V9938, PICO9918_INST_ONLY, PICO9918_R50_VSCANLINES, PICO9918_REG_ENHANCED2, and PICO9918_REG_MAX_SCAN_SPRITES.

Referenced by pico9918_config_apply_now(), and pico9918_frame_raise_end_of_frame_int().

◆ pico9918_config_schedule_apply()

void pico9918_config_schedule_apply ( pico9918_t *  tms9918,
bool  applyVdpEffects 
)

ask for the block to be applied at the next end of frame

The deferred form, and what a device wants: the apply seeds registers and republishes the palette, so doing it mid-frame would show on the line being scanned out. applyVdpEffects asks for that reseeding; without it the apply runs its host-side and derived effects only.

A host that has no frame boundary to wait for wants pico9918_config_apply_now().

Definition at line 243 of file pico9918_config.c.

◆ pico9918_config_apply_now()

void pico9918_config_apply_now ( pico9918_t *  tms9918,
bool  applyVdpEffects 
)

apply the block now, and cancel any apply already owed

For a host that has just written the block itself - a configurator front end, or a consumer stepping the library a frame at a time - and would rather see the effects than wait for a boundary it does not have. The deferred request is cleared, so the next end of frame does not apply the same block a second time.

Definition at line 249 of file pico9918_config.c.

References pico9918_config_apply(), and PICO9918_INST_ONLY.

◆ pico9918_config_set_applied_callback()

void pico9918_config_set_applied_callback ( pico9918_t *  tms9918,
pico9918_config_applied_fn  cb,
void *  userdata 
)

register the host's config-applied hook

Fires from pico9918_config_apply(), which the frame module calls where the configDirty flag is actually consumed - the end-of-frame interrupt, not the scanline body - so it is per-frame at worst and a function pointer is permitted. It exists so a host's own apply effects stay in lockstep with the library's register and palette effects, instead of the host having to watch configDirty itself.

Called LAST, after the VDP-side effects, and on every personality: a host effect is the host's to gate, and one derived from a register has to read the value this call may just have seeded. NULL (the default) means the host has no such effects and nothing is called.

Registered per instance in a multi-instance build - see pico9918.h for why the two builds take different shapes.

Definition at line 184 of file pico9918_config.c.

Variable Documentation

◆ pico9918_config_fields

const pico9918_config_field_t pico9918_config_fields[]

the descriptor table.

The host save path reads pendingMirror/max/ defaultValue from it

Definition at line 181 of file pico9918_config.h.

◆ pico9918_config_field_count

const size_t pico9918_config_field_count

how many rows pico9918_config_fields has

Definition at line 184 of file pico9918_config.h.