Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
968 lines (837 sloc) 16.6 KB
;**************************************************************************
;*
;* FILE bootcrt.tas
;* Copyright (c) 2011 Daniel Kahlin <daniel@kahlin.net>
;* Written by Daniel Kahlin <daniel@kahlin.net>
;* Additions by Gideon Zweijtzer <gideon.zweijtzer@gmail.com> and Wilfred Bos
;*
;* DESCRIPTION
;* Boot Cartridge for the 1541-Ultimate
;*
;******
* = $8000
flag_zp = $02
do_sync = $014F
runcode = $0172
device = $0173
namelen = $0174
name = $0175
uci_status = $df1c
uci_control = $df1c
uci_command = $df1d
uci_reponse = $df1e
uci_stdata = $df1f
RUNCODE_LOAD_BIT = %00000001
RUNCODE_RUN_BIT = %00000010
RUNCODE_JUMP_BIT = %00000100
RUNCODE_CAPTURE_BIT = %00001000
RUNCODE_RECORD_BIT = %00010000
RUNCODE_TAPE_BIT = %00100000
RUNCODE_REAL_BIT = %01000000
RUNCODE_MOUNT_BIT = %10000000
RUNCODE_DMALOAD_RUN = %00000011
RUNCODE_DMALOAD = %00000001
RUNCODE_DMALOAD_JUMP = %00000101
RUNCODE_MOUNT_DMALOAD_RUN = %10000011
RUNCODE_MOUNT_LOAD_RUN = %11000011
RUNCODE_TAPE_RECORD = %01110000
RUNCODE_TAPE_CAPTURE = %01101000
;**************************************************************************
;*
;* NAME cartridge header
;*
;* DESCRIPTION
;* Normal $8000 cartridge header
;*
;******
.word cold_start
.word warm_start
.byte "C"|$80,"B"|$80,"M"|$80,"8","0"
; ident
.text 0,"Ultimate Boot Cartridge / TLR",0
;**************************************************************************
;*
;* NAME cold_start
;*
;* DESCRIPTION
;* Make minimal setup. Force cartridge of and reset if flag signals that
;* a transfer is in progress.
;*
;******
cold_start
sei
cld
ldx #$ff
txs
lda flag_zp
bmi main_start
clean_reset
ldx #RESET_TRAMPOLINE_LEN
cr_lp1
lda reset_trampoline-1,x
sta $d000-1,x
dex
bne cr_lp1
jmp $d000
reset_trampoline
lda #$40
sta $dfff ; disable boot cart
jmp ($fffc)
RESET_TRAMPOLINE_LEN = *-reset_trampoline
;**************************************************************************
;*
;* NAME warm_start
;*
;* DESCRIPTION
;* Acknowledge NMI and return immediately.
;*
;******
warm_start
bit $dd0d
pla
tay
pla
tax
pla
rti
;**************************************************************************
;*
;* NAME main_start
;*
;* DESCRIPTION
;* Perform load.
;* Acc = flag_zp, X=$FF.
;*
;******
main_start
jsr $fda3
jsr clone_fd50
jsr clone_fd15
lda runcode
and #RUNCODE_JUMP_BIT
bne start_jump
jsr $ff5b ; screen init
jsr $e51b
jsr $ff5e
; init basic vectors
jsr $e453
; init basic storage (sets $01fc and $01fd to $01)
jsr $e3bf
; basic start message + new (resets stack pointer to $fa)
jsr $e422
jsr wait_cart_ready
;******
;* LOAD
bit runcode ; test real bit
bvs ms_skp1
jsr dma_load
jmp ms_skp3
start_jump
; vic init (copy of E5A0, which was normally reached through ff5b
LDA #$03 ; set the screen as the output device
STA $9A ; save the output device number
LDA #$00 ; set the keyboard as the input device
STA $99 ; save the input device number
LDX #$2F ; set the count/index
- CPX #$21 ; skip d020
BEQ + ;
CPX #$22 ; skip d021
BEQ + ;
CPX #$12 ; skip d011
BEQ +
LDA $ECB8,X ; get a vic ii chip initialisation value
STA $CFFF,X ; save it to the vic ii chip
+ DEX ; decrement the count/index
BNE - ; loop if more to do
jsr $e51b
jsr $ff5e
; init basic vectors
jsr $e453
; init basic storage (sets $01fc and $01fd to $01)
jsr $e3bf
; basic start message + new (resets stack pointer to $fa)
jsr $e422
jsr wait_cart_ready
jsr dma_load_impl
ldx #jumper_code_end - jumper_code - 1
- lda jumper_code,x
sta JUMPER,x
dex
bpl -
jmp JUMPER
kbuffer
.text 13,"RUN",13
KBUFFER_LEN = *-kbuffer
extra_delay
ldx #0
ldy #0
- dey
bne -
dex
bne -
rts
ms_skp1
lda runcode
and #RUNCODE_TAPE_BIT
bne ms_skp2
jsr extra_delay
jsr extra_delay
jsr extra_delay
jsr extra_delay
jsr print_load
lda #145 ;CRSR-UP
jsr $ffd2
jsr $ffd2
lda #0
sta $d3 ;reset column
sei
ldx #KBUFFER_LEN
stx $c6
ms_lp1
lda kbuffer-1,x
sta $0277-1,x
dex
bne ms_lp1
jmp exit_basic
kbuffer2
.text "LOAD",13,"RUN",13
KBUFFER2_LEN = *-kbuffer2
ms_skp2
lda runcode
and #RUNCODE_RECORD_BIT
bne record_tape
lda runcode
and #RUNCODE_CAPTURE_BIT
bne capture_tape
jmp do_load
record_tape
ldx #0
- inx
lda $f0ea,x
pha
and #$7f
jsr $ffd2
pla
asl
bcc -
lda #$0d
jsr $ffd2
sei
lda #$27 ;; turn write pin to input
sta $00
lda #$37 ;; everything else is normal, motor off
sta $01
ldx #$00
- lda record_routine,x
sta $380,x
inx
cpx #RECORD_ROUTINE_LEN
bne -
jmp $380 ;; cartridge will be killed shortly, we have to be quick
record_routine
lda #$40
sta $dfff ; disable boot cart
nop
- lda $01
and #$10 ;; sense input test, wait until button is pressed
bne -
lda #$17
sta $01 ; turn on motor
ldx #0
- lda $380 + (write_message - record_routine),x
jsr $ffd2
inx
cpx #WRITE_MSG_LEN
bne -
self
jmp $380 + (self - record_routine)
write_message
.text "ULTIMATE IS WRITING TO TAPE - JUST WAIT", 13
WRITE_MSG_LEN = *-write_message
RECORD_ROUTINE_LEN = *-record_routine
capture_tape
ldx #0
- inx
lda $f0ea,x
pha
and #$7f
jsr $ffd2
pla
bpl -
lda #$0d
jsr $ffd2
sei
lda #$27 ;; turn write pin to input
sta $00
lda #$37 ;; everything else is normal, motor off
sta $01
ldx #$00
- lda capture_routine,x
sta $380,x
inx
cpx #CAPTURE_ROUTINE_LEN
bne -
jmp $380 ;; cartridge will be killed shortly, we have to be quick
capture_routine
lda #$40
sta $dfff ; disable boot cart
nop
- lda $01
and #$10 ;; sense input test, wait until button is pressed
bne -
lda #$17
sta $01 ; turn on motor
ldx #0
- lda $380 + (capture_message - capture_routine),x
jsr $ffd2
inx
cpx #CAPTURE_MSG_LEN
bne -
self2
lda $01
and #$10 ;; sense input test, wait until button gets released
beq self2
lda #$37
sta $01 ; turn off motor
; DONE!
ldx #4
- lda $380 + (done_message - capture_routine),x
jsr $ffd2
dex
bne -
; Now let's inform the Ultimate that we're done
lda uci_status
and #$30
bne fault
; Target #4, command #3
lda #$04
sta uci_command
lda #$03
sta uci_command
lda #$08
sta uci_control
lda #$01
sta uci_control
lda #$07
sta $d020
- lda uci_status
lsr
bcs -
lda #$05
sta $d020
lda #$02
sta uci_control
self3
jmp $380 + (self3 - capture_routine)
fault
lda #$02
sta $d020
jmp $380 + (self3 - capture_routine)
capture_message
.text "ULTIMATE IS CAPTURING TAPE - JUST WAIT", 13
done_message
.text "DONE"
CAPTURE_MSG_LEN = *-capture_message
CAPTURE_ROUTINE_LEN = *-capture_routine
do_load
lda #145 ;CRSR-UP
jsr $ffd2
jsr $ffd2
lda #0
sta $d3 ;reset column
sei
ldx #KBUFFER2_LEN
stx $c6
ms_lp2
lda kbuffer2-1,x
sta $0277-1,x
dex
bne ms_lp2
jmp exit_basic
ms_skp3
;******
;* RUN
lda runcode
and #RUNCODE_RUN_BIT
beq load_program
jsr extra_delay
jsr extra_delay
jsr extra_delay
jsr extra_delay
jsr print_run
jmp run_program
;**************************************************************************
;*
;* NAME wait_cart_ready
;*
;* DESCRIPTION
;* Wait until cart is ready. Print mounting... if disk is being mounted.
;*
;******
wait_cart_ready
jsr print_cr
bit runcode
bpl wcr_skp1
; print "MOUNTING..."
lda #<mount_msg
ldy #>mount_msg
jsr $ab1e
wcr_skp1
; wait for cart to be ready
wcr_lp1
bit flag_zp
bvc wcr_lp1
ldy $d3
beq wcr_skp2
; blank out "MOUNTING..."
lda #$20
wcr_lp2
sta ($d1),y
dey
bpl wcr_lp2
iny
sty $d3
wcr_skp2
; print "READY."
lda #<ready_msg
ldy #>ready_msg
jsr $ab1e
rts
mount_msg
.text "MOUNTING...",0
ready_msg
.text "READY.",13,0
;**************************************************************************
;*
;* NAME dma_load
;*
;* DESCRIPTION
;* Perform DMA load
;*
;******
dma_load
jsr print_load
jsr print_load2
dma_load_impl
sei
ldx #DMA_LOADER_LEN
dl_lp1
lda dma_loader_st-1,x
sta DMA_LOADER-1,x
dex
bne dl_lp1
lda #1
ldx #$34
ldy $01
jsr DMA_LOADER
rts
DMA_LOADER = $0150
dma_loader_st
stx $01
sta flag_zp ; signal to allow DMA
dl_lp2
lda flag_zp ; wait for DMA complete signal from cart
bne dl_lp2
sty $01
rts
DMA_LOADER_LEN = *-dma_loader_st
;**************************************************************************
;*
;* NAME run_program, load_program, exit_basic
;*
;* DESCRIPTION
;* run program
;*
;******
exit_basic
ldy #2
.byte $2c
load_program
ldy #1
.byte $2c
run_program
ldy #0
sei
ldx #RUNNER_LEN
rp_lp1
lda runner_st-1,x
sta RUNNER-1,x
dex
bne rp_lp1
tya
beq rp_skp1 ;Y=0, run program
; lda #$6c
; sta runner_runjmp+0
lda #$74
sta runner_runjmp+1
lda #$a4
sta runner_runjmp+2
dey
beq rp_skp1 ;Y=1, load program
; Y=2, exit to basic
lda #$2c
sta runner_relinkjsr
rp_skp1
; turn off os messages for run
lda #$00
jsr $ff90
jsr $ff84
; cleaning up some traces
lda #$a7
sta $01fb
lda #$9c
sta $01fe
lda #$e3
sta $01ff
lda #$1a
sta $a1
lda #$A0
sta $c2 ; current byte pointer of memory test
lda #$01
sta $c5 ; matrix code of key previously pressed (return)
sta $cb ; matrix code of key currently pressed (return)
lda #$20
sta $ce ; screen code of char under cursor
lda do_sync
beq +
jsr synchronize
; important for basic
+ ldx #$fb
txs
jmp RUNNER
RUNNER = $0150
runner_st
lda #$40
sta $dfff ; disable boot cart
cli
lda #$54
sta $dffe ; trigger
lda #0
sta $02
runner_relinkjsr = *-runner_st+RUNNER
jsr $a533 ; relink
jsr $a659 ; clr, setup chrget/chrgot ptr
runner_runjmp = *-runner_st+RUNNER
jmp $a7ae ; run
RUNNER_LEN = *-runner_st
JUMPER = $0150
jumper_code
lda #0
sta $02
lda #$40
sta $dfff
cli
jmp ($00aa)
jumper_code_end
;**************************************************************************
;*
;* NAME clone_fd50
;*
;* DESCRIPTION
;* Clone the memory init at $fd50 without memtest.
;*
;******
clone_fd50
lda #0
tay
cfd50_lp1
; sta $0002,y
sta $0003,y ;Make sure flag_zp ($02) is preserved.
sta $0200,y
sta $0300,y
iny
bne cfd50_lp1
ldx #$03
lda #$3c
sta $b2
stx $b3
ldx #$00
ldy #$a0
jmp $fd8c ;Set MemBounds
;**************************************************************************
;*
;* NAME clone_fd15
;*
;* DESCRIPTION
;* Clone the vector setup at $fd15 without trashing memory below kernal
;*
;******
clone_fd15
ldy #$1f
cfd15_lp1
lda $fd30,y
sta $0314,y
dey
bpl cfd15_lp1
rts
;**************************************************************************
;*
;* NAME print_load
;*
;* DESCRIPTION
;* print load message
;*
;******
print_load
lda #<load1_msg
ldy #>load1_msg
jsr $ab1e
jsr print_quote
jsr print_name
jsr print_quote
jsr print_comma
ldx device
lda #0
jsr $bdcd
jsr print_comma
lda #"1"
jmp $ffd2
; rts
print_load2
lda #<load2_msg
ldy #>load2_msg
jsr $ab1e
jsr print_name
lda #<load3_msg
ldy #>load3_msg
jmp $ab1e
; rts
load1_msg
.text "LOAD",0
load2_msg
.text 13,13,"SEARCHING FOR ",0
load3_msg
.text 13,"LOADING"
.text 0
print_name
ldx #0
pn_lp1
cpx namelen
beq pn_ex1
lda name,x
jsr $ffd2
inx
bne pn_lp1
pn_ex1
rts
;**************************************************************************
;*
;* NAME print_cr, print_quote, print_comma
;*
;* DESCRIPTION
;* Print common single chars.
;*
;******
print_cr
lda #13
.byte $2c
print_quote
lda #34
.byte $2c
print_comma
lda #","
jmp $ffd2
;**************************************************************************
;*
;* NAME print_run
;*
;* DESCRIPTION
;* print run message
;*
;******
print_run
lda #<run_msg
ldy #>run_msg
jmp $ab1e
run_msg
.text 13,"READY.",13
.text "RUN",13
.text 0
;**************************************************************************
;*
;* NAME synchronize_impl
;*
;* DESCRIPTION
;* synchronizes CPU with VIC for cycle exact start of programs
;* This is the part that is copied to RAM
;*
;******
STACKPOINTER = $CF00
SYNCLOC = $CF01
SYNCHRONIZEIRQPOINTER = SYNCLOC + synchronizeirq - synchronize_impl
SYNCHRONIZEWEDGEPOINTER = SYNCLOC + WedgeIRQ - synchronize_impl
synchronize_impl
; pointer has already been set by the prepare function
lda #$35
sta $01
tsx
stx STACKPOINTER
; Set up IRQ vector (write 'through' kernal rom)
lda #<SYNCHRONIZEIRQPOINTER
sta $fffe
lda #>SYNCHRONIZEIRQPOINTER
sta $ffff
- lda $d012
cmp #$F0
bne -
; disable IRQs
lda #$7f
sta $dc0d
; acknowledg IRQs
lda $dc0d
; Just in case also for CIA2
lda $dd0d
; setup IRQ for VIC
lda #$FC
sta $d012
lda $d011
and #$7F
sta $d011
lda #$01
sta $d019
sta $d01a
cli
; now just wait until the IRQ occurs
irqwait
clc
bcc irqwait
synchronizeirq
; A Raster Compare IRQ is triggered on cycle 0 on the current $d012 line
; The MPU needs to finish it's current OP code before starting the Interrupt Handler,
; meaning a 0 -> 7 cycles delay depending on OP code.
; Then a 7 cycle delay is spendt invoking the Interrupt Handler (Push SR/PC to stack++)
; CYCLECOUNT: [7 -> 14] cycles after Raster IRQ occurred.
; Set up Wedge IRQ vector
lda #<SYNCHRONIZEWEDGEPOINTER ; 2
sta $fffe ; 4
lda #>SYNCHRONIZEWEDGEPOINTER ; 2
sta $ffff ; 4
; Set the Raster IRQ to trigger on the next Raster line
inc $d012 ; 6
; Acknowlege current Raster IRQ
lda #$01 ; 2
sta $d019 ; 4
; Store current Stack Pointer (will be messed up when the next IRQ occurs)
tsx ; 2
; Allow IRQ to happen (Remember the Interupt flag is set by the Interrupt Handler).
cli ; 2
; CYCLECOUNT: [35 -> 42] (max = 65 before new interrupt, hence: 65 - 35 = 30 / 2 = 15 nops)
; Execute NOPs untill the raster line changes and the Raster IRQ triggers
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
; CYCLECOUNT: [67 -> 74]
NOP
NOP
inc $0401
WedgeIRQ
; At this point the next Raster Compare IRQ has triggered and the jitter is max 1 cycle.
; CYCLECOUNT: [7 -> 8] (7 cycles for the interrupt handler + [0 -> 1] cycle Jitter for the NOP)
; Restore previous Stack Pointer (ignore the last Stack Manipulation by the IRQ)
txs
; PAL-63 ; NTSC-64 ; NTSC-65
;---------;------------;-----------
ldx #$08 ; ldx #$08 ; ldx #$09
dex ; dex ; dex
bne *-1 ; bne *-1 ; bne *-1
bit $00 ; nop
; nop
; Check if $d012 is incremented and rectify with an aditional cycle if neccessary
lda $d012
cmp $d012 ; <- critical instruction (ZERO-Flag will indicate if Jitter = 0 or 1)
; CYCLECOUNT: [61 -> 62] <- Will not work if this timing is wrong
; cmp $d012 is originally a 5 cycle instruction but due to piplining tech. the
; 5th cycle responsible for calculating the result is executed simultaniously
; with the next OP fetch cycle (first cycle of beq *+2).
; Add one cycle if $d012 wasn't incremented (Jitter / ZERO-Flag = 0)
beq *+2
; Stable code
ldx STACKPOINTER
txs
lda #$37
sta $01
; Note that this RTS will actually jump back to our caller (called to 'synchronize_impl')
rts
SYNCHRONIZELENGTH = * - synchronize_impl
;**************************************************************************
;*
;* NAME synchronize
;*
;* DESCRIPTION
;* setup raster synchronization; i.e. disable IRQ sources from CIA1
;* and setup IRQ from VIC at raster #$FC. Disable sprites, too.
;* then copy the actual synchronize function to RAM and call it.
;* so aside from rom RAM getting garbled, this function will return
;* to the ROM with CPU and VIC synchronized. Note, CIA timers are first
;* cleared and later reinitialized by the original kernal routine.
;*
;******
synchronize
sei
pha
txa
pha
tya
pha
; eliminate CIA interrupts
lda $dc0e
and #$fe
sta $dc0e
lda #$7F
sta $dc0d
sta $dd0d
lda $dc0d
lda $dd0d
; prepare VIC interrupt
lda #$00
sta $d015
; lda #$fc
; sta $d012
; lda $d011
; and #$7f
; sta $d011
; lda #$00
; sta $d01a
; lda #$0f
; sta $d019
; now copy synchronize routine to RAM, as we need to run it with our ROM banked out
ldx #0
- lda synchronize_impl,x
sta SYNCLOC,x
inx
cpx #SYNCHRONIZELENGTH
bne -
; GO!
jsr SYNCLOC
lda #$0d
sta $dfff
; disable VIC interrupt once again
lda #$00
sta $d01a
lda #$0f
sta $d019
; reinit CIA interrupts and return to called
lda #$81
sta $dc0d
lda $dc0e
ora #$11
sta $dc0e
pla
tay
pla
tax
pla
rts
You can’t perform that action at this time.