apple1emu-SAM/apple1emu.asm

665 lines
23 KiB
NASM
Raw Normal View History

; Apple 1 emulator for SAM Coupe, by Simon Owen
;
; Version 1.2 (5/9/2008)
2007-04-02 13:21:49 +00:00
;
; WWW: http://simonowen.com/sam/apple1emu/
base: equ &d000 ; Emulator code shares Apple 1 I/O area
status: equ &f9 ; Status and extended keyboard port
lmpr: equ &fa ; Low Memory Page Register
hmpr: equ &fb ; High Memory Page Register
vmpr: equ &fc ; Video Memory Page Register
keyboard: equ &fe ; Keyboard port
border: equ &fe ; Border port
2007-04-02 13:21:49 +00:00
rom0_off: equ %00100000 ; LMPR bit to disable ROM0
rom1_on: equ %01000000 ; LMPR bit to enable ROM1
vmpr_mode2: equ %00100000 ; Mode 2 select for VMPR
low_page: equ 3 ; LMPR during emulation
screen_page: equ 5 ; SAM display
file_page: equ 6 ; File import text page
m6502_nmi: equ &fffa ; nmi vector address
m6502_reset: equ &fffc ; reset vector address
m6502_int: equ &fffe ; int vector address (also for BRK)
r1onclbc: equ &01ea ; ROM 1 on, call BC
getkey: equ &1cab ; SAM ROM key reading (NZ=got key in A, A=0 for no key)
2007-04-02 13:21:49 +00:00
; PIA registers for keyboard and display I/O
2007-04-02 13:21:49 +00:00
kbd_data: equ &d010 ; keyboard data
kbd_ctrl: equ &d011 ; keyboard control
dsp_data: equ &d012 ; display data
dsp_ctrl: equ &d013 ; display control
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
org base
dump $
autoexec
di
jr start
2007-04-02 13:21:49 +00:00
defs kbd_data-$
; This gap contains the PIA locations from above
defs dsp_ctrl-kbd_data+1
start: ld a,low_page+rom0_off
2007-04-02 13:21:49 +00:00
out (lmpr),a
ld a,vmpr_mode2+screen_page
out (vmpr),a
ld (basic_stack),sp
2007-04-02 13:21:49 +00:00
ld sp,stack_top
call set_sam_attrs ; set the mode 2 attrs so the screen is visible
call setup_im2 ; enable IM 2
ld a,(op_00) ; lowest address used by emulator
ld (rom_write+1),a ; save byte to restore to simulate ROM
2007-04-02 13:21:49 +00:00
reset_loop: ld hl,0
ld (dsp_data),hl ; display ready
ld (kbd_data),hl ; no key available
2007-04-02 13:21:49 +00:00
ld hl,(m6502_reset) ; start from reset vector
ld (reg_pc),hl
ld a,&04 ; interrupts disabled
ld (reg_p),a
2007-04-02 13:21:49 +00:00
ei
call load_state
2007-04-02 13:21:49 +00:00
call execute ; GO!
call save_state
2007-04-02 13:21:49 +00:00
jr reset_loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Utility functions
; Fill SAM mode 2 display attributes to make the screen visible
set_sam_attrs: ld a,screen_page+rom0_off
out (lmpr),a
ld hl,&2000
ld bc,&1844 ; 24 blocks of bright green on black
clear_lp: ld (hl),c
inc l
jr nz,clear_lp
inc h
djnz clear_lp
ld a,low_page+rom0_off
out (lmpr),a
ret
; Scroll the screen up 1 row and clear the bottom line
scroll_screen: ld hl,0
ld d,h
ld e,l
inc h
ld bc,&1700
scroll_lp: FOR 32, ldi ; 32 * LDI
jp pe,scroll_lp
dec h
scroll_clr: ld (hl),0
inc l
jp nz,scroll_clr
ret
; Advance the cursor 1 character, wrapping and scrolling if necessary
advance_chr: ld hl,(cursor_xy)
cp &5f
jr z,back_chr
cp &0a
jr z,advance_line
cp &0d
jr z,advance_line
ld a,l
add a,6
ld l,a
cp &f0
jr c,no_wrap
advance_line: ld l,0
inc h
ld a,h
cp &18
jr nz,no_wrap
dec h
exx
call scroll_screen
exx
no_wrap: ld (cursor_xy),hl
ret
back_chr: ld a,l
sub 6
ld l,a
jr nc,no_wrap
ld l,39*6
ld a,h
sub 1
adc a,0
ld h,a
jr no_wrap
; Map display character from SAM to Apple 1
map_chr: sub &20
ret nc
xor a
2007-04-02 13:21:49 +00:00
ret
; Display character in A at current cursor position
display_chr: add a,a ; * 2
ld l,a
ld h,0
add hl,hl ; * 4
2007-04-02 13:21:49 +00:00
add hl,hl ; * 8
ld de,font_data
add hl,de
ld d,mask_data/256
exx
ld hl,(cursor_xy)
ld a,l
and %00000111
srl l
srl l
srl l
inc l
exx
ld e,a
exx
ld de,&0020
ld b,8
draw_lp: exx
ld c,(hl)
inc l
xor a
cp e
jr z,no_rot
ld b,e
rot_lp: srl c
rra
djnz rot_lp
no_rot: ld b,a
ld a,(de)
inc e
exx
and (hl)
inc l
exx
or c
ex af,af'
ld a,(de)
dec e
exx
and (hl)
exx
or b
exx
ld (hl),a
dec l
ex af,af'
ld (hl),a
add hl,de
djnz draw_lp
ret
; LSB=pixel position (0-240), MSB=display row (0-23)
cursor_xy: defw 0
; Map key symbols from SAM to Apple 1
map_key: cp &0c
jr z,del_key
cp &fc
jr z,tab_key
cp &80
jr nc,ignore_key
cp &61 ; 'a'
ret c
cp &7b ; 'z'+1
ret nc
and %11011111
ret
del_key: ld a,&5f ; Delete -> _
ret
tab_key: ld a,&09 ; standard tab
ret
invalid_key: ld a,&20 ; space for invalid
ret
ignore_key: xor a ; ignore
ret
; Get a type-in key from the imported file
get_filekey: ld a,file_page+rom0_off
out (lmpr),a
ld hl,(filepos)
ld a,h
or l
or (hl)
jr z,file_done ; jump if no file
file_skip: ld a,(hl) ; fetch next file character
inc hl
and a
jr z,clear_file2 ; clear file at end
cp &0d ; CR?
jr z,file_skip ; ignore CR in file
cp &0a ; LF?
jr nz,file_done
ld a,&0d ; convert LF to CR
file_done: ld (filepos),hl
and a
ret
filepos: defw 0
; Clear the file area to disable type-in
clear_file: ld a,file_page+rom0_off
out (lmpr),a
clear_file2: ld hl,0
ld c,l
clr_file_lp: ld (hl),c
inc l
jr nz,clr_file_lp
inc h
bit 7,h
jr nz,clr_file_lp
ld h,l
xor a ; no file character
jr file_done
update_io:
ld a,&f7
in a,(status)
and %00100000
jr nz,not_esc ; jump if Esc not pressed
call clear_file ; Esc cancels type-in mode
ld a,&fe
in a,(keyboard)
rra
jr c,not_reset ; unshifted gives chr
ld a,&c9 ; RET opcode
ld (main_loop),a ; return to reset on next instruction
not_reset:
2007-04-02 13:21:49 +00:00
still_esc: ld a,&f7
in a,(status)
and %00100000
jr z,still_esc ; wait until Esc released
ld a,&1b ; Esc character
jr got_key
2007-04-02 13:21:49 +00:00
not_esc:
ld a,&7f
in a,(keyboard)
bit 1,a
jr nz,not_sym
ld a,&f7
in a,(keyboard)
rra
ld c,&ff ; line interrupt disable
jr nc,got_line
rra
ld c,88 ; centre of display (312/2-68=88)
jr c,not_sym
got_line: ld a,c
out (status),a ; set or disable line interrupt
not_sym:
ld hl,kbd_ctrl
bit 7,(hl) ; key available?
jr nz,no_key ; no need to read more yet
call get_filekey ; got a type-in key from file?
jr nz,got_key ; jump if we have
skip_key: ld a,&1f ; LMPR page for BASIC, with ROM 0 enabled
2007-04-02 13:21:49 +00:00
out (lmpr),a
ld (save_stack),sp
ld sp,(basic_stack)
ld bc,getkey ; return key code in A, zero for none
call r1onclbc
ld sp,(save_stack)
2007-04-02 13:21:49 +00:00
jr z,no_key
call map_key
and a
jr z,skip_key
got_key:
ld hl,kbd_data
ld (hl),a
set 7,(hl) ; bit 7 always set
inc l
set 7,(hl) ; key available
no_key:
ld hl,dsp_data
bit 7,(hl) ; display char available?
jr z,no_char
res 7,(hl) ; display ready
ld a,(hl) ; fetch character to display
and a
jr z,done_draw
cp &7f
jr z,done_draw
push af
ld a,screen_page+rom0_off
out (lmpr),a ; page in screen
2007-04-02 13:21:49 +00:00
ld a,&00 ; space
call display_chr ; erase cursor
pop af
cp &5f ; backspace? (underscore)
jr z,done_draw2 ; if so, no need to process further
push af
call map_chr ; map output from SAM->Apple 1
call display_chr ; show it
pop af
done_draw2: call advance_chr ; advance the cursor position
ld a,&5f ; cursor block
2007-04-02 13:21:49 +00:00
call display_chr ; show cursor
done_draw:
no_char:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Interrupt handling
setup_im2: ld hl,im2_table
ld a,im2_jp/256
im2_fill: ld (hl),a
inc l
jr nz,im2_fill
inc h
ld (hl),a ; complete the final entry
ld a,im2_table/256
ld i,a
im 2 ; set interrupt mode 2
ret
im2_handler: push af
push bc
push de
push hl
ex af,af'
exx
push af
push bc
push de
push hl
push ix
push iy
in a,(lmpr)
push af
call update_io
pop af
out (lmpr),a
pop iy
pop ix
pop hl
pop de
pop bc
pop af
exx
ex af,af'
pop hl
pop de
pop bc
pop af
ei
reti
2007-04-02 13:21:49 +00:00
; IM 2 table must be aligned to 256-byte boundary
defs -$\256
im2_table: defs 257
; IM 2 vector must have LSB==MSB
defs $/256-1
stack_top: ; stack fits nicely in the slack space
im2_jp: jp im2_handler
basic_stack: defw 0
save_stack: defw 0
2007-04-02 13:21:49 +00:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 65C02 emulation
execute: ld a,&1a ; LD A,(DE)
ld (ix),a
jp (ix)
i_undoc_3: inc de ; 3-byte NOP
i_undoc_2: inc de ; 2-byte NOP
i_undoc_1: jp (ix) ; NOP
2007-04-02 13:21:49 +00:00
read_write_loop:
write_loop: ld a,h
cp op_00/256 ; MSB of lowest emulator byte
jr nc,write_trap
2007-04-02 13:21:49 +00:00
zwrite_loop:
main_loop: ld a,(de) ; 7/7/15 - fetch opcode
inc de ; 6/7/11 - PC++
ld l,a ; 4/6/6 - LSB is opcode
ld h,msb_table/256 ; 7/7/15 - look-up table
ld h,(hl) ; 7/8/16 - opcode MSB
jp (hl) ; 4/5/9 - execute!
; = 35T (official) / 40T (off-screen) / 72T (on-screen)
write_trap: cp &d0 ; I/O page?
jr nz,rom_write
ld a,l
2007-04-02 13:21:49 +00:00
cp &12 ; display char?
jr z,chr_write
jp (ix)
chr_write: set 7,(hl) ; display busy
jp (ix)
2007-04-02 13:21:49 +00:00
rom_write: ld a,&00 ; patched by starting code
ld (op_00),a ; emulator appears read-only to be ROM for RAM tests
jp (ix)
read_loop: ld a,h
2007-04-02 13:21:49 +00:00
cp &d0
jr z,read_trap
2007-04-02 13:21:49 +00:00
ld a,(de) ; fetch opcode
inc de ; PC++
ld l,a ; LSB is opcode
ld h,msb_table/256 ; look-up table
ld h,(hl) ; opcode MSB
2007-04-02 13:21:49 +00:00
jp (hl) ; execute!
read_trap: ld a,l
2007-04-02 13:21:49 +00:00
cp &10 ; key read?
jr z,key_read
2007-04-02 13:21:49 +00:00
jp (ix)
key_read: inc l
res 7,(hl) ; key not available
2007-04-02 13:21:49 +00:00
jp (ix)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
load_state: ld a,(reg_a)
ld b,a ; set A
ld a,(reg_x)
ld iyh,a ; set X to IYh
2007-04-02 13:21:49 +00:00
ld a,(reg_y)
ld iyl,a ; set Y to IYl
2007-04-02 13:21:49 +00:00
exx
ld a,(reg_s)
ld l,a ; set S
ld h,&01 ; MSB for stack pointer
ld a,(reg_p)
ld c,a ; keep safe
and %00001100 ; keep D and I
or %00110000 ; force T and B
ld d,a ; set P
ld a,c
and %01000000 ; keep V
ld e,a ; set V
ld a,c
rra ; carry from C
ex af,af' ; set carry
ld a,c
and %10000010 ; keep N Z
xor %00000010 ; zero for Z
exx
ld c,a ; set N Z
2007-04-02 13:21:49 +00:00
ld de,(reg_pc) ; set PC
ld ix,main_loop ; decode loop
2007-04-02 13:21:49 +00:00
ret
save_state: ld a,b ; get A
ld (reg_a),a
ld a,iyh ; get X from IYh
2007-04-02 13:21:49 +00:00
ld (reg_x),a
ld a,iyl ; get Y from IYl
2007-04-02 13:21:49 +00:00
ld (reg_y),a
ex af,af' ; carry
inc c
dec c ; set N Z
push af ; save flags
ex af,af' ; protect carry
2007-04-02 13:21:49 +00:00
exx
pop bc
ld a,c
and %10000001 ; keep Z80 N and C
bit 6,c ; check Z80 Z
jr z,save_nz
or %00000010 ; set Z
save_nz: or e ; merge V
or d ; merge T B D I
ld (reg_p),a
2007-04-02 13:21:49 +00:00
ld a,l ; get S
ld (reg_s),a
exx
ld (reg_pc),de
ret
; During running we keep the 65xx registers in Z80 registers
; These are used only to hold the state before/after running
2007-04-02 13:21:49 +00:00
reg_a: defb 0
reg_p: defb 0
reg_x: defb 0
reg_y: defb 0
reg_s: defb 0
reg_pc: defw 0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2007-04-02 13:21:49 +00:00
defs -$\256
2007-04-02 13:21:49 +00:00
msb_table: defb op_00>>8, op_01>>8, op_02>>8, op_03>>8, op_04>>8, op_05>>8, op_06>>8, op_07>>8
defb op_08>>8, op_09>>8, op_0a>>8, op_0b>>8, op_0c>>8, op_0d>>8, op_0e>>8, op_0f>>8
defb op_10>>8, op_11>>8, op_12>>8, op_13>>8, op_14>>8, op_15>>8, op_16>>8, op_17>>8
defb op_18>>8, op_19>>8, op_1a>>8, op_1b>>8, op_1c>>8, op_1d>>8, op_1e>>8, op_1f>>8
defb op_20>>8, op_21>>8, op_22>>8, op_23>>8, op_24>>8, op_25>>8, op_26>>8, op_27>>8
defb op_28>>8, op_29>>8, op_2a>>8, op_2b>>8, op_2c>>8, op_2d>>8, op_2e>>8, op_2f>>8
defb op_30>>8, op_31>>8, op_32>>8, op_33>>8, op_34>>8, op_35>>8, op_36>>8, op_37>>8
defb op_38>>8, op_39>>8, op_3a>>8, op_3b>>8, op_3c>>8, op_3d>>8, op_3e>>8, op_3f>>8
defb op_40>>8, op_41>>8, op_42>>8, op_43>>8, op_44>>8, op_45>>8, op_46>>8, op_47>>8
defb op_48>>8, op_49>>8, op_4a>>8, op_4b>>8, op_4c>>8, op_4d>>8, op_4e>>8, op_4f>>8
defb op_50>>8, op_51>>8, op_52>>8, op_53>>8, op_54>>8, op_55>>8, op_56>>8, op_57>>8
defb op_58>>8, op_59>>8, op_5a>>8, op_5b>>8, op_5c>>8, op_5d>>8, op_5e>>8, op_5f>>8
defb op_60>>8, op_61>>8, op_62>>8, op_63>>8, op_64>>8, op_65>>8, op_66>>8, op_67>>8
defb op_68>>8, op_69>>8, op_6a>>8, op_6b>>8, op_6c>>8, op_6d>>8, op_6e>>8, op_6f>>8
defb op_70>>8, op_71>>8, op_72>>8, op_73>>8, op_74>>8, op_75>>8, op_76>>8, op_77>>8
defb op_78>>8, op_79>>8, op_7a>>8, op_7b>>8, op_7c>>8, op_7d>>8, op_7e>>8, op_7f>>8
defb op_80>>8, op_81>>8, op_82>>8, op_83>>8, op_84>>8, op_85>>8, op_86>>8, op_87>>8
defb op_88>>8, op_89>>8, op_8a>>8, op_8b>>8, op_8c>>8, op_8d>>8, op_8e>>8, op_8f>>8
defb op_90>>8, op_91>>8, op_92>>8, op_93>>8, op_94>>8, op_95>>8, op_96>>8, op_97>>8
defb op_98>>8, op_99>>8, op_9a>>8, op_9b>>8, op_9c>>8, op_9d>>8, op_9e>>8, op_9f>>8
defb op_a0>>8, op_a1>>8, op_a2>>8, op_a3>>8, op_a4>>8, op_a5>>8, op_a6>>8, op_a7>>8
defb op_a8>>8, op_a9>>8, op_aa>>8, op_ab>>8, op_ac>>8, op_ad>>8, op_ae>>8, op_af>>8
defb op_b0>>8, op_b1>>8, op_b2>>8, op_b3>>8, op_b4>>8, op_b5>>8, op_b6>>8, op_b7>>8
defb op_b8>>8, op_b9>>8, op_ba>>8, op_bb>>8, op_bc>>8, op_bd>>8, op_be>>8, op_bf>>8
defb op_c0>>8, op_c1>>8, op_c2>>8, op_c3>>8, op_c4>>8, op_c5>>8, op_c6>>8, op_c7>>8
defb op_c8>>8, op_c9>>8, op_ca>>8, op_cb>>8, op_cc>>8, op_cd>>8, op_ce>>8, op_cf>>8
defb op_d0>>8, op_d1>>8, op_d2>>8, op_d3>>8, op_d4>>8, op_d5>>8, op_d6>>8, op_d7>>8
defb op_d8>>8, op_d9>>8, op_da>>8, op_db>>8, op_dc>>8, op_dd>>8, op_de>>8, op_df>>8
defb op_e0>>8, op_e1>>8, op_e2>>8, op_e3>>8, op_e4>>8, op_e5>>8, op_e6>>8, op_e7>>8
defb op_e8>>8, op_e9>>8, op_ea>>8, op_eb>>8, op_ec>>8, op_ed>>8, op_ee>>8, op_ef>>8
defb op_f0>>8, op_f1>>8, op_f2>>8, op_f3>>8, op_f4>>8, op_f5>>8, op_f6>>8, op_f7>>8
defb op_f8>>8, op_f9>>8, op_fa>>8, op_fb>>8, op_fc>>8, op_fd>>8, op_fe>>8, op_ff>>8
2007-04-02 13:21:49 +00:00
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2007-04-02 13:21:49 +00:00
defs -$\256 ; align to 256-byte boundary
2007-04-02 13:21:49 +00:00
; !"#$%&1()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]`abcdefghijklmnopqrstuvwxyz{|}~
2007-04-02 13:21:49 +00:00
font_data:
MDAT "font.bin"
2007-04-02 13:21:49 +00:00
mask_data: defb %00000011,%11111111
defb %11000000,%11111111
defb %11110000,%00111111
defb %11111100,%00001111
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2007-04-02 13:21:49 +00:00
end: equ $
length: equ end-start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Instruction implementations
INC "opimpl.inc"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Woz Monitor ROM (&ff00-&ffff)
; May be overwritten by includes below
dump &ff00
MDAT "apple1.rom"
; Use RAM-based Applesoft BASIC by default
IF 1
; Applesoft BASIC [Lite] (6000-7FFF)
; http://cowgod.org/replica1/applesoft/
dump low_page,&6000
MDAT "applesoft-lite-0.4-ram.bin"
ELSE
; Lee Davidson's Enhanced BASIC (5800-77CE)
; http://members.lycos.co.uk/leeedavison/6502/ehbasic/
dump low_page,&5800
MDAT "ehbasic.bin"
ENDIF
; Use Ken Wessen's BASIC+assembler by default
IF 1
; Ken Wessen's custom BASIC + Krusader assembler + enhanced monitor (E000-FFFF)
; BRK handler points to mini-monitor in this version
; http://school.anhb.uwa.edu.au/personalpages/kwessen/apple1/Krusader.htm
2007-04-02 13:21:49 +00:00
dump &e000
MDAT "65C02.rom.bin"
ELSE
; Applesoft BASIC [Lite] + Woz monitor (E000-FFFF)
; http://cowgod.org/replica1/applesoft/
dump &e000
MDAT "applesoft-lite-0.4.bin"
ENDIF
2007-04-02 13:21:49 +00:00
; Test program to output the character set (5000-500B)
; LDX $00 ; loop: TXA ; JSR echo ; INX ; BRA loop
dump low_page,&5000
defb &a2, &00, &8a, &20, &ef, &ff, &e8, &80, &f9