Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Input support (with an example) #5

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 169 additions & 39 deletions br-subleq.s
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,19 @@ irq_handler:

sta $d019 ; ack VASYL IRQ
lda VREG_PORT0 ; get the character to print
bne do_output
cli ; enable interrupts so that the keyboard can work
@keyloop:
jsr $ffe4
beq @keyloop
sta VREG_PORT0
inc VREG_DLISTL ; release VASYL from spinlock, as it is ok to run concurrently from now on
bne irq_end ; always taken

do_output:
inc VREG_DLISTL ; release VASYL from spinlock, as it is ok to run concurrently from now on
jsr $FFD2 ; CHROUT
irq_end:
jmp $ea81 ; pull regs from the stack and RTI

not_vasyl_irq:
Expand All @@ -55,29 +66,46 @@ not_vasyl_irq:
; incrementing VREG_DLISTL by one. Otherwise it will spin for an arbitrary amount of time
; (including across multiple frames).
.macro SPINLOCK
MOV VREG_DLISTL, <spinlock ; potential race, so do not use just before frame's end
MOV VREG_DLISTH, >spinlock
MOV VREG_DLISTL, <@spinlock ; potential race, so do not use just before frame's end
MOV VREG_DLISTH, >@spinlock
MOV $d021, 2
.if (* - dl_start) & $ff = $ff ; if spinlock would fall on a page's last byte
VNOP ; we insert a dummy NOP to prevent that.
VNOP ; we insert a dummy NOP to prevent that.
.endif
spinlock:
MOV VREG_DLSTROBE, $a7 ; spin, baby, spin ; $a7 is VNOP, which we get pointed to
; once DLISTL is incremented
@spinlock:
MOV VREG_DLSTROBE, $a7 ; spin, baby, spin ; $a7 is a VNOP, which we get pointed to
; once DLISTL is incremented
SKIP
WAIT NTSC_LAST_SAFE_LINE, 0
BRA safe_to_update ; we are still before NTSC's last safe line, and so also before PAL's.
BRA @safe_to_update ; we are still before NTSC's last safe line, and so also before PAL's.
WAIT NTSC_LINE_COUNT, 0 ; this only completes on PAL, so an NTSC machine will restart (at spinlock+1)
SKIP
WAIT PAL_LAST_SAFE_LINE, 0
BRA safe_to_update ; we're on PAL and still before last safe line.
BRA @safe_to_update ; we're on PAL and still before last safe line.
END ; wait for the next frame
safe_to_update:
@safe_to_update:
MOV VREG_DLISTL, <dl_restart
MOV VREG_DLISTH, >dl_restart
.endmacro


; VJMP is a long-range jump
.macro VJMP label
MOV VREG_DLIST2L, <label
MOV VREG_DLIST2H, >label
MOV VREG_DL2STROBE, 0
.endmacro

; VJNZA - long-range jump if counter A is non-zero
.macro VJNZA label
MOV VREG_DLIST2L, <label
MOV VREG_DLIST2H, >label
DECA
MOV VREG_DL2STROBE, 0
.endmacro



.include "vlib/vlib.s"

.segment "VASYL"
Expand All @@ -96,58 +124,93 @@ dl_start:
; make DL restart at dl_restart so that program continues in the new frame
MOV VREG_DLISTL, <dl_restart
MOV VREG_DLISTH, >dl_restart
MOV VREG_DLIST2L, <mainloop
MOV VREG_DLIST2H, >mainloop
MOV $d01a, %00010000 ; enable VASYL interrupts

WAIT 300, 0 ; this WAIT can only complete on PAL - subsequent instructions won't be executed on an NTSC machine.
WAIT NTSC_LINE_COUNT, 0 ; this WAIT can only complete on PAL - subsequent instructions won't be executed on an NTSC machine.
MOV VREG_ADR1, <(frame_end+1)
MOV VREG_ADR1+1, >(frame_end+1)
MOV VREG_PORT1, <PAL_LAST_SAFE_LINE ; default frame-end marker suitable for PAL
END

character_out:
MOV $d021, 7
IRQ
MOV VREG_ADR0, $ff
MOV VREG_ADR0+1, $ff
SPINLOCK
MOV $d021,6
BRA char_out_done
VJMP back_from_comparator_b

character_in:
IRQ
MOV VREG_ADR0, $ff
MOV VREG_ADR0+1, $ff
MOV VREG_STEP0, 0 ; so that the byte can accessed multiple times
MOV VREG_PORT0, 0 ; signal to 6510 that we want input
SPINLOCK
VJMP back_from_comparator_a


