so why does it move the display, because on a real zx81 Vsync does not reset/hold the hsync generator it continues to run just like it always does, only 2 things cause the Hsync to reset, a roll over from count 207 to 0 and an int ack (M1 low, and IORQ low) the upshot is that because of the extra time spent manipulating the stack we get an extra hsync that the character generator line counter counts after the vsync has ended. so on a real zeddy to counter this i decrease the number of blank lines in the top margin by 1 hence the entire display moves up by 1 line, i also increase the number of blank lines in the lower margin by 1 so overall display generation frequency remains the same.
i'll post the code later, im on the wrong PC at the moment.
Code: Select all
;=================================================== ;= = ;= ZX81 interrupt handler, By Andrew Rea. = ;= = ;= due to the complex way the ZX81 generates its = ;= display during 'SLOW' mode if you wish to = ;= carry out a regular task at every interrupt = ;= you must manipulate the IX register which is = ;= used as a jump vector within the ZX81 rom. = ;= = ;= But to keep a steady display you must also = ;= either copy the rom code to generate the display= ;= or write your own code. = ;= = ;= this solution uses mostly the original rom. = ;= = ;= it also saves and restores the IY register, = ;= which is use in the rom code, normally you = ;= should not run USER code that uses the IY = ;= but since it is saved things seem to work ok = ;= = ;=================================================== ;writtne for tasm assembler, with undocumented instructions ;supported. ; ; Compile with "tasm -80 -b input_source_code.asm output_binary_file.bin ; ; #define db .byte ; TASM cross-assembler definitions #define DB .byte #define dw .word #define DW .word #define ds .block #define DS .block #define org .org #define ORG .org #define end .end #define END .end ;========================= ;= = ;= you can change the = ;= following to suit the = ;= address of the PT3 = ;= player module. = ;= = ;========================= PLAYER_START .EQU $2000 PLAYER_PLAY .EQU $2002 PLAYER_MUTE .EQU $2005 ;this will be compiled as a ZX81 .p file org $4009 ;= System variables ============================================ db 0 ;VERSN dw 0 ;E_PPC dw dfile ;D_FILE dw dfile+1 ;DF_CC dw var ;VARS dw 0 ;DEST dw var+1 ;E_LINE dw last-1 ;CH_ADD dw 0 ;X_PTR dw last ;STKBOT dw last ;STKEND db 0 ;BERG dw membot ;MEM db 0 ;not used db 2 ;DF_SZ dw 1 ;S_TOP db $FF,$FF,$FF ;LAST_K db 55 ;MARGIN dw dfile ;NXTLIN dw 0 ;OLDPPC db 0 ;FLAGX dw 0 ;STRLEN dw $0C8D ;T_ADDR dw 0 ;SEED dw $FFFF ;FRAMES db 0,0 ;COORDS db $BC ;PR_CC db 33,24 ;S_POSN db %01000000 ;CDFLAG ds 33 ;Print buffer membot: ds 30 ;Calculator´s memory area ds 2 ;not used ;= First BASIC line, asm code ================================== line0: db 0,0 dw dfile-$-2 db $ea ;zx81 token REM ;= Your code should follow this file ========================== ;= ;= This is the intterupt handler CONTROL: DB $00 ;used as a way of getting a signal into the interrupt handler ; ;on initialise ' call start ' ; ; CONTROL = 0 ; ; 0~ DON'T CALL PLAYER DURRING INT ; BUT SILENCE SOUND. ; ; 1 ~ CALL PLAYER DURRING INT ; ; 255 ~ SILENCE SOUND AND ; STOP INTTERUPT HANDLER PT3FILE_ADDRESS: DW $8000 ;DEFAULT ADDRESS FOR PT3 FILE, ALTER THIS ;TO THE ADDRESS OF YOUR PT3 FILE ;THEN START THE HANDLER START: XOR A ;ZERO LD (CONTROL),A ;STORE IN CONTROL BYTE ;AFTER INITIALISING ;PLAYER WILL NOT PLAY ;UNTIL USER ALTERS THIS BYTE ;TO = 1 GO_HANDLER: TEST_X: ;LD A,IXL ;margin is still in top ; DB $DD,%01111101 ; CP $81 ; JR NZ,TEST_X ;top of screen ;LD A,IXL TEST_X_2: DB $DD,%01111101 ;test for bottom CP $8F JR NZ,TEST_X_2 ; ;AND THEN LOAD IX REGISTER SO OUR LD IX,END_OF_BOTTOM_MARGIN ;HANDLER IS USED INSTEAD INIT_PLAYER: LD HL,(PT3FILE_ADDRESS) CALL PLAYER_START ;INITIALISE THE PLAYER RET ;BACK TO USERS PROGRAM ;============================= ;= = ;= DISPLAY INTTERUPT HANDLER = ;= = ;================================================== ;= = ;= The display intturupt handler has 2 main parts = ;= = ;= during user program execution the NMI int are = ;= processed by incrementing the a' register (from= ;= the af' pair ) and when zero is reached the = ;= main registers AF, BC, DE, HL are pushed onto = ;= the stack and a jump is made to address held = ;= in IX. The nmi's are active during the 'blank' = ;= area's at the top and bottom of the diaply = ;= these are the top and bottom margins. ;= = ;= So by altering IX we can take control of the = ;= display intterupt cycle = ;= = ;= 1) create a vsync signal and read keyboard. = ;= 1a) start top margin, and resume user program. = ;= = ;= 2) generate the display. = ;= 2a) start bottom margin, and resume user prog- = ;= gram = ;= = ;================================================== ;NOW THE TRICKY BIT ! ; ;========== ;= = ;= PART 1 = ;= = ;========== END_OF_BOTTOM_MARGIN: ;NMI ROUTINE JUMPS HERE AT THE END OF THE BOTTOM MARGIN. ;THIS REPLACES THE $028F JUMP. ;when we get here the NMI routine has already put AF, BC, DE, HL on the stack ;HL on last ; ; sp ---> HL ; DE ; BC ; AF ;nmi is also turned off ;but i want it to return here so i can alter IX again. ;so beacuase the rom routine will pop these off before RET-ing ;i need to get my return address under 4 words on the stack ; LD HL, AFTER_VSYNC ;the address i wish to return to ;after the call to rom. ;RIGHT GONNA TRY A DIFFERENT APPROACH TO THIS. ; ;four items on stack... POP AF ;HL IN AF, leaves 3 POP DE ;DE IN DE, leaves 2 POP BC ;BC IN BC, leaves 1 ;AF STILL ON STACK EX (SP),IY ;swap IY and final value on stack ;IY it could be any value by now... PUSH HL ;THE RETURN ADDRESS where execution will ;continue after ROM code has run PUSH IY ;AF BACK ON STACK PUSH BC ;BC BACK ON STACK PUSH DE ;DE BACK ON STACK PUSH AF ;HL BACK ON STACK ;so now we have IY on the stack then our return address and then the original ;4 registers pushed by the NMI routine. LD IY,$4000 ;SET IY FOR ROM CODE TO RUN CORRECTLY JP $0229 ;THIS ROM ROUTINE NORMALLY POPS THE REGISTERS AND ;RETURNS TO USER CODE EXECUTION ;WITH IX LOADED $0281, READY FOR NEXT VIDEO INT. ;BUT NOW IT RETURNS HERE, BECAUSE I SHOVED EXTRA STUFF ;ON THE STACK AFTER_VSYNC: START_OF_TOP_MARGIN: ;so we should have returned here ;with just IY left on the stack POP IY ;NOW ALL THIS PUSHING AND POPPING MEANS WE HAVE MISSED THE FIRST HSYNC ;REMEBER HSYNC ARE NOT RESET BY VSYNC, BUT A NORMAL INT ACK. ;SO NOW ALL THE CHARACTERS ARE MOVED UP 1 PIXEL LINE (LINE COUNTER IN ula) ;SO MUST DECREMENT THE NMI INTERUPT COUNTER (IT ACTUALLY COUNTS UP) ; ;Ha Ha, I've known for a while that eightyone does not correctly ;handle the hsync timming. so the character are displayed wrong on eightyone. ; ;wonder if this will work on any other emulator correctly ? ;vb81xur displays it ok, but my test for IXL going to $8f doesn't seem ;to work on VB81xur ;originally was turning off nmi whilst adjusting the a' register ;but tried it without and it works just fine on real ZX81 ;could be another place for emulators to fall over ;-) ; out ($fd),a ;TURN OFF nmi JUST INCASE AN NMI HAPPENS WITH THE ex af,af' ;THE WRONG AF PAIR ACTIVE inc a ;DECREMENT THE LINE COUNTER (YES IT COUNTS UP) ex af,af' ;SWAP BACK TO NORMAL AF ; out ($fe),a ;AND TURN ON THE nmi AGAIN RESET_IX_VECT: ;FINALLY WE ARE READY TO RETURN TO USER CODE LD IX,END_OF_TOP_MARGIN ;SET THE IX REG READY FOR NEXT END OF TOP MARGIN RET ;RETURN TO USER PROGRAM END_OF_TOP_MARGIN: ;========== ;= = ;= PART 2 = ;= = ;========== ;NMI ROUTINE HAS NOW JUMPED HERE AT THE END OF THE TOP MARGIN ;NMI'S ARE OFF ;STACK IS AS BEFORE ;MUST PRETTY MUCH COPY ROM ELSE TOP FEW LINES OF DISPLAY GET CORRUPTED LD A,R ; SAME AS LD BC,$1901 ; ROM LD A,$F5 ; CODE CALL $02B5 ; BUT.... ;RETURNS HERE WHEN DISPLAY IS DONE START_OF_BOTTOM_MARGIN PUSH IY ;SAVE iy ONCE AGAIN LD a,($4028) ; get the margin from sys-vars NEG ; Negate ; normal rom increments (one less margin line) ;INC A ; MISSING THIS INC COMPENSATES FOR THE ONE ; ONE LESS ABOVE EX AF,AF' ; place negative count of blank lines in A' LD IX,END_OF_BOTTOM _MARGIN ;AND MAKE SURE IX IS LOADED OUT ($FE),A ; enable the NMI generator. ; THATS PRETTY MUCH THE INTERRUPT HANDLER ; main registers are still on the stack with IY on top. ;LETS PLAY SOME MUSIC ;-) ; IF (CONTROL) = 255 THEN ix LOADED WITH NORMAL ROM VECTOR (STOPS HANDLER RUNNING ) ; (NEXT TIME NMI ROUTINE ) ; (DOES A JP (IX) after ) ; (after the top margin. ) ; AND ALSO MUTES PLAYER. ; ; IF (CONTROL) = 0 THEN MUTES PLAYER. ; ; IF (CONTROL) = 1 THEN CALLS PLAYER EVERY 1/50TH SECOND ; ; IF (CONTROL) = 'other value' THEN DO NOTHING; SO SOUND CHIP WILL CONTINUE TO SOUND... ; LAST DATA SENT TO IT... LD A,(CONTROL) INC A ;TEST FOR 255 JR NZ,DONT_EXIT_INTS ;ONLY 255 WILL RSULT IN ZERO EXIT_INTS: LD IX,$028F ;NORMAL ix VECTOR INC A ;TRICK NEXT TEST DONT_EXIT_INTS: exx push hl exx DEC A ;TEST FOR ZERO CALL Z,PLAYER_MUTE ;IF IT WAS 0 RESULT WILL BE ZERO DEC A ;OR 1 CALL Z,PLAYER_PLAY ;OTHER VALUES WILL DO NOTHING exx pop hl exx ;HERE ; THATS IT FOR THE PLAYER BIT. POPS: pop IY ;restored from entry to int routine POP HL ;AND FINNALY RESTORE USERS REGISTERS POP DE ; POP BC ; POP AF ; ;AND ENDS HERE RET ; BACK TO USER PROG. ;===================================== ;= your code should end here =================================== ;zx81end.asm ;used at end of file.... db $76 ;N/L end of line 0 ;- Display file -------------------------------------------- dfile: db $76 db $76,$76,$76,$76,$76,$76,$76,$76 db $76,$76,$76,$76,$76,$76,$76,$76 db $76,$76,$76,$76,$76,$76,$76,$76 ;- BASIC-Variables ---------------------------------------- var: db $80 ;- End of program area ---------------------------- last: end
The challenge was to build a ZX81-compatible with as few standard-ICs as possible. I knew I omitted some shifters in the sync-Logic, but why..? It will take me a while to understand that, if ever...
Thats a nice home brew you got there, gow many chips is it ? do you still have the schematics for it ?
But This is what i was expecting from homebrew machines, if my theory is correct, i did notice however that the first scanline is shifted to the left also.
The Zeddy-board has 14 Chips + RAM+ROM+CPU. Schematics can be found here:
http://forum.tlienhard.com/phpBB3/viewt ... =331#p1647 (sorry, mostly in German, but you will find the ZX81_Clone_Schem.zip file with the schematics).
I already know that the HSync-Timing differs, making the board run few more cycles between the NMIs. Intermediately I improved that timing ( ZX81_Clone_2_Schem.zip, still 14 TTLs) but the wiring on the board became too complicated, so I reverted to the initial version. Thats the downside of using standard-logic - compared to FPGAs debugging is somewhat harder.
it's a simple fix for CPLD or FPGA....
It all revolves around the Hsync counter, Vsync does NOT STOP the hsync counter NOR does it reset it... only 2 things reset Hsync counter
1) a roll around from max-count back to zero
2) and int-ack (M1 and IORQ both low).
But also the hsyncs are generated 16 cycles after the reset.
i use the following code in my CPLD ULA which as far as i can tell works exactly like the real thing (yet to be proved wrong )
Code: Select all
reg [7:0]hc; wire hc_reset; assign hc_reset = (!m1 & !iorq); always @ (negedge slow_clock) begin if (hc_reset | hc == 206) begin hc <= 8'd0 ; end else begin hc <= hc + 8'd1 ; end end wire hsync; assign hsync = !hc & !hc & !hc & hc ;
It's a bit more trouble to fix 74xx series clones but not impossible
P.s That a really nice clone machine that Bodo has made
- Posts: 108
- Joined: Mon May 23, 2011 2:10 pm
- Location: A bit north of Cardiff, Wales.
That is, reset on INTACK then 16 cycle pulse 16 cycles after the INTACK ...that's what I measured on the real ZX81 when I did the previous version.
I want to keep the chip count no higher than 6 (currently 5 chips for the VSYNC reset version). I'll keep on trying, and will post if successful