'' 2-bit paletted tile-based video driver '' Based on the NTSC Spectrum-like TV Video Driver '' ────────────────────────────────────────────────────────────────────────────────── '' Version story: '' '' 2007-11-25 2.0 Tiled Version (Spork Frog) '' 2007-12-05 1.0 First version (José Luis Cebrián) '' 2009-11-12 Halved tile horizontal resolution (Héctor Peraza) '' to eliminate redundant pixel pairs '' (BoulderDash uses 8x16 tiles). '' This also halves tile memory requirements. '' Fixed vertical sync pulse generation. '' 2009-11-13 Changed pixel clock settings (Héctor Peraza) '' in order to display 20 tiles per line, '' as in the classic C64 BoulderDash. '' 2009-11-14 Reverse pixel order before waitvid (Héctor Peraza) '' to avoid having to define mirrored tiles. '' 2009-11-15 Added an optional status line. (Héctor Peraza) '' 2009-11-17 Video config parameters can be passed (Héctor Peraza) '' on driver startup, to allow for different '' configurations and/or platforms. '' 2009-11-18 Implemented PAL mode. Color seems to (Héctor Peraza) '' be a bit off in PAL mode. '' 2009-12-05 Added the possibilty to halve tile (Héctor Peraza) '' height (we need that for the title screen, '' since it has text on the bottom part '' and characters are half-size) '' '' ────────────────────────────────────────────────────────────────────────────────── '' This code is in the public domain. Feel free to use it in any way you like. '' '' The screen for this driver is composed of two major parts: '' '' 1. Tile map '' Each of these entries is 1 byte long, representing the tile number. '' There are 880 words total for a 40 by 22 tile screen. The displayed '' area is 20 x 12 in NTSC mode, 20 x 14 in PAL mode. '' '' 2. Tiles '' Each tile is made up of 16 words with 2 bit color encoding on '' each tile. You can define as few or as many tiles as you want, '' but be sure not to use any tiles that you don't define. '' '' There is also an optional status line at the top of the screen that does not '' scroll with the rest. It is also composed of tiles, but these are only 8 pixels '' high. Two empty raster lines separate the status from the main screen. The '' foreground color can be set per character basis. '' '' 80Mhz is *required* in order to output pixels fast enough. CON ' Border offset, to center the image HorizBorderOffset = 9 ' positive values move the screen to the right VertBorderOffset = 10 ' positive values move the screen down ' Counter Module Configuration ' • CTRMode - Operating mode (0001 for Video Mode) ' • PLLDiv - Divisor for the VCO frequency (111: use VCO value as-is) '┌─────────── CTRMode '│ ┌───── PLLDiv CTRA_TVGEN = %00001_111 ' NTSC Color frequency in Hz ' ' This is the 'base' clock rate for all our NTSC timings. At start, the ' driver will program the FRQA register to output at this rate. Our base ' clock value is 1/16 of the NTSC clock rate, or approximately 0.01746 µs. NTSC_ClockFreq = 3_579_545 ' NTSC Timings table ' Time Clocks Output ' Total horizontal timing: 63.5 µs 3638 ' Horizontal blanking period: 10.9 µs 624 ' Front porch: 1.5 µs 86 * Black ($02) ' Synchronizing pulse: 4.7 µs 269 * Blank ($00) ' Back porch: 4.7 µs 269 ' Breeze away: 0.6 µs 34 * Black ($02) ' Colour burst: 2.5 µs 144 * Y Hue ($8A) ' Wait to data: 1.6 µs 92 * Black ($02) ' Visible line 52.6 µs 3008 ' Left border 2.5 µs 146 * Black ($00) ' Pixel data 47.5 µs 2720 ' Character (x20) 2.4 µs 136 * Data ' Right border 2.6 µs 146 * Black ($00) ' Half visible line ¹ 20.8 µs 1195 ' ' Lines marked with * are the actual parts of a visible line as sent to the TV. ' ' ¹ The vertical sync pulse is a series of half lines with inverted horizontal sync ' pulses (the so-called equalization pulses). These half lines should have a length ' of 3638/2 = 1819 clocks (that is, a 624-clocks HSync followed by about 1195 clocks ' of visible data). VSCL_NTSC_FrontPorch = 85 VSCL_NTSC_SynchronizingPulse = 270 VSCL_NTSC_BackPorch = 34 + 144 + 92 VSCL_NTSC_BreezeAway = 34 VSCL_NTSC_ColourBurst = 144 VSCL_NTSC_WaitToData = 92 VSCL_NTSC_VisibleLine = 3008 VSCL_NTSC_HalfLine = 1192 ' NTSC Half line VSCL_NTSC_LeftBorder = 146 + HorizBorderOffset VSCL_NTSC_RightBorder = 146 - HorizBorderOffset VSCL_NTSC_Character = (17 << 12) + 136 ' Eight double-width pixels NTSC_VerticalLines = 262 - 9 NTSC_TopLine = (NTSC_VerticalLines + 192) / 2 - VertBorderOffset NTSC_BottomLine = (NTSC_VerticalLines - 192) / 2 - VertBorderOffset ' PAL Color frequency in Hz ' ' This is the 'base' clock rate for all the PAL timings. At start, the ' driver will program the FRQA register to output at this rate. Our base ' clock value is 1/16 of the PAL clock rate, or approximately 0.014097 µs PAL_ClockFreq = 4_433_618 ' PAL Timings table ' Time Clocks Output ' Total horizontal timing: 64.0 µs 4540 ' Horizontal blanking period: 12.0 µs 851 ' Front porch: 1.6 µs 113 * Black ($02) ' Synchronizing pulse: 4.7 µs 333 * Blank ($00) ' Back porch: 5.7 µs 404 ' Breeze away: 0.9 µs 64 * Black ($02) ' Colour burst: 2.3 µs 160 * Y Hue ($8A) ' Wait to data: 2.5 µs 180 * Black ($02) ' Visible line 52.0 µs 3689 ' Left border 3.4 µs 244 * Black ($00) ' Pixel data 45.1 µs 3200 ' Character (x20) 2.3 µs 160 * Data ' Right border 3.4 µs 245 * Black ($00) ' Half visible line ¹ 20.0 µs 1419 ' ' Lines marked with * are the actual parts of a visible line as sent to the TV. ' ' ¹ The vertical sync pulse is a series of half lines with inverted horizontal sync ' pulses (the so-called equalization pulses). These half lines should have a length ' of 4540/2 = 2270 clocks (that is, a 851-clocks HSync followed by about 1419 clocks ' of visible data). VSCL_PAL_FrontPorch = 114 VSCL_PAL_SynchronizingPulse = 332 VSCL_PAL_BackPorch = 64 + 160 + 180 VSCL_PAL_BreezeAway = 64 VSCL_PAL_ColourBurst = 160 VSCL_PAL_WaitToData = 180 VSCL_PAL_VisibleLine = 3689 VSCL_PAL_HalfLine = 1419 ' PAL Half line VSCL_PAL_LeftBorder = 244 + HorizBorderOffset VSCL_PAL_RightBorder = 245 - HorizBorderOffset VSCL_PAL_Character = (20 << 12) + 160 ' Eight double-width pixels PAL_VerticalLines = 312 - 9 PAL_TopLine = (PAL_VerticalLines + 224) / 2 - VertBorderOffset PAL_BottomLine = (PAL_VerticalLines - 224) / 2 - VertBorderOffset PUB Start(vParam) '' Starts the TV driver and begins the output of NTSC video. '' Uses a Cog. '' '' Parameters: '' vParam → Array with video parameters: '' [0] → VCFG mode '' [1] → Pingroup for VCFG pins '' [2] → Pinmask for VCFG pins '' [3] → Pinmask for output pins '' [4] → Color standard: 0 = NTSC, 1 = PAL '' [5] → Tile map address '' [6] → Address of a vsync flag byte in HUB memory cognew(@Entry, vParam) DAT org $000 Entry jmp #StartDriver ' ───────────────────────────────────────── ' Data section ' ───────────────────────────────────────── ' Colors used in waitvid COLOR_SYNC long $00 ' Sync level is below black COLOR_BLACK long $02 COLOR_YHUE_NTSC long $8A ' NTSC color burst COLOR_YHUE_PAL_1 long $7A ' PAL swinging color burst COLOR_YHUE_PAL_2 long $AA ' " " " " COLOR_BORDER long $02 ' Black border ' The following constants are too big to use in-place, so we need to ' reserve some registers to put them here _ScreenSize long 40 * 28 ' Tile map size _NTSC_ClockFreq long NTSC_ClockFreq _VSCL_NTSC_Character long VSCL_NTSC_Character _VSCL_NTSC_VisibleLine long VSCL_NTSC_VisibleLine _VSCL_NTSC_HalfLine long VSCL_NTSC_HalfLine _PAL_ClockFreq long PAL_ClockFreq _VSCL_PAL_Character long VSCL_PAL_Character _VSCL_PAL_VisibleLine long VSCL_PAL_VisibleLine _VSCL_PAL_HalfLine long VSCL_PAL_HalfLine ' Other constants TrueWord long $FFFF ' ───────────────────────────────────────── ' Code section ' ───────────────────────────────────────── StartDriver ' Configure the Cog generators mov R0, PAR rdlong _VCFG_Mode, R0 add R0, #4 rdlong _VCFG_PinGroup, R0 add R0, #4 rdlong _VCFG_PinMask, R0 add R0, #4 rdlong _PortMask, R0 add R0, #4 rdlong VideoMode, R0 add R0, #4 rdlong ScreenPtr, R0 add R0, #4 rdlong VSyncPtr, R0 test VideoMode, #1 wz if_z mov _ClockFreq, _NTSC_ClockFreq if_nz mov _ClockFreq, _PAL_ClockFreq if_z mov _VSCL_Character, _VSCL_NTSC_Character if_nz mov _VSCL_Character, _VSCL_PAL_Character if_z mov _VSCL_VisibleLine, _VSCL_NTSC_VisibleLine if_nz mov _VSCL_VisibleLine, _VSCL_PAL_VisibleLine if_z mov _VSCL_HalfLine, _VSCL_NTSC_HalfLine if_nz mov _VSCL_HalfLine, _VSCL_PAL_HalfLine if_z mov _VSCL_LeftBorder, #VSCL_NTSC_LeftBorder if_nz mov _VSCL_LeftBorder, #VSCL_PAL_LeftBorder if_z mov _VSCL_RightBorder, #VSCL_NTSC_RightBorder if_nz mov _VSCL_RightBorder, #VSCL_PAL_RightBorder if_z mov _VSCL_FrontPorch, #VSCL_NTSC_FrontPorch if_nz mov _VSCL_FrontPorch, #VSCL_PAL_FrontPorch if_z mov _VSCL_SynchronizingPulse, #VSCL_NTSC_SynchronizingPulse if_nz mov _VSCL_SynchronizingPulse, #VSCL_PAL_SynchronizingPulse if_z mov _VSCL_BreezeAway, #VSCL_NTSC_BreezeAway if_nz mov _VSCL_BreezeAway, #VSCL_PAL_BreezeAway if_z mov _VSCL_ColourBurst, #VSCL_NTSC_ColourBurst if_nz mov _VSCL_ColourBurst, #VSCL_PAL_ColourBurst if_z mov _VSCL_BackPorch, #VSCL_NTSC_BackPorch if_nz mov _VSCL_BackPorch, #VSCL_PAL_BackPorch if_z mov _VSCL_WaitToData, #VSCL_NTSC_WaitToData if_nz mov _VSCL_WaitToData, #VSCL_PAL_WaitToData if_z mov _VerticalLines, #NTSC_VerticalLines if_nz mov _VerticalLines, #PAL_VerticalLines if_z mov _TopLine, #NTSC_TopLine if_nz mov _TopLine, #PAL_TopLine if_z mov _BottomLine, #NTSC_BottomLine if_nz mov _BottomLine, #PAL_BottomLine if_z mov _Burst1, COLOR_YHUE_NTSC if_nz mov _Burst1, COLOR_YHUE_PAL_1 if_z mov _Burst2, COLOR_YHUE_NTSC if_nz mov _Burst2, COLOR_YHUE_PAL_2 movs VCFG, _VCFG_PinMask ' VCFG'S = pinmask (pin31: 0000_0111 : pin24) movd VCFG, _VCFG_PinGroup ' VCFG'D = pingroup (grp. 3 i.e. pins 24-31) movi VCFG, _VCFG_Mode ' Baseband video on bottom nibble, 2-bit color, enable chroma on baseband or DIRA, _PortMask ' Setup the port mask for DAC access (set DAC pins to output) movi CTRA, #CTRA_TVGEN ' Setup the Counter Module Generator A mov R1, _ClockFreq ' R1 := Video Clock Frequency in Hz rdlong R2, #0 ' R2 := Current CPU Clock Frequency in Hz call #Divide ' R3 := R1÷R2 (fractional part) mov FRQA, R3 ' Setup the Counter Module Generator frequency mov phaseflip, #0 ' Frame loop :Frame mov LineCounter, _VerticalLines ' LineCounter := Number of vertical lines mov CharacterRows, #0 ' Copy the screen parameters to local variables in Cog memory mov AttribPtr, ScreenPtr mov TilePtr, AttribPtr add TilePtr, _ScreenSize rdlong COLORS, TilePtr add TilePtr, #4 rdlong OffsetX, TilePtr mov OffsetY, OffsetX shr OffsetX, #16 and OffsetY, TrueWord add TilePtr, #4 rdlong Status, TilePtr add TilePtr, #4 mov StatusPtr, TilePtr add TilePtr, #20*2 ' Y Offset Calculations mov R0, OffsetY test Status, #2 wz if_z and OffsetY, #%1111 if_nz and OffsetY, #%111 shl OffsetY, #1 mov CharacterRows, OffsetY if_z shr R0, #4 if_nz shr R0, #3 add R0, #1 :AddLoop add AttribPtr, #40 djnz R0, #:AddLoop sub AttribPtr, #40 ' X Offset Calculations mov R0, OffsetX and OffsetX, #%111 shl OffsetX, #1 mov R5, #16 sub R5, OffsetX shr R0, #3 add AttribPtr, R0 ' Status Line row count mov R6, #0 ' Visible line loop :ScanLine call #HSync ' Check if the current line is in the top or bottom border cmp LineCounter, _BottomLine wc if_c jmp #:EmptyLine cmp LineCounter, _TopLine wc if_nc jmp #:CheckStat jmp #:DataLine :CheckStat test Status, #1 wz if_z jmp #:EmptyLine mov R0, _TopLine add R0, #10 cmp LineCounter, R0 wc if_nc jmp #:EmptyLine cmp R6, #16 wz if_z jmp #:EmptyLine ' Draw the status line mov VSCL, _VSCL_LeftBorder waitvid COLOR_BORDER, #0 ' Character rendering loop mov VSCL, _VSCL_Character mov R0, #20 ' R0 := Character counter (per line) mov R1, StatusPtr ' R1 := Pointer to status line mov R4, R1 add R4, R0 ' R4 := Pointer to foreground color :StatusChar rdbyte R3, R1 ' R3 := Tile description add R1, #1 ' Advance the attribute pointer for the next character shl R3, #4 ' Multiply by bitmap size to form an address add R3, R6 ' Add the row offset for each tile add R3, TilePtr ' R3 := Address of the bitmap word to read rdword PIXELS, R3 ' Set pixels to the proper data rev PIXELS, #16 ' Reverse bit order to output pixels MSB first rdbyte R3, R4 ' R3 := foreground color add R4, #1 ' Advance the color pointer for the next character mov R2, COLORS shl R2, #8 or R2, R3 ' Only foreground color is set, background remains the same from palette ror R2, #8 test VideoMode, #1 wz if_nz xor R2, phaseflip ' Flip color phase if PAL mode waitvid R2, PIXELS ' Output our video djnz R0, #:StatusChar ' and loop mov VSCL, _VSCL_RightBorder waitvid COLOR_BORDER, #0 ' Output the right border add R6, #2 djnz LineCounter, #:ScanLine ' Next line (note that here LineCounter is always > 0) ' jmp #:EmptyLine ' Draw a data line :DataLine mov VSCL, _VSCL_LeftBorder waitvid COLOR_BORDER, #0 ' Output the left border ' Character rendering loop mov VSCL, _VSCL_Character mov R0, #20 ' R0 := Character counter (per line) mov R1, AttribPtr ' R1 := Pointer to attribute (tile) area :Character rdbyte R2, R1 ' R2 := Tile description long add R1, #1 ' Advance the attribute pointer for the next character test Status, #2 wz if_z shl R2, #5 ' Multiply by bitmap size to form an address if_nz shl R2, #4 add R2, CharacterRows ' Add the row offset for each tile add R2, TilePtr ' R2 := Address of the bitmap word to read rdword PIXELS, R2 ' Set pixels to the proper data cmp OffsetX, #0 wz if_z jmp #:Output shl PIXELS, OffsetX rdbyte R2, R1 test Status, #2 wz if_z shl R2, #5 ' Multiply by bitmap size to form an address if_nz shl R2, #4 add R2, CharacterRows ' Add the row offset for each tile add R2, TilePtr ' R2 := Address of the bitmap word to read rdword R3, R2 shr R3, R5 or PIXELS, R3 :Output rev PIXELS, #16 ' Reverse bit order to output pixels MSB first mov R2, COLORS test VideoMode, #1 wz if_nz xor R2, phaseflip ' Flip color phase if PAL mode waitvid R2, PIXELS ' Output our video djnz R0, #:Character ' and loop mov VSCL, _VSCL_RightBorder waitvid COLOR_BORDER, #0 ' Output the right border ' Calculate the address of the next attribute line add CharacterRows, #2 test Status, #2 wz if_z mov R2, #32 if_nz mov R2, #16 cmp CharacterRows, R2 wz if_z mov CharacterRows, #0 ' Advance to the next line of attributes if the sub-character counter if_z add AttribPtr, #40 ' reaches the end of character, and reset it to 8 djnz LineCounter, #:ScanLine ' Next line (note that here LineCounter is always > 0) ' Empty (border only) lines :EmptyLine mov VSCL, _VSCL_VisibleLine ' Output an entire visible line of BORDER color waitvid COLOR_BORDER, #0 djnz LineCounter, #:ScanLine ' Next line: note that this may be the last one ' VSync mov R0, #$FF wrbyte R0, VSyncPtr test VideoMode, #1 wz if_nz xor phaseflip, phasemask call #VSyncHigh ' VSync procedure: 6 half-lines of HSync-only values (3 lines) call #VSyncLow ' 6 half-lines inverted from the previous ones (3 lines) call #VSyncHigh ' 6 half-lines more of HSync-only values (3 lines) mov R0, #0 wrbyte R0, VSyncPtr jmp #:Frame ' Next frame ' ───────────────────────────────────────── ' Synchronization subroutines ' ───────────────────────────────────────── HSync mov VSCL, _VSCL_FrontPorch waitvid COLOR_BLACK, #0 mov VSCL, _VSCL_SynchronizingPulse waitvid COLOR_SYNC, #0 mov VSCL, _VSCL_BreezeAway waitvid COLOR_BLACK, #0 mov VSCL, _VSCL_ColourBurst test VideoMode, #1 wz if_nz xor phaseflip, phasemask wz ' Swing color burst if PAL if_z mov R0, _Burst1 if_nz mov R0, _Burst2 waitvid R0, #0 mov VSCL, _VSCL_WaitToData waitvid COLOR_BLACK, #0 HSync_Ret ret VSyncHigh mov R0, #6 :Loop mov VSCL, _VSCL_FrontPorch waitvid COLOR_BLACK, #0 mov R1, _VSCL_SynchronizingPulse shr R1, #1 ' Half-length pulses mov VSCL, R1 waitvid COLOR_SYNC, #0 mov VSCL, R1 waitvid COLOR_BLACK, #0 mov VSCL, _VSCL_BackPorch ' BackPorch = BreezeAway + ColourBurst + WaitToData waitvid COLOR_BLACK, #0 mov VSCL, _VSCL_HalfLine waitvid COLOR_BLACK, #0 djnz R0, #:Loop VSyncHigh_Ret ret VSyncLow mov R0, #6 :Loop mov VSCL, _VSCL_FrontPorch waitvid COLOR_BLACK, #0 mov VSCL, _VSCL_HalfLine waitvid COLOR_SYNC, #0 mov VSCL, _VSCL_BackPorch ' BackPorch = BreezeAway + ColourBurst + WaitToData waitvid COLOR_SYNC, #0 mov VSCL, _VSCL_FrontPorch waitvid COLOR_SYNC, #0 mov R1, _VSCL_SynchronizingPulse sub R1, _VSCL_FrontPorch mov VSCL, R1 ' mov VSCL, _VSCL_SynchronizingPulse waitvid COLOR_BLACK, #0 djnz R0, #:Loop VSyncLow_Ret ret ' ───────────────────────────────────────── ' Utility subroutines ' ───────────────────────────────────────── ' Divide R1 by R2 and return the result in R3 with 32 bits of decimal precision ' Input: R1 → Dividend ' R2 → Divisor (it is required that R1 < R2) ' Output: R3 → (R1/R2) << 32 Divide mov R0, #33 :Loop cmpsub R1, R2 wc rcl R3, #1 shl R1, #1 djnz R0, #:Loop Divide_Ret ret phaseflip long $00000000 phasemask long $F0F0F0F0 ' invert hue portion of color ' ───────────────────────────────────────── ' Uninitialized data ' ───────────────────────────────────────── R0 res 1 R1 res 1 R2 res 1 R3 res 1 R4 res 1 R5 res 1 R6 res 1 COLORS res 1 PIXELS res 1 _VCFG_PinMask res 1 _VCFG_PinGroup res 1 _VCFG_Mode res 1 _PortMask res 1 _ClockFreq res 1 _VSCL_Character res 1 _VSCL_VisibleLine res 1 _VSCL_HalfLine res 1 _VSCL_LeftBorder res 1 _VSCL_RightBorder res 1 _VSCL_FrontPorch res 1 _VSCL_SynchronizingPulse res 1 _VSCL_BreezeAway res 1 _VSCL_ColourBurst res 1 _VSCL_BackPorch res 1 _VSCL_WaitToData res 1 _VerticalLines res 1 _TopLine res 1 _BottomLine res 1 _Burst1 res 1 _Burst2 res 1 LineCounter res 1 CharacterRows res 1 TilePtr res 1 AttribPtr res 1 ScreenPtr res 1 StatusPtr res 1 VSyncPtr res 1 PAddr res 1 PCtr res 1 OffsetX res 1 OffsetY res 1 Status res 1 VideoMode res 1 fit