; comparator checks if both lo- and hi-byte are equal to $ff.
comparator:
comparator_addr:
comparator_a:
comparator_a_addr1:
MOV VREG_STEP0, 0 ; low byte
MOV VREG_ADR0, <(iocheck_table+$80)
MOV VREG_ADR0+1, >(iocheck_table+$80)
MOV VREG_ADR1, <(comparator_addr2+1)
MOV VREG_ADR1+1, >(comparator_addr2+1)
MOV VREG_ADR1, <(comparator_a_addr2+1)
MOV VREG_ADR1+1, >(comparator_a_addr2+1)
XFER VREG_STEP1, (0)
XFER VREG_PORT1, (0)
VNOP ; one waitcycle needed for the write above to land
comparator_addr2:
comparator_a_addr2:
SETA 0 ; this will be modified
DECA
BRA back_from_comparator
comparator_addr3:
VJNZA back_from_comparator_a
comparator_a_addr3:
MOV VREG_STEP0, 0 ; hi byte
MOV VREG_ADR0, <(iocheck_table+$80)
MOV VREG_ADR0+1, >(iocheck_table+$80)
MOV VREG_ADR1, <(comparator_addr4+1)
MOV VREG_ADR1+1, >(comparator_addr4+1)
MOV VREG_ADR1, <(comparator_a_addr4+1)
MOV VREG_ADR1+1, >(comparator_a_addr4+1)
XFER VREG_STEP1, (0)
XFER VREG_PORT1, (0)
VNOP ; one waitcycle needed for the write above to land
comparator_addr4:
comparator_a_addr4:
SETA 0 ; this will be modified
VJNZA back_from_comparator_a
DECB ; I/O indicator
BRA character_in

; comparator checks if both lo- and hi-byte are equal to $ff.
comparator_b:
comparator_b_addr:
MOV VREG_STEP0, 0 ; low byte
MOV VREG_ADR0, <(iocheck_table+$80)
MOV VREG_ADR0+1, >(iocheck_table+$80)
MOV VREG_ADR1, <(comparator_b_addr2+1)
MOV VREG_ADR1+1, >(comparator_b_addr2+1)
XFER VREG_STEP1, (0)
XFER VREG_PORT1, (0)
VNOP ; one waitcycle needed for the write above to land
comparator_b_addr2:
SETA 0 ; this will be modified
VJNZA back_from_comparator_b

comparator_b_addr3:
MOV VREG_STEP0, 0 ; hi byte
MOV VREG_ADR0, <(iocheck_table+$80)
MOV VREG_ADR0+1, >(iocheck_table+$80)
MOV VREG_ADR1, <(comparator_b_addr4+1)
MOV VREG_ADR1+1, >(comparator_b_addr4+1)
XFER VREG_STEP1, (0)
XFER VREG_PORT1, (0)
VNOP ; one waitcycle needed for the write above to land
comparator_b_addr4:
SETA 0 ; this will be modified
DECA
char_out_done:
BRA back_from_comparator
BRA character_out
comparator_trampoline:
BRA comparator
BRA back_from_comparator_b
DECB ; I/O indicator
VJMP character_out
comparator_b_trampoline:
BRA comparator_b


dl_restart: ;; new frame starts here
Expand All @@ -166,12 +229,45 @@ mainloop: ;; new instruction processing starts here
XFER VREG_PORT1, (0) ;; lo byte of new PC (if positive), branch after DECA not taken, just address of the next instruction
XFER VREG_PORT1, (0) ;; hi byte of new PC (if positive), branch after DECA not taken, just address of the next instruction

;; copy address of [a] into place where [a] will be read
;; copy address of [a] into places where [a] will be read

.if 1 ;; three cycles shorter, but harder on the eyes
MOV VREG_ADR1, <(addr1+1)
MOV VREG_ADR1+1, >(addr1+1)
XFER VREG_PORT1, (0) ;; lo byte of [a]
XFER VREG_PORT1, (0) ;; hi byte of [a]

MOV VREG_STEP0, -1
MOV VREG_STEP1, (comparator_a_addr3 - comparator_a_addr1)
XFER VREG_ADR1, (0)

MOV VREG_ADR1, <(comparator_a_addr1 + 1)
MOV VREG_ADR1+1, >(comparator_a_addr1 + 1)
XFER VREG_PORT1, (0)
XFER VREG_PORT1, (0)

MOV VREG_STEP0, 3
XFER VREG_STEP1, (0)
.else
MOV VREG_STEP0, 0
MOV VREG_ADR1, <(addr1+1)
MOV VREG_ADR1+1, >(addr1+1)
XFER VREG_PORT1, (0) ;; lo byte of [a]
MOV VREG_ADR1, <(comparator_a_addr1 + 1)
MOV VREG_ADR1+1, >(comparator_a_addr1 + 1)
MOV VREG_STEP0, 1
XFER VREG_PORT1, (0) ;; lo byte of [a]

MOV VREG_STEP0, 0
MOV VREG_ADR1, <(addr1+3)
MOV VREG_ADR1+1, >(addr1+3)
XFER VREG_PORT1, (0) ;; hi byte of [a]
MOV VREG_ADR1, <(comparator_a_addr3 + 1)
MOV VREG_ADR1+1, >(comparator_a_addr3 + 1)
MOV VREG_STEP0, 1
XFER VREG_PORT1, (0) ;; hi byte of [a]
.endif

