A new ZX81 Emulator

Emulator and emulator development specific topics
Post Reply
Bill H
Posts: 155
Joined: Sat Nov 27, 2010 6:05 pm

A new ZX81 Emulator

Post by Bill H »

Hello All,

I am currently working on a ZX81 emulator with a twist. This one runs in on a Coleco Adam. I have a good chunk of it working, I get it up to the 'K" prompt but I am stuck with keyboard input. Since the Adam is a Z80 system with free RAM in the right area I am able to load the ZXROM at 0h, my emulator code at 2000h and have 16k free memory from 4000h - 7999h (i will probably use 32k when done). I have gone into the ZX81 ROM and nop'ed all IN's and OUT's for now and am using the NMI to jump to my emulator which copies the D_FILE to the Adam's video processor and allows me to debug as needed. I have been trying to replace the keyboard scanning routine with my own. What I am doing is leaving the ZXROM when the keyboard is scanned to my emulator so I can return a keypress if there is one. That is where I am lost. The scan routine is supposed to exit with H holding the column press and L holding the row pressed but I just can't seem to figure out exactly what the ZXROM expects H & L to be. Here is the code in question:

Code: Select all

; ----------------------------------
; THE 'KEYBOARD SCANNING' SUBROUTINE
; ----------------------------------
; The keyboard is read during the vertical sync interval while no video is 
; being displayed.  Reading a port with address bit 0 low i.e. $FE starts the 
; vertical sync pulse.

;; KEYBOARD
L02BB:  LD      HL,$FFFF        ; (16) prepare a buffer to take key.
        LD      BC,$FEFE        ; (20) set BC to port $FEFE. The B register, 
                                ;      with its single reset bit also acts as 
                                ;      an 8-counter.
        IN      A,(C)           ; (11) read the port - all 16 bits are put on 
                                ;      the address bus.  Start VSYNC pulse.
        OR      $01             ; (7)  set the rightmost bit so as to ignore 
                                ;      the SHIFT key.

;; EACH-LINE
L02C5:  OR      $E0             ; [7] OR %11100000
        LD      D,A             ; [4] transfer to D.
        CPL                     ; [4] complement - only bits 4-0 meaningful now.
        CP      $01             ; [7] sets carry if A is zero.
        SBC     A,A             ; [4] $FF if $00 else zero.
        OR      B               ; [7] $FF or port FE,FD,FB....
        AND     L               ; [4] unless more than one key, L will still be 
                                ;     $FF. if more than one key is pressed then A is 
                                ;     now invalid.
        LD      L,A             ; [4] transfer to L.

; now consider the column identifier.

        LD      A,H             ; [4] will be $FF if no previous keys.
        AND     D               ; [4] 111xxxxx
        LD      H,A             ; [4] transfer A to H

; since only one key may be pressed, H will, if valid, be one of
; 11111110, 11111101, 11111011, 11110111, 11101111
; reading from the outer column, say Q, to the inner column, say T.

        RLC     B               ; [8]  rotate the 8-counter/port address.
                                ;      sets carry if more to do.
        IN      A,(C)           ; [10] read another half-row.
                                ;      all five bits this time.

        JR      C,L02C5         ; [12](7) loop back, until done, to EACH-LINE

;   The last row read is SHIFT,Z,X,C,V  for the second time.

        RRA                     ; (4) test the shift key - carry will be reset
                                ;     if the key is pressed.
        RL      H               ; (8) rotate left H picking up the carry giving
                                ;     column values -
                                ;        $FD, $FB, $F7, $EF, $DF.
                                ;     or $FC, $FA, $F6, $EE, $DE if shifted.

;   We now have H identifying the column and L identifying the row in the
;   keyboard matrix.

;   This is a good time to test if this is an American or British machine.
;   The US machine has an extra diode that causes bit 6 of a byte read from
;   a port to be reset.

        RLA                     ; (4) compensate for the shift test.
        RLA                     ; (4) rotate bit 7 out.
        RLA                     ; (4) test bit 6.

        SBC     A,A             ; (4)           $FF or $00 {USA}
        AND     $18             ; (7)           $18 or $00
        ADD     A,$1F           ; (7)           $37 or $1F

;   result is either 31 (USA) or 55 (UK) blank lines above and below the TV 
;   picture.

        LD      ($4028),A       ; (13) update system variable MARGIN

        RET                     ; (10) return
And my modified section with my changes marked with "; @#@":

Code: Select all

; ----------------------------------
; THE 'KEYBOARD SCANNING' SUBROUTINE
; ----------------------------------
; The keyboard is read during the vertical sync interval while no video is 
; being displayed.  Reading a port with address bit 0 low i.e. $FE starts the
; vertical sync pulse.

