-
Notifications
You must be signed in to change notification settings - Fork 10
/
cvbasic_9900_prologue.asm
1958 lines (1723 loc) · 54.1 KB
/
cvbasic_9900_prologue.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;
; CVBasic prologue (BASIC compiler, 9900 target)
;
; by Tursi
; https//harmlesslion.com
;
; based on code by
;
; by Oscar Toledo G.
; https//nanochess.org/
;
; Creation date Aug/05/2024.
; Revision date Aug/06/2024. Ported music player from Z80 CVBasic.
; Revision date Aug/07/2024. Ported Pletter decompressor from Z80 CVBasic.
; Added VDP delays.
; Revision date Aug/12/2024. Rewrite started for TMS9900 cpu
; Revision date Aug/16/2024. Corrected bug in define_char_unpack.
; Revision date Aug/18/2024. Ported bugfixes to TMS9900 version
; Revision date Aug/30/2024. All samples except pletter and banking working on TMS9900 version
; Revision date Oct/15/2024. Added LDIRMV.
;
; Platforms supported:
; o TI-99/4A with 32k Memory Expansion
; this is intended to be assembled by xdt99, no console ROM dependencies
; cvbasic --ti994a test1.bas test1.a99
; xas99.py -R test1.a99
; When looking at this - remember that JUMP and BRANCH have the /opposite/
; meanings to the 6502 - JUMP is the short relative one, and BRANCH is absolute.
;
; CVBasic variables in scratchpad.
;
; don't warn on branch/jump optimizations or unused variables
;: warn-opts = off
;: warn-symbols = off
; use original (possibly incorrectly ported) random
OLD_RND equ 0
; We have to use our own workspace, not GPLWS, because the interrupt routine makes it unsafe to
; use r11, which we kind of need! So that eats 32 bytes of RAM but means most of the register
; restrictions I wrote this code on (avoid R1, avoid R6, avoid R13-R15) are lifted.
mywp equ >8300
myintwp equ >8320
; data storage in scratchpad
dorg >8340
; used to track scratchpad variables
firstsp equ $
read_pointer bss 2 ; for data/read statements
cursor bss 2 ; screen position
pletter_off bss 2 ; Used by Pletter
; joystick bytes
joy1_data bss 1 ; keep these bytes together and even aligned
joy2_data bss 1
key1_data bss 1 ; byte - keyboard - keep these bytes together and even aligned
key2_data bss 1 ; byte - keyboard (not used)
frame bss 2 ; word
lfsr bss 2 ; word MUST BE EVEN ALIGNED
mode bss 1
flicker bss 1
ntsc bss 1
.ifne CVBASIC_MUSIC_PLAYER
music_playing bss 1
music_start bss 2 ; word MUST BE EVEN ALIGNED
music_pointer bss 2 ; word MUST BE EVEN ALIGNED
audio_freq1 bss 2 ; word MUST BE EVEN ALIGNED
audio_freq2 bss 2 ; word MUST BE EVEN ALIGNED
audio_freq3 bss 2 ; word MUST BE EVEN ALIGNED
music_timing bss 1
music_note_counter bss 1
music_instrument_1 bss 1
music_note_1 bss 1
music_counter_1 bss 1
music_instrument_2 bss 1
music_note_2 bss 1
music_counter_2 bss 1
music_instrument_3 bss 1
music_note_3 bss 1
music_counter_3 bss 1
music_drum bss 1
music_counter_4 bss 1
audio_vol1 bss 1
audio_vol2 bss 1
audio_vol3 bss 1
audio_vol4hw bss 1
audio_noise bss 1
audio_control bss 1
music_mode bss 1
music_frame bss 1
.endif
.ifne CVBASIC_BANK_SWITCHING
even
saved_bank bss 2
music_bank bss 2
.endif
; used to track scratchpad variables
even
lastsp equ $
; While we don't mean to USE the console ROM, for interrupts we
; are forced to interface with some of it. We need these addresses
; to minimize what it does so we can maximize our use of scratchpad.
; While I'd like to use the cassette hook - requires only 10 instructions
; and only uses 6 words of scratchpad, we can't here because it
; loses the return address, meaning you can only use it if you
; know where your LIMI 2 is and interrupts are otherwise disabled. So
; we have to use the longer but more standard interrupt hook, which also
; reads VDP status for us (no choice).
intcnt equ >8379 ; interrupt counter byte, adds 1 (from GPLWS r14) every frame
vdp_status equ >837B ; VDP status byte mirror
intwsr1 equ >83c2 ; INT WS R1 - interrupt control flags - must be >8000
intwsr2 equ >83c4 ; INT WS R2 - address of user interrupt routine (point to int_handler)
intwsr11 equ >83d6 ; screen timeout counter - must be odd (init to 1, is inct every frame)
intwsr13 equ >83da ; INT WS R13 - used for interrupt call (word)
intwsr14 equ >83dc ; INT WS R14 - used for interrupt call (word)
intwsr15 equ >83de ; INT WS R15 - used for interrupt call (word)
gplwsr11 equ >83f6 ; GPL WS R11 - return address to interrupt ROM (not used, but overwritten each int)
gplwsr12 equ >83f8 ; GPL WS R12 - used for cassette test and interrupt hook test (zeroed each int)
gplwsr13 equ >83fa ; GPL WS R13 - used in my interrupt handler
gplwsr14 equ >83fc ; GPL WS R14 - flags used to detect cassette - must be >0108 (or at least >0020 clear)
gplwsr15 equ >83fe ; GPL WS R15 - base address of VDP for status read - must be >8C02
; Some hardware equates
INTWS equ >83C0 ; interrupt calling Workspace
GPLWP equ >83E0 ; we use this one
SOUND equ >8400
VDPDATA equ >8800
VDPSTATUS equ >8802
VDPWDATA equ >8c00
VDPWADR equ >8c02
; cartridge header for all ROM pages
; this might do weird things to bank 0 but we have to chop it up anyway...
bank all,>6000
data >aa01,>0100,>0000,proglist,>0000,>0000
proglist
data >0000,SFIRST
byte 20
text 'CVBASIC GAME *' ; 20 characters to allow name to be hex edited
; startup code copies the first three banks to 24k RAM (always) and jumps there
SFIRST
clr @>6000 ; set bank 0 - last shared instruction
bank 0
li r3,3 ; how many banks
li r4,>6000 ; from bank
li r0,>a000 ; target in RAM
SFLP
clr *r4+ ; set the bank
li r1,>6050 ; from address
li r2,>1FB0 ; count
SFLP2
mov *r1+,*r0+ ; move words
dect r2 ; count down
jne SFLP2
dec r3 ; count down pages
jne SFLP
CODEST
b @START ; jump to startup code in RAM
; fixed program in high RAM - magic bank number higher than normally legal
; this will be chopped up and inserted into the first three banks
; we're still in bank 0 - this will result in a gap in the output binary
; that we can use to put the pieces together more easily.
aorg >a000
; Utility functions
; Write register to VDP - R0 = reg in MSB, data in LSB
WRTVDP
ori r0,>8000
jmp SETRD
; Set VDP for write address - address in R0
SETWRT
ori r0,>4000
; fall through
; Set VDP for read address - address in R0
SETRD
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
b *r11
; Write byte to VDP - address in R0, data in MSB R2
; Inline address set to avoid needing to cache r11
WRTVRM
ori r0,>4000
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
; No need to delay after setting a write address - there's no VRAM access
movb r2,@VDPWDATA
b *r11
; Read byte from VDP - address in R0, data returned in MSB R0
; Inline address set to avoid needing to cache r11
RDVRM
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
nop
movb @VDPDATA,r0
b *r11
; Fill VRAM - address in R0, byte in R2, count in R3
; Original: address in pointer, byte in temp, count in temp2 (ZP)
; Inline address set to avoid needing to cache r11
FILVRM
ori r0,>4000
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
; No need to delay after setting a write address - there's no VRAM access
!1
movb r2,@VDPWDATA
dec r3
jne -!1
b *r11
; Read VRAM - address in R0, CPU data at R2, count in R3
; Inline address set to avoid needing to cache r11
LDIRMV
swpb r2
movb r2,@VDPWADR
swpb r2
movb r2,@VDPWADR
swpb r2
swpb r2
!1
movb @VDPDATA,*r0+
dec r3
jne -!1
b *r11
; Load VRAM - address in R0, CPU data at R2, count in R3
; Original: address in pointer, CPU address at temp, count in temp2
; Inline address set to avoid needing to cache r11
LDIRVM
ori r0,>4000
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
; No need to delay after setting a write address - there's no VRAM access
!1
movb *r2+,@VDPWDATA
dec r3
jne -!1
b *r11
; Define a pattern three times with 2k offsets - used for bitmap color and pattern tables
; Load VRAM 3 times with offset - address in R0, CPU data at R2, count in R3
; Original: address in pointer, CPU address at temp, count in temp2
LDIRVM3
mov r11,r4 ; save return address
mov r2,r5 ; save CPU address
mov r3,r7 ; save count
bl @LDIRVM
ai r0,>0800 ; the OR'd mask doesn't matter
mov r5,r2 ; restore CPU
mov r7,r3 ; restore count
bl @LDIRVM
ai r0,>0800 ; the OR'd mask doesn't matter
mov r5,r2
mov r7,r3
mov r4,r11 ; for tail recursion
jmp LDIRVM
; Disable screen by setting VDP register 1 to >a2
DISSCR
limi 0
li r0,>81a2
DISSCR2
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
limi 2
b *r11
; enable screen by setting VDP register 1 to >E2
ENASCR
limi 0
li r0,>81e2
jmp DISSCR2
; copy a set of blocks of data to VDP, offset by 32 bytes each
; address in R8, CPU data at R9, count per row in R6 (MSB), number rows in R4 (MSB), CPU stride in R5 (MSB) (VDP stride fixed at 32)
; original: address in pointer, CPU address at temp, count per row in temp2, num rows in temp2+1, stride in YYXX
CPYBLK
limi 0
mov r11,r7 ; save return
srl r6,8
srl r4,8
srl r5,8 ; bytes to words
!1
mov r8,r0 ; get vdp address
mov r9,r2 ; get cpu address
mov r6,r3 ; get count
!2
bl @LDIRVM ; copy one row
a r5,r9 ; add stride to CPU address
ai r8,32 ; add 32 to VDP
dec r4 ; count down rows
jne -!1 ; loop till done
limi 2
b *r7 ; back to caller
; clear screen and reset cursor to >1800
cls
mov r11,r4 ; save return
li r0,>1800 ; SIT address
mov r0,@cursor ; save cursor
li r2,>2000 ; byte to write
li r3,768 ; number of bytes
limi 0 ; ints off
bl @FILVRM ; write them
limi 2 ; ints back on
b *r4 ; back to caller
; copy a string to screen at cursor - address enforced
; CPU address in R2, length in R3
print_string
mov r11,r4 ; save return
mov @cursor,r0 ; get cursor pos
andi r0,>07ff ; enforce position - pretty large range though? 80 column support maybe?
ai r0,>1800 ; add is safer than OR, and we have that option
a r3,@cursor ; add the count to cursor (might as well do it now!)
limi 0
bl @LDIRVM ; do the write
limi 2
b *r4 ; back to caller
; emit a 16-bit number as decimal with leading zero masking at cursor
; R0 - number to print
; original number in YYAA?
print_number
limi 0 ; interrupts off so we can hold the VDP address
clr r5 ; leading zero flag
print_number5
li r1,10000 ; divisor
mov r11,r4
bl @print_digit
mov r4,r11
print_number4
li r1,1000 ; divisor
mov r11,r4
bl @print_digit
mov r4,r11
print_number3
li r1,100 ; divisor
mov r11,r4
bl @print_digit
mov r4,r11
print_number2
li r1,10
mov r11,r4
bl @print_digit
mov r4,r11
print_number1
li r1,1
andi r5,>00ff
ori r5,>0100
mov r11,r4
bl @print_digit
limi 2 ; ints on
b *r4 ; back to caller
print_digit
mov r11,r6
clr r2
div r1,r2
ai r2,>30
ci r2,>30
jne !3
ci r5,>0100
jhe !4
b *r6
!4
ci r5,>0200
jl !6
mov r5,r2
jne !5
!6
li r2,>30
!3
andi r5,>00ff
ori r5,>0100
!5
mov @cursor,r0 ; get cursor
andi r0,>07ff ; enforce position - large range for two screen pages
ai r0,>1800 ; add is safer than OR, and we have that option
bl @SETWRT ; set write address
swpb r2
movb r2,@VDPWDATA
inc @cursor ; track it
b *r6
; Load sprite definitions: Sprite number in R4, CPU data in R0, count of sprites in R5 (MSB)
; Original: pointer = sprite number, temp = CPU address, a = number sprites
; Note: sprites are all expected to be double-size 16x16, 32 bytes each, so sprite char 1 is character 4
; Sprite pattern table at >3800
define_sprite
mov r11,r8 ; save return
mov r0,r2 ; Source data.
mov r4,r0 ; VRAM target.
sla r0,5 ; sprite number times 32 for bytes
ai r0,>3800 ; add VDP base
mov r5,r3 ; Length.
srl r3,8 ; make int
sla r3,5 ; count times 32
limi 0 ; ints off
bl @LDIRVM ; do the copy
limi 2 ; ints on
b *r8 ; back to caller
; Load character definitions: Char number in R1, CPU data in R2, count in R3 (MSB)
; Original: pointer = char number, temp = CPU address, a = number chars
; Note this loads the pattern three times if in bitmap mode (MODE&0x04)
; Pattern table at >0000
define_char
mov r11,r8 ; save return
mov r0,r2 ; source data
mov r4,r0 ; move input to scratch
sla r0,3 ; char number times 8 (VDP base is 0, so already there)
mov r5,r3
srl r3,8 ; make word
sla r3,3 ; count times 8
movb @mode,r5 ; get mode flags
andi r5,>0800
jne !1 ; not in bitmap mode, do a single copy
limi 0 ; ints off
bl @LDIRVM3 ; do the triple copy
limi 2 ; ints on
b *r8 ; back to caller
!1
limi 0 ; ints off
bl @LDIRVM ; do the single copy
limi 2 ; ints on
b *r8 ; back to caller
; Load bitmap color definitions: Char number in R1, CPU data in R2, count in R3 (MSB)
; Original: pointer = char number, temp = CPU address, a = number chars
; Note: always does the triple copy. Color table at >2000
define_color
mov r11,r8 ; save return
mov r0,r2 ; source data
mov r4,r0 ; move input to scratch
sla r0,3 ; char number times 8
ai r0,>2000 ; add base address
mov r5,r3
srl r3,8 ; make word
sla r3,3 ; count times 8
limi 0 ; ints off
bl @LDIRVM3 ; do the triple copy
limi 2 ; ints on
b *r8 ; back to caller
; Update sprite entry - copy data (4 bytes) to sprite table mirror at sprites
; R4 = sprite number, R5 = byte 1, r6 = byte 2, r7 = byte 3, r0 = byte 4 (all MSB)
; Original: A = sprite number, source data at sprite_data
update_sprite
srl r4,8 ; make word
sla r4,2 ; x4 for address
ai r4,sprites ; sprite mirror address
movb r5,*r4+ ; move bytes
movb r6,*r4+ ; move bytes
movb r7,*r4+ ; move bytes
movb r0,*r4+ ; move bytes
b *r11
; SGN R0 - return 1, -1 or 0 as 16 bit
_sgn16
mov r0,r0 ; check for zero
jeq !1 ; if yes, we're done
andi r0,>8000 ; check for negative
jeq !2 ; was not
seto r0 ; was negative, make it -1
b *r11 ; back to caller
!2
inc r0 ; we know it was zero, and we want 1
!1
b *r11 ; back to caller
; 16-bit signed modulo. R1 % R2 = R0 - 9900 doesn't do signed divide
; original was stack%stack=YYAA
; Remainder is negative if the dividend was negative
_mod16s
clr r0 ; make dividend 32-bit
mov r2,r2 ; check divisor for zero
jeq !1
abs r2 ; make sure divisor is positive
mov r1,r1 ; check sign of dividend
jgt !2 ; go do the faster positive version
abs r1 ; was negative, make it positive
div r2,r0 ; do the division => r2=quotient, r3=remainder
neg r1 ; make remainder negative
mov r1,r0 ; into r0
b *r11
!2
div r2,r0 ; do the division => r2=quotient, r3=remainder
mov r1,r0 ; into r0
!1
b *r11
; 16-bit signed divide. R1 / R2 = R0 - 9900 doesn't do signed divide
; original was stack/stack=YYAA
; Remainder is negative if the signs differ
_div16s
clr r0 ; make dividend 32-bit
mov r2,r3 ; check divisor for zero
jeq !1 ;
xor r1,r3
abs r2
abs r1 ; might as well make them positive now that we have copies
div r2,r0 ; do the divide => r0=quotient, r1=remainder
andi r3,>8000 ; mask out sign bit
jeq !1 ; skip ahead to positive version
neg r0 ; negate the result
!1
b *r11
; Random number generator - return in R0, (complex one uses R3,R4, simpler one only R0)
; Original output into YYAA
random
.ifne OLD_RND
; TODO: Not 100% sure I ported this one right... probably could be simpler with 16-bit manips...
mov @lfsr,r0 ; fetch current state
jne !0
li r0,>7811 ; reset value if zero
mov r0,@lfsr
!0
movb @lfsr+1,r0
movb @mywp,@mywp+1 ; trick, copy msb to lsb (so the 16-bit rotate works)
mov r0,r3 ; we use this again
src r0,2 ; circular rotate twice (rotates directly like z80)
xor @lfsr+1,r0 ; because of 16 bit addressing, only the LSB is correct
movb @mywp+1,@mywp ; fix up - copy LSB to MSB
mov r0,r4 ; save it (temp)
src r3,1 ; rotate the second read once
xor r3,r4 ; xor into the temp copy
movb @lfsr,r0 ; get the lsb
sla r0,2 ; just a straight shift
xor r4,r0 ; xor the temp copy in (both bytes of r4 were valid)
mov @lfsr,r4 ; get word for shifting
srl r4,1 ; shift once
socb r0,r4 ; merge in the msb we just generated
mov r4,@lfsr ; write it back
mov r4,r0 ; for return
b *r11
.else
; simpler one from dreamcast days...
mov @lfsr,r0 ; get seed
srl r0,1 ; shift
jnc .rand1 ; jump if no 1
xor @rmask,r0 ; xor new bits
.rand1
mov r0,@lfsr ; save the output
b *r11
rmask
data >b400 ; mask for 16 bit
.endif
; Set SN Frequency: R0=freqency code, R2=channel command (MSB)
; Original: A=least significant byte X=channel command Y=most significant byte
sn76489_freq
mov r0,r3
andi r3,>000f
swpb r3
socb r3,r2
movb r2,@SOUND ; cmd and least significant nibble
srl r0,4
andi r0,>003f
swpb r0
movb r0,@SOUND ; most significant byte
b *r11
; Set SN volume: R0=volume (MSB, inverse of attenuation), R2=channel command (MSB)
; Original: A=volume (inverse of attenuation), X=channel command
sn76489_vol
inv r0
andi r0,>0f00
socb r2,r0
movb r0,@SOUND
b *r11
; Set noise type: R0=Noise type (MSB)
; original: A=noise command
sn76489_control
andi r0,>0f00
ori r0,>e000
movb r0,@SOUND
b *r11
; Set up vdp generic settings - R0 should be preloaded with a register in MSB, data in LSB
; R2 should contain the color table entry (in MSB), R3 the bitmap table (in MSB). Rest is
; hard coded. WARNING: Disables interrupts but does not re-enable them.
vdp_generic_mode
mov r11,r4 ; save return
limi 0 ; ints off
bl @WRTVDP ; caller must set up this one
li r0,>01a2 ; VDP mode, screen off
bl @WRTVDP
li r0,>0206 ; >1800 pattern table
bl @WRTVDP
li r0,>0003 ; for color table
socb r2,r0
swpb r0
bl @WRTVDP
li r0,>0004 ; for pattern table
socb r3,r0
swpb r0
bl @WRTVDP
li r0,>0536 ; >1b00 for sprite attribute table
bl @WRTVDP
li r0,>0607 ; >3800 for sprite pattern table
bl @WRTVDP
li r0,>0701 ; default screen color
bl @WRTVDP
b *r4
; set up VDP mode 0
mode_0
mov r11,r8 ; careful - we call vdp_generic_mode and LDIRVM3
li r0,>0800 ; bit we want to clear
szcb r0,@mode
li r2,>ff00 ; $2000 for color table.
li r3,>0300 ; $0000 for bitmaps
li r0,>0002 ; r0 setting
bl @vdp_generic_mode ; interrupts are now off
li r0,>0100 ; target in VDP memory
li r2,font_bitmaps ; CPU memory source
li r3,>0300 ; number of bytes
bl @LDIRVM3
limi 2
limi 0
li r0,>2000
li r2,>f000
li r3,>1800 ; fill color table with white on transparent
bl @FILVRM
limi 2
bl @cls
mov r8,r11 ; restore return address, and fall through to vdp_generic_sprites
; Initialize sprite table
vdp_generic_sprites
mov r11,r8 ; save return address
li r0,>1b00 ; sprite attribute table in VDP
li r2,>d100 ; off screen, and otherwise unimportant
li r3,128 ; number of bytes
limi 0
bl @FILVRM
li r0,sprites
li r2,>d1d1 ; write 2 bytes at a time
li r3,128
!1
mov r2,*r0+ ; initialize CPU mirror
dect r3
jne -!1
li r0,>01e2 ; screen on
bl @WRTVDP
limi 2
b *r8
; set up VDP mode 1
mode_1
mov r11,r8 ; careful - we call vdp_generic_mode and LDIRVM3
li r0,>0800 ; bit we want to clear
szcb r0,@mode
li r2,>ff00 ; $2000 for color table.
li r3,>0300 ; $0000 for bitmaps
li r0,>0002 ; r0 setting
bl @vdp_generic_mode ; interrupts are now off
li r0,>0000
li r2,>0000
li r3,>1800
bl @FILVRM ; clear pattern table
limi 2
li r0,>2000
li r2,>f000
li r3,>1800
limi 0
bl @FILVRM ; init color table
limi 2
li r0,>5800 ; >1800 with the write bit set
clr r3 ; value to write
!1
limi 0 ; write the screen image table, but pause every 32 bytes for interrupts
swpb r0
movb r0,@VDPWADR
swpb r0
movb r0,@VDPWADR
li r2,32
!2
movb r3,@VDPWDATA
ai r3,>0100
dec r2
jne -!2
limi 2
ai r0,32
ci r0,>5b00
jl -!1
mov r8,r11 ; restore return address
jmp vdp_generic_sprites ; using tail recursion
; Set up VDP mode 2
mode_2
mov r11,r8 ; careful - we call vdp_generic_mode and LDIRVM3
li r0,>0800 ; bit we want to set
socb r0,@mode
li r2,>8000 ; $2000 for color table.
li r3,>0000 ; $0000 for bitmaps
li r0,>0000 ; r0 setting
bl @vdp_generic_mode ; interrupts are now off
li r0,>0100
li r2,font_bitmaps
li r3,>0300
bl @LDIRVM ; load character set
limi 2
limi 0
li r0,>2000
li r2,>f000
li r3,>0020
bl @FILVRM ; init color table
limi 2
bl @cls ; clear screen
mov r8,r11 ; restore return
b @vdp_generic_sprites
; this is where interrupts happen every frame
; Unlike a normal TI application, this one runs with interrupts ON,
; so all operations need to be sure to protect VDP address with LIMI 0,
; as well as any operations that might need to manipulate data managed
; by this interrupt. We enter via the normal user hook, so WP is on
; GPLWS, interrupts are off, and the VDP is already reset and status
; stashed on vdp_status (>837b). Our return address to the ROM is in
; r11, but we are NOT going to use it so that we don't need to reserve
; r8 for whatever nonsense it does. That means we need to load intws
; and RTWP ourselves at the end. We are on our own workspace so we
; don't have to worry about the main app's workspace.
int_handler
; first copy the sprite table
lwpi myintwp ; separate safe workspace
.ifne CVBASIC_BANK_SWITCHING
mov @>7ffe,@saved_bank ; save bank switch page
.endif
li r11,>005b ; >1b00 with the write bit added, and byte flipped
movb r11,@VDPWADR ; SAL address
swpb r11
movb r11,@VDPWADR ; going to copy the sprite table to VDP
movb @mode,r11
andi r11,>0400 ; if bit >04 (inhibit flicker) is cleared, jump ahead to rotate
jeq !4
clr r11 ; else we're going to just write it straight across
li r12,128
li r13,sprites
!7
movb *r13+,@VDPWDATA
dec r12
jne -!7
jmp !5
!4
movb @flicker,r11 ; here we write it rotated every frame
ai r11,>0400
andi r11,>7f00
movb r11,@flicker
swpb r11 ; make count
li r12,32 ; count
!6
ai r11,sprites ; this is still faster than separate incs
movb *r11+,@VDPWDATA ; copy one sprite
movb *r11+,@VDPWDATA ; no delay needed
movb *r11+,@VDPWDATA
movb *r11,@VDPWDATA ; small optimization, since we have an add coming anyway
ai r11,-(sprites-1) ; remove address and add the rest of the increment
andi r11,>007F ; clamp it in range
dec r12
jne -!6
!5
; next read the joysticks - output needs to be 21xxLDRU - 1 and 2 are button and button2 respectively
; We don't have a button 2. We also need to read the keyboard and fill in key1_data. key2_data we
; will leave unused. Note key1_data expects Coleco-style 0-9,10-*,11-#,15=not pressed, but we can throw
; everything else as ASCII. We could do a split keyboard for 2 players, but I guess we'll leave it for now.
; joy1
li r12,>0024 ; CRU base of select output
li r13,>0600 ; joystick 1 column
ldcr r13,3 ; select it
src r12,7 ; delay
li r12,>0006 ; CRU base of return read
stcr r13,8 ; read 8 bits (we could get away with fewer, but be consistent)
bl @convert_joystick
movb r12,@joy1_data
; joy2
li r12,>0024 ; CRU base of select output
li r13,>0700 ; joystick 2 column
ldcr r13,3 ; select it
src r12,7 ; delay
li r12,>0006 ; CRU base of return read
stcr r13,8 ; read 8 bits (we could get away with fewer, but be consistent)
bl @convert_joystick
movb r12,@joy2_data
; do a quick modifier read for button 2 (control and fctn for joy 1 and 2 respectively)
li r12,>0024 ; CRU base of select output
clr r13 ; modifiers
ldcr r13,3 ; select it
src r12,7 ; delay
li r12,>0006 ; CRU base of return read
stcr r13,8 ; read 8 bits (we could get away with fewer, but be consistent)
li r12,>4000 ; control
li r11,>8000 ; button 2 bit
czc r12,r13
jne .noc1b2
socb r11,@joy1_data
.noc1b2
li r12,>1000 ; fctn
czc r12,r13
jne .noc2b2
socb r11,@joy2_data
.noc2b2
; key1 - this is a very simple read with no modifiers, it just gives access to the letters and numbers
clr r11 ; column
!key1
li r12,>0024 ; CRU base of select output
ldcr r11,3 ; select column
src r12,7 ; delay
li r12,>0006 ; CRU base of return read
stcr r13,8 ; get the bits
li r12,7 ; bit search
!key2
sla r12,1
czc @masktable(r12),r13 ; bit set?
jne !key3 ; continue
srl r12,1
srl r11,5
a r12,r11 ; calculate table offset
movb @keyboard_table(r11),@key1_data ; might be a dead key, but that's okay
jmp !key4
!key3
srl r12,1
dec r12
jgt -!key2
jeq -!key2 ; we don't have a jump if not negative
ai r11,>0100
ci r11,>0600
jne -!key1
li r11,>0f00
movb r11,@key1_data ; no key was pressed
!key4
; check for quit - attempts to work it into the above were not working
; borrowed from console ROM
li r12,>0024
ldcr r12,3
src r12,7
li r12,>0006
stcr r11,8
li r12,>1100
czc r12,r11
jne .noquit
clr @intwsr2
blwp @>0000
.noquit
.ifne CVBASIC_MUSIC_PLAYER
movb @music_mode,r0
jeq !10
bl @music_hardware
!10
.endif
inc @frame
li r0,3
a r0,@lfsr ; Make LFSR more random
.ifne CVBASIC_MUSIC_PLAYER
movb @music_mode,r0
jeq !9
movb @music_frame,r0
ai r0,>0100
ci r0,>0500
jl !11
clr r0
movb r0,@music_frame
jhe !9
!11 movb r0,@music_frame
bl @music_generate
!9
.endif
;CVBASIC MARK DON'T CHANGE
; restore the saved bank
.ifne CVBASIC_BANK_SWITCHING
mov @saved_bank,r0 ; recover page switch
clr *r0 ; switch it
.endif
; get back the interrupt workspace and return
lwpi INTWS
RTWP
; given a joystick read in r13, return bits in r12
; The final output is 8 bits:
; 21xxLDRU - 1 and 2 are button and button2 respectively
; NOTE: if called by the compiler, this won't act as expected
convert_joystick
clr r12
czc @joystick_table,r13
jne !j1
ori r12,>0800
!j1
czc @joystick_table+2,r13
jne !j2
ori r12,>0400
!j2
czc @joystick_table+4,r13
jne !j3
ori r12,>0200