;; copy address of [b] into three places: to read value to be negated and to store result of [b]-[a]
MOV VREG_STEP0, 0
MOV VREG_STEP1, 0
Expand All @@ -181,8 +277,8 @@ mainloop: ;; new instruction processing starts here
MOV VREG_ADR1, <(addr2_2+1)
MOV VREG_ADR1+1, >(addr2_2+1)
XFER VREG_PORT1, (0) ;; lo byte of [b]
MOV VREG_ADR1, <(comparator_addr+1)
MOV VREG_ADR1+1, >(comparator_addr+1)
MOV VREG_ADR1, <(comparator_b_addr+1)
MOV VREG_ADR1+1, >(comparator_b_addr+1)
MOV VREG_STEP0, 1 ; advance after next read
XFER VREG_PORT1, (0) ;; lo byte of [b]

Expand All @@ -193,10 +289,14 @@ mainloop: ;; new instruction processing starts here
MOV VREG_ADR1, <(addr2_2+1+2)
MOV VREG_ADR1+1, >(addr2_2+1+2)
XFER VREG_PORT1, (0) ;; hi byte of [b]
MOV VREG_ADR1, <(comparator_addr3 + 1)
MOV VREG_ADR1+1, >(comparator_addr3 + 1)
MOV VREG_ADR1, <(comparator_b_addr3 + 1)
MOV VREG_ADR1+1, >(comparator_b_addr3 + 1)
XFER VREG_PORT1, (0) ;; hi byte of [b]

SETB 2 ; I/O operation marker
VJMP comparator_a
back_from_comparator_a:
MOV VREG_STEP0, 0
;; read value from [a], put as step 0 to be negated
addr1:
MOV VREG_ADR0, 0 ; this will be modified
Expand All @@ -210,8 +310,21 @@ addr1:
MOV VREG_ADR1+1, $ff
XFER VREG_PORT1, (0)

BRA comparator_trampoline
back_from_comparator:
BRA comparator_b_trampoline
back_from_comparator_b:

DECB
BRA no_output
BRA pcpos ; counter B == 0, output requested, we can skip all the arithmetics and B storing
no_output:
DECB
BRA no_input
MOV VREG_ADR0, $ff ; input received, we only need to store it in B
MOV VREG_ADR0+1, $ff
SETB 1 ; this will cause the arithmetics to be skipped
BRA just_store_a
no_input:

MOV VREG_STEP0, 0
MOV VREG_STEP1, 0
addr2:
Expand Down Expand Up @@ -258,13 +371,16 @@ addrval_b:
MOV VREG_STEP1, 0 ; PORT1 will be written thrice, we only want to know last value
MOV VREG_ADR0, <(addtable+$100) ; middle of the table, position of '0'
MOV VREG_ADR0+1, >(addtable+$100) ; middle of the table, position of '0'
just_store_a:
addr2_2:
;; store result in b
MOV VREG_ADR1, 0 ; this will be modified
MOV VREG_ADR1+1, 0 ; this will be modified
addrval_aneg2:
MOV VREG_STEP0, 0 ; step here will be -[a] value (-128,127)
XFER VREG_PORT1, (0) ; read value at 0, move from 0 to -[a] value
DECB ; check input marker - if we're executing the IO path (B is 1),
BRA pcpos ; we've done all we need to, so let's move on to the next instruction
addrval_b2:
MOV VREG_STEP0, 0 ; step here will be [b] value (-128,127)
XFER VREG_PORT1, (0) ; read value at -[a], move from -[a] to [b] value
Expand All @@ -287,6 +403,8 @@ pcleq:
pcrun:
d020_val:
MOV $20, 6 ; debug indicator
MOV VREG_DLIST2L, <mainloop
MOV VREG_DLIST2H, >mainloop
SKIP
frame_end:
WAIT NTSC_LAST_SAFE_LINE,0 ; default frame-end marker suitable for NTSC
Expand Down Expand Up @@ -347,11 +465,11 @@ iocheck_table:
.word :+ ; if ommited then point to the next instruction
.endif
.word :+ ; link to next instruction (required for VASYL, not existing in pure Subleq)
.word addr_a ; [a]
.word (addr_a & $ffff) ; [a]
.ifnblank addr_b
.word (addr_b & $ffff) ; [b], [b]<-[b]-[a] ;; "& $ffff" enables negative values
.else
.word addr_a ; if 2nd argument is omitted reuse [a]
.word (addr_a & $ffff) ; if 2nd argument is omitted reuse [a]
.endif
:
.endmacro
Expand All @@ -373,6 +491,14 @@ vm_start:

subleq negone, three ; 3-(-1)=4, three=4


typist:
subleq -1, char
subleq char, -1
subleq char_w, char, typist ; break on 'X'
subleq one, char, print_hello
subleq zero, zero, typist

; Following code only works by lucky coincidence - just the lo-bytes of ptrs need to be adjusted.
; We need 16-bit arithmetics!
print_hello:
Expand Down Expand Up @@ -410,6 +536,9 @@ isseven: .byte 0
negone: .byte $ff
hello_txt: .byte "vasyl says hello!", $0d, 0
char_counter: .byte 0
char: .byte 0
char_w: .byte "w"
char_x: .byte "x"

; all the exports for debug purposes
; vpeek(seven) should be 4
Expand Down Expand Up @@ -441,3 +570,4 @@ char_counter: .byte 0
.export setaval
.export d020_val
.export iocheck_table
.export char