Appendix C — The TMS9918 Profile
The combination of platform tec1g-mon3 and display tms9918 contributes the commit-shaped loop, VRAM map, shadow tables, sprite and tile resources, generated ops and library routines. Every listing and value here is copied from a program built with glimmer build; the register interfaces are the .routine lines the assembler checks under strict contract checking.
The loop
; --- runtime loop ---
Start:
call VdpInit
call LoadResourcesVram
MainLoop:
call VdpWaitVBlank ; pace on the status-register flag
call GlimCommit ; flush shadows in the blank window
call GlimPollBindings
ld a,(CurrentCard) ; latch: card transitions land at
ld (GlimActiveCard),a ; frame start, never mid-frame
call GlimRunLogicEffects
call GlimMergeRaised
call GlimRunRenderEffects
call GlimEndFrame
jp MainLoop- The VDP renders autonomously from its VRAM; the loop paces on the vblank status flag.
VdpWaitVBlankspins until the flag rises, and reading the status register clears it for the next frame. GlimCommitstreams changed shadow tables to VRAM immediately after vblank, inside the blank window. Render blocks write ordinary memory this frame; the top of the next frame publishes it.LoadResourcesVramappears when the program declares a sprite or a tile; it uploads patterns and colours once, afterVdpInit.
Ports and the VRAM map
| Equate | Value | Meaning |
|---|---|---|
VDP_DATA | $BE | data port: reads and writes VRAM at the auto-incrementing address |
VDP_CONTROL | $BF | control port: address setup, register writes, status reads |
VRAM_PATTERN | $0000 | tile pattern table, 8 bytes per tile |
VRAM_NAME | $0800 | name table, 32x24 tile indexes |
VRAM_SPRITE_ATTR | $1B00 | sprite attributes, 4 bytes per slot |
VRAM_COLOR | $2000 | colour table, one byte per pattern group |
VRAM_SPRITE_PAT | $3800 | sprite pattern table, 8 bytes per sprite |
VdpSetAddrWrite sends the low byte, then the high byte OR $40, through the control port; every byte written to VDP_DATA after that lands at the next VRAM address. The register init table wires this Graphics I layout into the VDP:
; --- VDP register init (value, then index|$80, via the control port) ---
VdpRegInitTbl:
.db $00, $C0, $02, $80, $00, $36, $07, $01
; Graphics I; display on, 16K; name $0800; colour $2000;
; pattern $0000; sprite attrs $1B00; sprite patterns $3800;
; backdrop blackColours
| Name | Value | Name | Value |
|---|---|---|---|
VC_TRANSPARENT | 0 | VC_MEDRED | 8 |
VC_BLACK | 1 | VC_LIGHTRED | 9 |
VC_MEDGREEN | 2 | VC_DARKYELLOW | 10 |
VC_LIGHTGREEN | 3 | VC_LIGHTYELLOW | 11 |
VC_DARKBLUE | 4 | VC_DARKGREEN | 12 |
VC_LIGHTBLUE | 5 | VC_MAGENTA | 13 |
VC_DARKRED | 6 | VC_GRAY | 14 |
VC_CYAN | 7 | VC_WHITE | 15 |
A colour is one of fifteen fixed codes plus transparent. Sprites carry one colour each; tiles carry a foreground and background pair packed as fg * 16 + bg in the colour table.
Shadows
NameShadow: .ds 768, 0 ; 32x24 name table shadow
NameDirtyRows: .db 0, 0, 0 ; 24 dirty-row bits
SpriteShadow: .ds 128, 0 ; 32 x (y, x, pattern, colour)
SpriteDirty: .db 0NameShadowmirrors the name table:row * 32 + columnholds the tile index at that cell.NamePutwrites it and sets the row's bit inNameDirtyRows, three bytes covering rows 0-23.SpriteShadowmirrors the sprite attribute table, four bytes per slot in VDP order: y, x, pattern, colour. Any write throughSpriteSetorSpriteInitsetsSpriteDirty.GlimCommitreads both markers at frame start. A setSpriteDirtystreams all 128 shadow bytes toVRAM_SPRITE_ATTR; each set row bit sends one 32-byte row throughCommitNameRow. A frame with clean shadows costs a few flag tests.
Sprites
sprite Dot color white
"..XXXX.."
".XXXXXX."
...
end- Declaration order is the sprite's slot and its pattern number: the first
spriteis slot 0, the second slot 1. Slots stay contiguous from 0 because the VDP stops processing sprites at the first slot whose Y is$D1;VdpInitwrites$D1into all 32 shadow slots, so undeclared slots stay hidden and terminate the scan. - Each declaration compiles to 8 pattern bytes in
GlimSpritePatsand a name equate.LoadResourcesVramuploads the patterns toVRAM_SPRITE_PATin one block, then callsSpriteInitper slot to set its pattern and colour in the shadow.
GlimSpritePats:
.db %00111100
.db %01111110
...
Dot .equ 0 ; sprite slot + patternTiles
tile Brick color lightred on black
"XXXXXXXX"
"X......X"
...
endGraphics I colours patterns in groups of eight: one colour-table byte covers pattern indexes g*8 to g*8+7.
- Tiles sharing a (fg, bg) pair share a group. Tile index 0 stays the blank tile, so the first pair fills group 0 from index 1 and holds seven tiles; later groups hold eight. A pair whose group is full spills into a new group.
- The first pair's background is the screen background: empty name table cells show tile 0, which sits in group 0 and takes its colours from the first pair.
VdpInitfills the colour table with$F1(white on black) beforeLoadResourcesVramwrites the declared pairs. - The colour table holds 32 groups. The compiler reports a build diagnostic when a program declares more, naming the count. Reusing (fg, bg) pairs reduces the count.
GlimTilePats:
; tile Brick -> index 1
.db %11111111
.db %10000001
...
Brick .equ 1 ; tile indexThe ops
A sprite or tile declaration also generates assembler ops; block bodies invoke them as ordinary statements and they expand inline.
op sprite_at(slot imm8, xcell imm16, ycell imm16)
ld a,(xcell)
ld d,a
ld a,(ycell)
ld e,a
ld a,slot
call SpriteSet
end
op tile_at(tile imm8, col imm8, row imm8)
ld a,tile
ld d,col
ld e,row
call NamePut
endsprite_at Player, PlayerX, PlayerY reads the two byte cells and positions the slot; the cell names assemble as addresses. tile_at takes immediates, so code with a computed position loads A, D and E and calls NamePut directly, as Sprite Chase does for its score pips.
Library routines
Each entry's .routine contract is copied from the generated file. LoadResourcesVram and the ops appear when the program declares a sprite or a tile.
| Routine | Contract |
|---|---|
VdpInit | clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry |
VdpSetAddrWrite | in HL clobbers A,carry,zero,sign,parity,halfCarry |
VdpWriteBlock | in HL,BC clobbers A,BC,HL,carry,zero,sign,parity,halfCarry |
VdpFill | in HL,BC,E clobbers A,BC,carry,zero,sign,parity,halfCarry |
VdpWaitVBlank | clobbers A,carry,zero,sign,parity,halfCarry |
SpriteSet | in A,D,E clobbers A,HL,carry,zero,sign,parity,halfCarry |
SpriteInit | in A,D,E clobbers A,HL,carry,zero,sign,parity,halfCarry |
NamePut | in A,D,E clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry |
CommitNameRow | in A clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry |
GlimCommit | clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry |
LoadResourcesVram | clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry |
VdpInitwrites the eight registers fromVdpRegInitTbl, fills the colour table with$F1, clears the pattern table (2048 bytes) and the name table (768 bytes), and hides all 32 sprites in the shadow. The loop calls it once beforeMainLoop.VdpSetAddrWritesets the VRAM write address in HL.VdpWriteBlockthen streams BC bytes from HL through the data port.VdpFillsets the address itself and writes BC copies of E.VdpWaitVBlankspins on bit 7 of the status register. The generated loop calls it.SpriteSetpositions slot A at D = x, E = y in the shadow.SpriteInitassigns slot A its pattern D and colour E; the generatedLoadResourcesVramcalls it once per declared slot.NamePutputs tile A at column D, row E of the name-table shadow and marks the row.CommitNameRowflushes one shadow row (A = 0-23) to VRAM;GlimCommitcalls it per marked row.- Pattern and colour uploads beyond the declared resources are one-time init work. An
enterblock can call theVdp*routines, with the tables held in an imported assembly module.
The LCD slice, its Api*ToLcd equates, text string data, and the lcd_row op are board hardware shared with the matrix profile; Appendix B documents them, and a tms9918 build emits the same lines.