commit 37ac774138eceb6c7f25bc7866728d018256ac96
parent 822e76c00ceadee13fd533320aa460a502c57169
Author: Gerd Beuster <gerd@frombelow.net>
Date: Mon, 30 Nov 2020 18:36:16 +0100
Data stack implemented
Diffstat:
6 files changed, 310 insertions(+), 38 deletions(-)
diff --git a/roms/boot/boot.asm b/roms/boot/boot.asm
@@ -1,26 +1,42 @@
CLOCK_SPEED = 4 ;4 Mhz Clock
+
+.if SYMON
+ rom_start = $c000
+.else
+ rom_start = $e000
+.endif
.include "boot_macros.inc"
-;;; Global zero page variables
+ * = $00
+.dsection zero_page
+.cerror * > $ff, "Zero page exhausted"
+ * = rom_start
+.dsection rom
+.cerror * > $ffff, "ROM exhausted"
+
export .namespace
-lfsr_state = $20 ; 16 bit
+.section zero_page
+ ;; LFSR
+lfsr_state: .word ?
+ ;; Stack
+ds_pointer: .word ?
+ds_frame: .word ?
+ ;; Temporary zero page variables;
+ ;; only used in subroutine
+puts_str: .word ?
+gets_len: .byte ?
+gets_str: .word ?
+ ;; Everything beyond these reserved variables available to
+ ;; programs in RAM.
+rom_zero_page_end:
+.send zero_page
.endn
-
-;;; Temporary zero page variables;
-;;; only used in subroutine
+
.namespace export
-puts_str = $10 ; 16 bit address
-gets_len = $10 ; 8 bit
-gets_str = $11 ; 16 bit address
+stack_end = $7ffb
.endn
-.if SYMON
- * = $c000
-.else
- * = $e000
-.endif
-
;;; The actual (hardware) interrupt vectors are located in the ROM
;;; at $ fffa/fffb (nmi) and $fffe/$ffff (irq). Since we want to be
;;; able to handle interrupts in RAM, the ROM isr routines jump to the
@@ -31,6 +47,7 @@ nmi_vector = $7ffc
irq_vector = $7ffe
.endn
+ .section rom
;;; ----------------------------------------------------------
;;; RESET
;;;
@@ -44,6 +61,7 @@ boot: .block
#STORE_WORD export.default_nmi_handler, export.nmi_vector
cld
cli
+ jsr export.init_stack
jsr export.init_acia
jsr check_for_program_download
bcs no_program_download
@@ -497,6 +515,61 @@ derefer_ram_irq:
;; in RAM
jmp (export.irq_vector)
+;;; ----------------------------------------------------------
+;;; STACK
+;;;
+;;; ----------------------------------------------------------
+
+.namespace export
+init_stack:
+.endn
+ .block
+ #STORE_WORD export.stack_end+1, export.ds_pointer
+ rts
+ .bend
+
+.namespace export
+new_stack_frame:
+.endn
+ .block
+ #PUSH $02 ; Store old frame pointer
+ #COPY_WORD export.ds_frame, (export.ds_pointer)
+ #COPY_WORD export.ds_pointer, export.ds_frame ; Set new frame pointer
+ rts
+ .bend
+
+.namespace export
+remove_stack_frame:
+.endn
+ .block
+ #COPY_WORD export.ds_frame, export.ds_pointer ; Reset stack pointer to begin of frame.
+ #COPY_WORD (export.ds_pointer), export.ds_frame ; Restore old frame pointer.
+ #PULL $02 ; Remove obsolete frame pointer from stack.
+ rts
+ .bend
+
+.namespace export
+push_a:
+.endn
+ .block
+ pha
+ #PUSH $01
+ pla
+ sta (export.ds_pointer)
+ rts
+ .bend
+
+.namespace export
+pull_a:
+.endn
+ .block
+ lda (export.ds_pointer)
+ pha
+ #PULL $01
+ pla
+ rts
+ .bend
+
;;; Default NMI handler. Unless the user program changes
;;; the nmi_vector, NMIs are handled here.
.namespace export
@@ -518,3 +591,5 @@ default_program:
.word derefer_ram_nmi ; nmi
.word boot ; reset
.word derefer_ram_irq ; irq
+
+.send rom
diff --git a/roms/boot/boot_macros.inc b/roms/boot/boot_macros.inc
@@ -84,6 +84,13 @@
gets_len = gets_len
gets = gets
putc = putc
+ init_stack = init_stack
+ new_stack_frame = new_stack_frame
+ remove_stack_frame = remove_stack_frame
+ push_a = push_a
+ pull_a = pull_a
+ ds_pointer = ds_pointer
+ ds_frame = ds_frame
.endn
BOOT_EMBEDDED = false
.else
@@ -137,3 +144,49 @@ STORE_WORD .macro
lda #>\1
sta \2+1
.endm
+
+PUSH: .macro ; Reserve bytes on stack (no actual push operation)
+ #SUB_WORD export.ds_pointer, \1
+ .endm
+
+PULL: .macro ; Remove bytes from stack (no return value)
+ #ADD_WORD export.ds_pointer, \1
+ .endm
+
+PUSH_WORD: .macro ; Push a word on the stack
+ #PUSH $02
+ lda #<\1
+ sta (export.ds_pointer)
+ lda #>\1
+ ldy #$01
+ sta (export.ds_pointer),y
+ .endm
+
+ ;; By using the Y register, we also support indirect addressing modes
+COPY_WORD: .macro
+ lda \1
+ sta \2
+ ldy #$01
+ lda \1,y
+ sta \2,y
+ .endm
+
+SUB_WORD: .macro
+ lda \1
+ sec
+ sbc #<\2
+ sta \1
+ lda \1+1
+ sbc #>\2
+ sta \1+1
+ .endm
+
+ADD_WORD: .macro
+ lda \1
+ clc
+ adc #<\2
+ sta \1
+ lda \1+1
+ adc #>\2
+ sta \1+1
+ .endm
diff --git a/sw/interrupts/interrupts.asm b/sw/interrupts/interrupts.asm
@@ -11,14 +11,36 @@
init:
;; jmp transmitter_interrupt
- jmp receiver_interrupt
+ ;; jmp receiver_interrupt
+ jmp manual_nmi_irq
+
+manual_nmi_irq:
+ ;; Expects NMI or IRQ triggered manually.
+ ;; Prints a message when an interrupt is received
+ .block
+ jsr init_acia
+ #STORE_WORD isr_print_message_irq, irq_vector
+ #STORE_WORD isr_print_message_nmi, nmi_vector
+ jsr getc
+ #PRINTSNL 'Please trigger an interrupt.'
+wait:
+ jmp wait
+
+isr_print_message_irq:
+ #PRINTSNL 'IRQ triggered.'
+ rti
+
+isr_print_message_nmi:
+ #PRINTSNL 'NMI triggered.'
+ rti
+ .bend
;;; Looks like interrupt controlled tranmission is buggy.
;;; The only way to transmit seems to be a delay loop.
transmitter_interrupt:
.block
jsr init_acia
- #STORE_WORD isr, irq_vector
+ #STORE_WORD isr_raise_flag, irq_vector
#PRINTSNL 'Push key to start.'
jsr getc
loop:
@@ -45,7 +67,7 @@ wait_loop:
receiver_interrupt:
.block
jsr init_acia
- #STORE_WORD isr, irq_vector
+ #STORE_WORD isr_raise_flag, irq_vector
;; Receiver interrupt on
lda acia_cmd_reg
lda #%11001001
@@ -59,7 +81,7 @@ loop:
jmp loop
.bend
-isr:
+isr_raise_flag:
lda flag
inc a
sta flag
diff --git a/sw/stack_test/Makefile b/sw/stack_test/Makefile
@@ -0,0 +1,3 @@
+TARGET=stack_test
+
+include ../Makefile.common
diff --git a/sw/stack_test/stack_test.asm b/sw/stack_test/stack_test.asm
@@ -0,0 +1,113 @@
+;;; A data stack
+.if SYMON
+ .include "boot_symon.l"
+.else
+ .include "boot.l"
+.endif
+ .include "boot_macros.inc"
+
+ * = $300
+ .dsection code
+ .cerror * > $8000, "RAM exhausted"
+
+ .section code
+init:
+ cld
+ jsr init_acia
+ jmp stack_test
+stack_test:
+ .block
+ ;; Testing empty stack
+ jsr init_stack
+ jsr new_stack_frame
+ jsr remove_stack_frame
+ lda ds_pointer
+ cmp #<stack_end+1
+ #CHECK_ERROR
+ lda ds_pointer+1
+ cmp #>stack_end+1
+ #CHECK_ERROR
+ ;; Push and pull
+ jsr new_stack_frame
+ PUSH $01
+ lda #$a0
+ sta (ds_pointer)
+ PUSH $01
+ lda #$b0
+ sta (ds_pointer)
+ PUSH $01
+ lda #$c0
+ sta (ds_pointer)
+ lda (ds_pointer)
+ cmp #$c0
+ #CHECK_ERROR
+ PULL $01
+ lda (ds_pointer)
+ cmp #$b0
+ #CHECK_ERROR
+ PULL $01
+ lda (ds_pointer)
+ cmp #$a0
+ #CHECK_ERROR
+ ;; Push and pull with new frame
+ PUSH $01
+ lda #$a1
+ sta (ds_pointer)
+ lda ds_pointer ; Save ds_pointer
+ pha ; for later comparison
+ jsr new_stack_frame
+ PUSH $03
+ ldy #$00
+ lda #$b1
+ sta (ds_pointer),y
+ inc y
+ sta (ds_pointer),y
+ inc y
+ sta (ds_pointer),y
+ inc y
+ PULL $01
+ lda (ds_pointer)
+ cmp #$b1
+ #CHECK_ERROR
+ jsr remove_stack_frame
+ pla ; Still same ds_pointer
+ cmp ds_pointer ; as before creating and
+ #CHECK_ERROR ; deleting frame?
+ lda (ds_pointer)
+ cmp #$a1
+ #CHECK_ERROR
+ PULL $01
+ lda (ds_pointer)
+ cmp #$a0
+ #CHECK_ERROR
+ jsr remove_stack_frame
+ lda ds_pointer ; Stack should be
+ cmp #<stack_end+1 ; empty again.
+ #CHECK_ERROR
+ lda ds_pointer+1
+ cmp #>stack_end+1
+ #CHECK_ERROR
+ #PUSH_WORD $abcd
+ jsr pull_a
+ cmp #$cd
+ #CHECK_ERROR
+ jsr pull_a
+ cmp #$ab
+ #CHECK_ERROR
+ PRINTSNL("Stack test completed!")
+loop:
+ jmp loop
+ .bend
+
+CHECK_ERROR: .macro
+ .block
+ beq error_cont
+error:
+ PRINTSNL("Error!")
+error_loop:
+ jmp error_loop
+error_cont:
+ .bend
+ .endm
+
+ .send code
diff --git a/sw/ttt/ttt.asm b/sw/ttt/ttt.asm
@@ -18,44 +18,49 @@
.include "boot.l"
.endif
.include "boot_macros.inc"
+ * = rom_zero_page_end
+ .dsection zero_page
* = $0300
.endif
-
-;;; Variables
+.dsection ttt_game
+
+.section zero_page
;; This temporary variable is used all over the place
- tmp = $30
+tmp: .word ?
;; Used by win_first_diagonal and win_first_diagonal
- empty_field = $3e
+empty_field: .byte ?
;; Used to check for forking opportunities
- fork_board_rows = $4006
- fork_board_columns = $4009
- fork_board_diagonals = $400c
+fork_board_rows: .byte ?, ?, ?
+fork_board_columns: .byte ?, ?, ?
+fork_board_diagonals: .byte ?, ?, ?
;; Used to pass location of board
;; to subroutines.
- board_ptr = $32
+board_ptr: .word ?
;; Sometimes we copy stuff from one board
;; to the other. For this we have to
;; point to the other board.
- other_board_ptr = $34
+other_board_ptr: .word ?
;; Default board location in memory
- main_board = $4000
+main_board: .byte ?, ?, ?
;; Mirrored board location in memory
- board_mirrored = $4003
+board_mirrored: .byte ?, ?, ?
;; Pointer to init subroutine of
- ;; first player (2 bytes)
- player_x_init_ptr = $36
+ ;; first player
+player_x_init_ptr: .word ?
;; Pointer to ply subroutine of
- ;; first player (2 bytes)
- player_x_ply_ptr = $38
+ ;; first player
+player_x_ply_ptr: .word ?
;; Pointer to init subroutine of
- ;; second player (2 bytes)
- player_o_init_ptr = $3a
+ ;; second player
+player_o_init_ptr: .word ?
;; Pointer to ply subroutine of
;; first player (2 bytes)
- player_o_ply_ptr = $3c
+player_o_ply_ptr: .word ?
;; Bord rendered as ASCII art
- board_ascii = $5000
+board_ascii: .byte ?, ?, ?, ?, ?, ?, ?, ?, ?
+.send zero_page
+.section ttt_game
start_ttt:
.block
@@ -159,5 +164,6 @@ run_tests:
jsr getc
jmp run_tests
.endif
-
+.send ttt_game
+