The ZX81 High Res Core Routine - As used by Rock Crush and Dans Revenge ----------------------------------------------------------------------- Introduction ------------ As a lot of people have been asking me historically, as to how the high res routines actually work, in response, I have decided to detail and release the following annotated Z80 code, which I hope helps: Revision 4.0 - Steven McDonald - December, 2020 - Tightened Timing! Technical Notes: ---------------- A call to HI-RES will start generating a high res display. The HRESGEN routine address is loaded into the IX register and will take over the display output - 50 times a second (UK). A further call to LO-RES will start to generate the normal lo res display again. The Z80 "I" register normally holds the start of the ZX81 character table / 256 (i.e. 30 * 256 = 7680). As nearly all of the ROM characters have a blank line at the top, the high res routine changes this to 12 which is at ROM address 3072 which I found contains much more interesting bit-patterns than other values which can be used. Due to hardware limitations, the "I" register can ONLY point into the ROM - and not RAM (hence the reason for only pseudo high res - sigh!). The high res routine works by fooling the ULA into thinking it is only on the top line of a character - every time! It cleverly does this by resetting and restarting the ULA ROW counter at every VSYNC pulse with the "IN A,(C)" and "OUT (FF),A" code sequence. This allows the same row of patterns to be used for every one of the 192 tv lines. As this procedure is carried out for every tv line, true high res is achieved vertically - but not horizontally, as this depends on the limited bit patterns stored at the location pointed to by: (the "I" register * 256 + 8 * the value of the "character" stored in the display buffer) Phew! This high res display buffer is stored at (E71E+0021) - 8000 i.e. at address 673F - in this example routine. It consists of 32 decimal display characters or "patterns", followed by a 201 decimal byte which just happens to be the Z80 "RET" opcode. These "patterns" can only be in the range (0 - 63) i.e. BIT 6 must be zero. BIT 7 is used to invert the video signal before it is ouput to the screen via the ULA shift register. This is repeated 192 decimal times for the complete high res display buffer! In lo res, each line ends with a 118 decimal byte - which is the Z80 opcode, "HALT" - which conveniently waits for the next interrupt! Basically, any byte with BIT 6 set stored in the display buffer, tells the ULA to stop outputting tv data and execute that instruction. As it happens "RET" also has BIT 6 set so is executed, thus cleverly returning control to the high res display routine! These routines work on a real 16K ZX81 and provide pseudo high res displays. The ZX81 emulator, "XTENDER" also follows what is going on, by trapping an immediate change of value to the IX register pair and kicking in its own high res screen handler to generate the correct picture. Therefore, under the emulator, this high res routine is not actually executed as such - just cleverly emulated! "XTENDER" first tries to emulate the pseudo high res by looking for the characteristically shaped 6k display buffer in the bottom 16k of RAM and outputting it to the PC screen appropriately. If it cannot locate this display buffer, normal resolution will be switched back in again. The Routines ------------ HI-RES: HALT ; Wait for an interrupt - used to sync the changeover! LD A,(FRAMES) ; Wait LD C,A ; for the SYNC1: LD A,(FRAMES) ; next CP C ; new JR Z SYNC1 ; tv frame - used to sync the changeover! LD A,0C ; Change the value of the "I" Register - LD I,A ; so the top line chosen is more interesting! LD IX,HRESGEN ; Pointer to the replacement high res routine set! RET ; Return LO-RES: HALT ; Wait for an interrupt - used to sync the changeover! LD A,(FRAMES) ; Wait LD C,A ; for the SYNC2: LD A,(FRAMES) ; next CP C ; new JR Z SYNC2 ; tv frame - used to sync the changeover! LD A,1E ; Reset the "I" Register LD I,A ; to the ROM default (30 decimal) LD IX,0281 ; Pointer to the ROM display routine set! RET ; Return HRESGEN:LD HL,E71E ; Start address of (HRES DFILE - 33) + 32768 ; i.e. Set BIT 15 of address for ULA LD DE,0021 ; Amount to add each time for address of next line DI ; Disable maskable interrupts LD C,FE ; The ULA port address LD B,16 ; Delay to sync the tv signal SYNC3: DJNZ SYNC3 ; Sync it! LD B,C0 ; The amount of hi res lines: 192 = 8*24 GENLINE:IN A,(C) ; Fool the ULA into thinking it is only on the OUT (FF),A ; top "line" of a character by resetting the ULA ROW ; counter (exactly timed with every tv VSYNC pulse) ADD HL,DE ; Calculate next screen address CALL ULAOUT ; and actually "jump" to it! DEC B ; Decrease counter JP NZ GENLINE ; Repeat until all done CALL 0292 ; ROM housekeeping routines - must be called! CALL 0220 ; ROM housekeeping routines - must be called! LD IX,HRESGEN ; Reload the hi res handler address as the ROM ; routines above reset it! JP 02A4 ; Give control back to the ROM for now ULAOUT: JP (HL) ; Yes, actually, jump into the display file! - ; The ULA will handle the rest - the Z80 "RET" ; op-code has BIT 6 SET (C9), so will be ; "executed" and control will return to the ; above "calling routine" - honest! Timing Refinement: It has been noted over the years, especially on modern monitors/TVs and some really accurate ZX81 emulators, that the above routine comes in exactly 1 scan line later than the standard Sinclair video signal did. This was probably unnoticable back in the day as the TV signal used to jump on changeover between modes as the syncing wasn't done a the exact right time either. For example the above routine waits for the next TV Frame before changing modes from LO-RES to HI-RES. The original Rock Crush didn't even bother to do this! Why 1 scan line later? Basically the first delay at SYNC3 was too long. Consider this perfectly timed alternative... Now the HI-RES and LO-RES modes and changeovers are perfectly in Sync! ; HRDRIVER - New timing to come in one scan line earlier which makes transition from HIRES to LORES seamless. HI-RES: HALT ; Wait for an interrupt - used to sync the changeover! LD A,(FRAMES) ; Wait LD C,A ; for the SYNC1: LD A,(FRAMES) ; next CP C ; new JP Z SYNC1 ; tv frame - used to sync the changeover! JP is slightly quicker! LD A,0C ; Change the value of the "I" Register - LD I,A ; so the top line chosen is more interesting! LD IX,HRESGEN ; Pointer to the replacement high res routine set! RET ; Return LO-RES: HALT ; Wait for an interrupt - used to sync the changeover! LD A,(FRAMES) ; Wait LD C,A ; for the SYNC2: LD A,(FRAMES) ; next CP C ; new JP Z SYNC2 ; tv frame - used to sync the changeover! JP is slightly quicker! LD A,1E ; Reset the "I" Register LD I,A ; to the ROM default (30 decimal) LD IX,0281 ; Pointer to the ROM display routine set! RET ; Return HRESGEN:DI ; Disable maskable interrupts LD B,04 ; Delay counter. This delay routine helps time active screen generation to exact T-State level SYNC3: INC HL ; Dummy instruction for timing loop DJNZ SYNC3 ; Timing delay until time to output the first scan line later below (Syncs with hardware HSYNC pulses) LD B,00 ; Yet another 7 T-State Dummy instruction LD BC,C0FE ; B = 192 (the number of display lines) and C = 254 (the port number, a read from which will begin a HSync pulse) LD HL,E71E ; The address of the hi-res display file (-33), with bit 15 set to point to the shadow copy in high memory LD DE,0021 ; There are 32 characters + a terminator per line. Load into DE register pair GENLINE:IN A,(C) ; Fool the ULA into thinking it is only on the OUT (FF),A ; top "line" of a character by resetting the ULA ROW ; counter (exactly timed with every tv VSYNC pulse) ADD HL,DE ; Calculate next screen address CALL ULAOUT ; and actually "jump" to it! DEC B ; Decrease counter JP NZ GENLINE ; Repeat until all done CALL 0292 ; ROM housekeeping routines - must be called! CALL 0220 ; ROM housekeeping routines - must be called! LD IX,HRESGEN ; Reload the hi res handler address as the ROM ; routines above reset it! JP 02A4 ; Give control back to the ROM for now ULAOUT: JP (HL) ; Yes, actually, jump into the display file! - ; The ULA will handle the rest - the Z80 "RET" ; op-code has BIT 6 SET (C9), so will be ; "executed" and control will return to the ; above "calling routine" - honest! This also has the benefit of slightly more CPU time available to the user, as effectively we aren't outputting that extra TV scan line anymore! Support ------- If you have any further questions about these routines, contact me at the email address below: support@rock-crush.com