Skip to content

Glimmer Reference03

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

asm
; --- 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. VdpWaitVBlank spins until the flag rises, and reading the status register clears it for the next frame.
  • GlimCommit streams 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.
  • LoadResourcesVram appears when the program declares a sprite or a tile; it uploads patterns and colours once, after VdpInit.

Ports and the VRAM map

EquateValueMeaning
VDP_DATA$BEdata port: reads and writes VRAM at the auto-incrementing address
VDP_CONTROL$BFcontrol port: address setup, register writes, status reads
VRAM_PATTERN$0000tile pattern table, 8 bytes per tile
VRAM_NAME$0800name table, 32x24 tile indexes
VRAM_SPRITE_ATTR$1B00sprite attributes, 4 bytes per slot
VRAM_COLOR$2000colour table, one byte per pattern group
VRAM_SPRITE_PAT$3800sprite 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:

asm
; --- 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 black

Colours

NameValueNameValue
VC_TRANSPARENT0VC_MEDRED8
VC_BLACK1VC_LIGHTRED9
VC_MEDGREEN2VC_DARKYELLOW10
VC_LIGHTGREEN3VC_LIGHTYELLOW11
VC_DARKBLUE4VC_DARKGREEN12
VC_LIGHTBLUE5VC_MAGENTA13
VC_DARKRED6VC_GRAY14
VC_CYAN7VC_WHITE15

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

asm
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 0
  • NameShadow mirrors the name table: row * 32 + column holds the tile index at that cell. NamePut writes it and sets the row's bit in NameDirtyRows, three bytes covering rows 0-23.
  • SpriteShadow mirrors the sprite attribute table, four bytes per slot in VDP order: y, x, pattern, colour. Any write through SpriteSet or SpriteInit sets SpriteDirty.
  • GlimCommit reads both markers at frame start. A set SpriteDirty streams all 128 shadow bytes to VRAM_SPRITE_ATTR; each set row bit sends one 32-byte row through CommitNameRow. A frame with clean shadows costs a few flag tests.

Sprites

text
sprite Dot color white
  "..XXXX.."
  ".XXXXXX."
  ...
end
  • Declaration order is the sprite's slot and its pattern number: the first sprite is 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; VdpInit writes $D1 into all 32 shadow slots, so undeclared slots stay hidden and terminate the scan.
  • Each declaration compiles to 8 pattern bytes in GlimSpritePats and a name equate. LoadResourcesVram uploads the patterns to VRAM_SPRITE_PAT in one block, then calls SpriteInit per slot to set its pattern and colour in the shadow.
asm
GlimSpritePats:
        .db     %00111100
        .db     %01111110
        ...
Dot               .equ 0   ; sprite slot + pattern

Tiles

text
tile Brick color lightred on black
  "XXXXXXXX"
  "X......X"
  ...
end

Graphics 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. VdpInit fills the colour table with $F1 (white on black) before LoadResourcesVram writes 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.
asm
GlimTilePats:
; tile Brick -> index 1
        .db     %11111111
        .db     %10000001
        ...
Brick             .equ 1   ; tile index

The ops

A sprite or tile declaration also generates assembler ops; block bodies invoke them as ordinary statements and they expand inline.

asm
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
end

sprite_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.

RoutineContract
VdpInitclobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry
VdpSetAddrWritein HL clobbers A,carry,zero,sign,parity,halfCarry
VdpWriteBlockin HL,BC clobbers A,BC,HL,carry,zero,sign,parity,halfCarry
VdpFillin HL,BC,E clobbers A,BC,carry,zero,sign,parity,halfCarry
VdpWaitVBlankclobbers A,carry,zero,sign,parity,halfCarry
SpriteSetin A,D,E clobbers A,HL,carry,zero,sign,parity,halfCarry
SpriteInitin A,D,E clobbers A,HL,carry,zero,sign,parity,halfCarry
NamePutin A,D,E clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry
CommitNameRowin A clobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry
GlimCommitclobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry
LoadResourcesVramclobbers A,BC,DE,HL,carry,zero,sign,parity,halfCarry
  • VdpInit writes the eight registers from VdpRegInitTbl, 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 before MainLoop.
  • VdpSetAddrWrite sets the VRAM write address in HL. VdpWriteBlock then streams BC bytes from HL through the data port. VdpFill sets the address itself and writes BC copies of E.
  • VdpWaitVBlank spins on bit 7 of the status register. The generated loop calls it.
  • SpriteSet positions slot A at D = x, E = y in the shadow. SpriteInit assigns slot A its pattern D and colour E; the generated LoadResourcesVram calls it once per declared slot.
  • NamePut puts tile A at column D, row E of the name-table shadow and marks the row. CommitNameRow flushes one shadow row (A = 0-23) to VRAM; GlimCommit calls it per marked row.
  • Pattern and colour uploads beyond the declared resources are one-time init work. An enter block can call the Vdp* 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.