;; KEYBOARD
L02BB:
        call  jpKEY             ; @#@ Call the zx81emu keyboard routine
        jr    GotKey            ; @#@ Skip all the scanning stuff
        nop                     ; @#@ filler
        ; @#@ LD      HL,$FFFF        ; (16) prepare a buffer to take key.
        ; @#@ LD      BC,$FEFE        ; (20) set BC to port $FEFE. The B register,
        ; @#@                         ;      with its single reset bit also acts as
        ; @#@                         ;      an 8-counter.
        .fill 2,0                     ; @#@ Pad to fill for the in
        ; @#@ IN      A,(C)           ; (11) read the port - all 16 bits are put on
                                ;      the address bus.  Start VSYNC pulse.
        OR      $01             ; (7)  set the rightmost bit so as to ignore 
                                ;      the SHIFT key.

;; EACH-LINE
L02C5:  OR      $E0             ; [7] OR %11100000
        LD      D,A             ; [4] transfer to D.
        CPL                     ; [4] complement - only bits 4-0 meaningful now.
        CP      $01             ; [7] sets carry if A is zero.
        SBC     A,A             ; [4] $FF if $00 else zero.
        OR      B               ; [7] $FF or port FE,FD,FB....
        AND     L               ; [4] unless more than one key, L will still be 
                                ;     $FF. if more than one key is pressed then A is 
                                ;     now invalid.
        LD      L,A             ; [4] transfer to L.

; now consider the column identifier.

        LD      A,H             ; [4] will be $FF if no previous keys.
        AND     D               ; [4] 111xxxxx
        LD      H,A             ; [4] transfer A to H

; since only one key may be pressed, H will, if valid, be one of
; 11111110, 11111101, 11111011, 11110111, 11101111
; reading from the outer column, say Q, to the inner column, say T.

        RLC     B               ; [8]  rotate the 8-counter/port address.
                                ;      sets carry if more to do.
        .fill 2,0                     ; @#@ Pad to fill for the in
        ; @#@ IN      A,(C)           ; [10] read another half-row.
        ; @#@                         ;      all five bits this time.

        JR      C,L02C5         ; [12](7) loop back, until done, to EACH-LINE

;   The last row read is SHIFT,Z,X,C,V  for the second time.

        RRA                     ; (4) test the shift key - carry will be reset
                                ;     if the key is pressed.
        RL      H               ; (8) rotate left H picking up the carry giving
                                ;     column values -
                                ;        $FD, $FB, $F7, $EF, $DF.
                                ;     or $FC, $FA, $F6, $EE, $DE if shifted.

GotKey:                         ; @#@ Skip point

;   We now have H identifying the column and L identifying the row in the
;   keyboard matrix.

;   This is a good time to test if this is an American or British machine.
;   The US machine has an extra diode that causes bit 6 of a byte read from
;   a port to be reset.

        RLA                     ; (4) compensate for the shift test.
        RLA                     ; (4) rotate bit 7 out.
        RLA                     ; (4) test bit 6.

        SBC     A,A             ; (4)           $FF or $00 {USA}
        AND     $18             ; (7)           $18 or $00
        ADD     A,$1F           ; (7)           $37 or $1F

;   result is either 31 (USA) or 55 (UK) blank lines above and below the TV 
;   picture.

        LD      ($4028),A       ; (13) update system variable MARGIN

        RET                     ; (10) return
It is working somewhat in that it is storing what I am sending back in HL to LAST_K, Currently I ma just decrement H & L trying to get a rise out of it:

Code: Select all

               ; ******************************************
               ;
               ; Keyboard handler
               ;
               ; ******************************************

KEY.Handler: .module KEY.Handler

               ld      bc,0fefeh

               ld      hl,(_hl)
               dec     l
               ld      (_hl),hl
               ld      a,l
               cp      255
               jr      nz,_cont1
               dec     h
               ld      (_hl),hl
_cont1:
               ld      hl,(_hl)
               ret
_hl:           .dw     65535

User avatar
Andy Rea
Posts: 1553
Joined: Fri May 09, 2008 2:48 pm

Re: A new ZX81 Emulator

Post by Andy Rea »

When we reach the RET of routine keyboard HL will be something of the following

Code: Select all

 1 - 2 - 3 - 4 - 5 -> register L = F7
 0 - 9 - 8 - 7 - 6 -> register L = EF
 Q - W - E - R - T -> register L = FB
 P - O - I - U - Y -> register L = DF
 A - S - D - F - G -> register L = FD
N/L- L - K - J - H -> register L = BF
SHF- Z - X - C - V -> register L = FE
SPC- . - M - N - B -> register L = 7F

REGISTER H ->
FD - FB- F7- EF- DF
H under the column of the key pressed and L along the row of the key pressed.. Shift is a special case and will reset ( zero ) bit zero of H when pressed in conjunction with another key. not sure what happens when multiple keys are pressed...
AFK

Bill H
Posts: 155
Joined: Sat Nov 27, 2010 6:05 pm

Re: A new ZX81 Emulator

Post by Bill H »

Thanks again - I see it is storing it in LAST_K but I do not have any change on the screen so I have to dig a little deeper. This is what the system variables look like:
system variables.PNG

dr beep
Posts: 1283
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: A new ZX81 Emulator

Post by dr beep »

Andy Rea wrote:
Tue Feb 25, 2020 7:46 pm
not sure what happens when multiple keys are pressed...
Multiple keys return a NO KEY PRESSED, it ignores the input.

dr beep
Posts: 1283
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: A new ZX81 Emulator

Post by dr beep »

Bill H wrote:
Tue Feb 25, 2020 1:12 pm
Hello All,

I am using the NMI to jump to my emulator which copies the D_FILE to the Adam's video processor and allows me to debug as needed.
That is the same way I coded SAM81, the ZX81 emulator for the SAM Coupe. It was coded in 1/2 day using the display from the ZX81EMUL for the ZX Spectrum I was developping then... 1996, long time ago.

This is a perfect method for a lowres emulator. It can make your emulator run full speed.
Later I coded SAM2ZX81, an emulator also capable for a default method of hires but it is slower.

At this moment I am coding 1K lowres games, Normally these should run without problems... but......
I have found a way to reduce the screensize even smaller than 25 bytes or skipping last lines to be added as empty lines

This is easy to add in a lowres emulator. You will display space to fill a line when a line still has room (compressed screen) and then go to net line.
With my trick, opcode E9 JP (HL), the ZX81 will fill the screen without newlines.
So if you see a E9 you decrease the screenpointer by 1, fill line with spaces like a compressed screen and add 1 to screenpointer (same as LF).
The next line will point to sam JP (HL) and will fill another line until screen is displayed.

dr beep
Posts: 1283
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: A new ZX81 Emulator

Post by dr beep »

Problems you might get

1) ROM-bug. Can you protect #0000-#1FFF against writing? If not, first addresses will be overwritten.
2) Reading IN-port (result can be different from machine you run or ZX81. Can be fixed with a scan and RST.
3) My games with "hidden" data on screen, can be emulated with correct display.
4) Loading games and starting... not to hard to solve
5) Fixing OUT-commands which can interfere with original machine.
6) Disable original display method from ZX81.

Bill H
Posts: 155
Joined: Sat Nov 27, 2010 6:05 pm

Re: A new ZX81 Emulator

Post by Bill H »

dr beep wrote:
Tue Feb 25, 2020 10:19 pm
Problems you might get

1) ROM-bug. Can you protect #0000-#1FFF against writing? If not, first addresses will be overwritten.
2) Reading IN-port (result can be different from machine you run or ZX81. Can be fixed with a scan and RST.
3) My games with "hidden" data on screen, can be emulated with correct display.
4) Loading games and starting... not to hard to solve
5) Fixing OUT-commands which can interfere with original machine.
6) Disable original display method from ZX81.
I have nop'ed all the in and out commands and am replacing them with calls to the adam emulator code. I have disabled the original NMI display routines since I use the NMI to jump to my code to process the DFILE. I can't protect the ram so I may have to modify the rom

dr beep
Posts: 1283
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: A new ZX81 Emulator

Post by dr beep »

Help offered......

Bill H
Posts: 155
Joined: Sat Nov 27, 2010 6:05 pm

Re: A new ZX81 Emulator

Post by Bill H »

Ok so I have the keyboard working great as long as I wait for a key on the Adam. I am not sure what col / row I should return if no key is pressed. I see it starts out with H = FFh and L = FFh and I tried to return that is there is no key press but the ZX81 doesn't like it. Any tips on this?

Edit:

I forgot I had to nop the debounce catch in DISPLAY-2 to get it to recognize my keys I am returning and I see that part of the code also handles FFh as being no key press.. So I have to figure out something

dr beep
Posts: 1283
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: A new ZX81 Emulator

Post by dr beep »

I think the only thing you need to do is to emulate a correct read of a IN port. After that the original code can do the job after
disabling the calls to upper memory for display (done the same on the SAM-coupe)

This needs a routine that checks the keys read by each reset bit from the ports.

I don't know (or couldn't find) how the ADAM reads a keyboard, but that must be translated in the right read of IN.

You can capture that by checking all IN command (either IN A,(254) or IN A,(C) will mostly do) and alter that code with a useable RST
(RST 8 can have room for that purpose with alteration in ROM).

Can you help me with info regarding the reading of ADAM-keyboard?

Post Reply