-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathROMv5a.asm.py
executable file
·5684 lines (5187 loc) · 207 KB
/
ROMv5a.asm.py
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
#!/usr/bin/env python3
#-----------------------------------------------------------------------
#
# Core video, sound and interpreter loop for Gigatron TTL microcomputer
#
#-----------------------------------------------------------------------
#
# Main characteristics:
#
# - 6.25 MHz clock
# - Rendering 160x120 pixels at 6.25MHz with flexible videoline programming
# - Must stay above 31 kHz horizontal sync --> 200 cycles/scanline
# - Must stay above 59.94 Hz vertical sync --> 521 scanlines/frame
# - 4 channels sound
# - 16-bits vCPU interpreter
# - 8-bits v6502 emulator
# - Builtin vCPU programs (Snake, Racer, etc) loaded from unused ROM area
# - Serial input handler, supporting ASCII input and two game controller types
# - Serial output handler
# - Soft reset button (keep 'Start' button down for 2 seconds)
# - Low-level support for I/O and RAM expander (SPI and banking)
#
#-----------------------------------------------------------------------
#
# ROM v2: Mimimal changes
#
# DONE Snake color upgrade (just white, still a bit boring)
# DONE Sound continuity fix
# DONE A-C- mode (Also A--- added)
# DONE Zero-page handling of ROM loader (SYS_Exec_88)
# DONE Replace Screen test
# DONE LED stopped mode
# DONE Update font (69;=@Sc)
# DONE Retire SYS_Reset_36 from all interfaces (replace with vReset)
# DONE Added SYS_SetMemory_54 SYS_SetVideoMode_80
# DONE Put in an example BASIC program? Self list, self start
# DONE Move SYS_NextByteIn_32 out page 1 and rename SYS_LoaderNextByteIn_32
# Same for SYS_PayloadCopy_34 -> SYS_LoaderPayloadCopy_34
# DONE Update version number to v2a
# DONE Test existing GT1 files, in all scan line modes
# DONE Sanity test on HW
# DONE Sanity test on several monitors
# DONE Update version number to v2
#
#-----------------------------------------------------------------------
#
# ROM v3: New applications
#
# DONE vPulse width modulation (for SAVE in BASIC)
# DONE Bricks
# DONE Tetronis
# DONE TinyBASIC v2
# DONE TicTacToe
# DONE SYS spites/memcpy acceleration functions (reflections etc.)
# DONE Replace Easter egg
# DONE Update version number to v3
#
#-----------------------------------------------------------------------
#
# ROM v4: Numerous small updates, no new applications
#
# DONE #81 Support alternative game controllers (TypeC added)
# DONE SPI: Setup SPI at power-on and add 'ctrl' instruction to asm.py
# DONE SPI: Expander control (Enable/disable slave, set bank etc)
# DONE SPI: SYS Exchange bytes
# DONE SYS: Reinitialize waveforms at soft reset, not just at power on
# DONE v6502: Prototype. Retire bootCount to free up zp variables
# DONE v6502: Allow soft reset when v6502 is active
# DONE Apple1: As easter egg, preload with WozMon and Munching6502
# DONE Apple1: Don't use buttonState but serialRaw
# DONE Snake: Don't use serialRaw but buttonState
# DONE Snake: Head-only snake shouldn't be allowed to turn around #52
# DONE Snake: Improve game play and colors in general
# DONE Snake: Tweak AI. Also autoplayer can't get hiscore anymore
# DONE Racer: Don't use serialRaw but buttonState
# DONE Racer: Faster road setup with SYS_SetMemory
# DONE Makefile: Pass app-specific SYS functions on command line (.py files)
# DONE Main: "Press [A] to start": accept keyboard also (incl. 'A') #38
# DONE Add 4 arrows to font to fill up the ROM page
# DONE Mode 1975 (for "zombie" mode), can do mode -1 to recover
# DONE TinyBASIC: support larger font and MODE 1975. Fix indent issue #40
# DONE Add channelMask to switch off the higher sound channels
# DONE Loader: clear channelMask when loading into sound channels
# DONE Update romTypeValue and interface.json
# DONE Update version number to v4
# DONE Formally Support SPI and RAM expander: publish in interface.json
# DONE Use `inspect' to make program listing with original comments #127
#
#-----------------------------------------------------------------------
#
# Ideas for ROM v5:
#
# DONE v6502: Test with VTL02
# DONE v6502: Test with Microchess
# DONE Sound: Better noise by changing wavX every frame (at least in channel 1)
# DONE Sound demo: Play SMB Underworld tune
# DONE SPI: Also reset state at soft reset
# DONE Fix clobbering of 0x81 by SPI SYS functions #103
# DONE Control variable to black out the area at the top of the screen
# DONE Fix possible video timing error in Loader #100
# DONE Fix zero page usage in Bricks and Tetronis #41
# DONE Add CALLI instruction to vCPU
# XXX Add CMPHS/CMPHU instructions to vCPU XXX Still needs testing
# DONE Main: add Apple1 to main menu
# DONE Replace egg with something new
# DONE Split interface.json and interface-dev.json
# DONE MSBASIC
# DONE Speed up SetMemory by 300% using bursts #126
# DONE Discoverable ROM contents #46
# DONE Vertical blank interrupt #125
# DONE TinyBASIC: Support hexadecimal numbers $....
# XXX Expander: Auto-detect banking, 64K and 128K -> needs FIX
# DONE Cardboot: Boot from *.GT1 file if SDC/MMC detected
# XXX CardBoot: Strip non-essentials
# XXX CardBoot: Fix card type detection
# XXX CardBoot: Read full sector
# DONE Apple-1: Memory mapped PIA emulation using interrupt (D010-D013)
# DONE Apple-1: Include A1 Integer BASIC
# DONE Apple-1: Suppress lower case
# DONE Apple-1: Include Mastermind and 15-Puzzle
# DONE Apple-1: Include mini-assembler
# DONE Apple-1: Intercept cassette interface = menu
# XXX Reduce the Pictures application ROM footprint #120
# XXX Mandelbrot: add more color schemes, eg. with permutations of RGB
# XXX Main: Better startup chime, eg. sequence the 4 notes and then decay
# XXX Main: Some startup logo as intro, eg. gigatron letters from the box
# XXX Racer: Control speed with up/down (better for TypeC controllers)
# XXX Racer: Make noise when crashing
# XXX Loader: make noise while loading (only channel 1 is safe to use)
# XXX Faster SYS_Exec_88, with start address (GT1)?
# XXX Let SYS_Exec_88 clear channelMask when loading into live channels
# XXX Investigate: Babelfish sometimes freezes during power-on?
#
# Ideas for ROM v6+
# XXX ROM functions: SYS_PrintString, control codes, SYS_DrawChar, SYS_Newline
# XXX v8080 prepping for CP/M
# XXX vForth virtual CPU
# XXX Video: Increase vertical resolution with same videoTable (160 lines?)
# XXX Video mode for 12.5 MHz systems
# XXX Pucrunch (well documented) or eximozer 3.0.2 (better compression)
# XXX SPI: Think about SPI modes (polarities)
# XXX I2C: Turn SPI port 2-3 into a I2C port as suggesred by jwolfram
# XXX Reset.c and Main.c (that is: port these from GCL to C, but requires LCC fixed)
# XXX Need keymaps in ROM? (perhaps undocumented if not tested)
# XXX FrogStroll (not in Contrib/)
# XXX How it works memo: brief description of every software function
# XXX Adjustable return for LUP trampolines (in case SYS functions need it)
# XXX Loader: make noise when data comes in
# XXX vCPU: Multiplication (mulShift8?)
# XXX vCPU: Interrupts / Task switching (e.g for clock, LED sequencer)
# XXX Scroll out the top line of text, or generic vertical scroll SYS call
# XXX SYS function for plotting a full character in one go
# XXX Multitasking/threading/sleeping (start with date/time clock in GCL)
#-----------------------------------------------------------------------
import importlib
from sys import argv
from os import getenv
from asm import *
import gcl0x as gcl
import font_v4 as font
enableListing()
#-----------------------------------------------------------------------
#
# Start of core
#
#-----------------------------------------------------------------------
# Pre-loading the formal interface as a way to get warnings when
# accidentally redefined with a different value
loadBindings('interface.json')
loadBindings('Core/interface-dev.json') # Provisional values for DEVROM
# Gigatron clock
cpuClock = 6.250e+06
# Output pin assignment for VGA
R, G, B, hSync, vSync = 1, 4, 16, 64, 128
syncBits = hSync+vSync # Both pulses negative
# When the XOUT register is in the circuit, the rising edge triggers its update.
# The loop can therefore not be agnostic to the horizontal pulse polarity.
assert syncBits & hSync != 0
# VGA 640x480 defaults (to be adjusted below!)
vFront = 10 # Vertical front porch
vPulse = 2 # Vertical sync pulse
vBack = 33 # Vertical back porch
vgaLines = vFront + vPulse + vBack + 480
vgaClock = 25.175e+06
# Video adjustments for Gigatron
# 1. Our clock is (slightly) slower than 1/4th VGA clock. Not all monitors will
# accept the decreased frame rate, so we restore the frame rate to above
# minimum 59.94 Hz by cutting some lines from the vertical front porch.
vFrontAdjust = vgaLines - int(4 * cpuClock / vgaClock * vgaLines)
vFront -= vFrontAdjust
# 2. Extend vertical sync pulse so we can feed the game controller the same
# signal. This is needed for controllers based on the 4021 instead of 74165
vPulseExtension = max(0, 8-vPulse)
vPulse += vPulseExtension
# 3. Borrow these lines from the back porch so the refresh rate remains
# unaffected
vBack -= vPulseExtension
# Start value of vertical blank counter
videoYline0 = 1-2*(vFront+vPulse+vBack-2)
# Mismatch between video lines and sound channels
soundDiscontinuity = (vFront+vPulse+vBack) % 4
# QQVGA resolution
qqVgaWidth = 160
qqVgaHeight = 120
# Game controller bits (actual controllers in kit have negative output)
# +----------------------------------------+
# | Up B* |
# | Left Right B A* |
# | Down Select Start A |
# +----------------------------------------+ *=Auto fire
buttonRight = 1
buttonLeft = 2
buttonDown = 4
buttonUp = 8
buttonStart = 16
buttonSelect = 32
buttonB = 64
buttonA = 128
#-----------------------------------------------------------------------
#
# RAM page 0: zero-page variables
#
#-----------------------------------------------------------------------
# Memory size in pages from auto-detect
memSize = zpByte()
# The current channel number for sound generation. Advanced every scan line
# and independent of the vertical refresh to maintain constant oscillation.
channel = zpByte()
# Next sound sample being synthesized
sample = zpByte()
# To save one instruction in the critical inner loop, `sample' is always
# reset with its own address instead of, for example, the value 0. Compare:
# 1 instruction reset
# st sample,[sample]
# 2 instruction reset:
# ld 0
# st [sample]
# The difference is not audible. This is fine when the reset/address
# value is low and doesn't overflow with 4 channels added to it.
# There is an alternative, but it requires pull-down diodes on the data bus:
# st [sample],[sample]
assert 4*63 + sample < 256
# We pin this reset/address value to 3, so `sample' swings from 3 to 255
assert sample == 3
# Former bootCount and bootCheck (<= ROMv3)
zpReserved = zpByte() # Recycled and still unused. Candidate future uses:
# - Video driver high address (for alternative video modes)
# - v6502: ADH offset ("MMU")
# - v8080: ???
vCpuSelect = zpByte() # Active interpreter page
# Entropy harvested from SRAM startup and controller input
entropy = zpByte(3)
# Visible video
videoY = zpByte() # Counts up from 0 to 238 in steps of 2
# Counts up (and is odd) during vertical blank
videoModeB = zpByte() # Handler for every 2nd line (pixel burst or vCPU)
videoModeC = zpByte() # Handler for every 3rd line (pixel burst or vCPU)
videoModeD = zpByte() # Handler for every 4th line (pixel burst or vCPU)
nextVideo = zpByte() # Jump offset to scan line handler (videoA, B, C...)
videoPulse = nextVideo # Used for pulse width modulation
# Frame counter is good enough as system clock
frameCount = zpByte(1)
# Serial input (game controller)
serialRaw = zpByte() # New raw serial read
serialLast = zpByte() # Previous serial read
buttonState = zpByte() # Clearable button state
resetTimer = zpByte() # After 2 seconds of holding 'Start', do a soft reset
# XXX move to page 1 to free up space
# Extended output (blinkenlights in bit 0:3 and audio in bit 4:7). This
# value must be present in AC during a rising hSync edge. It then gets
# copied to the XOUT register by the hardware. The XOUT register is only
# accessible in this indirect manner because it isn't part of the core
# CPU architecture.
xout = zpByte()
xoutMask = zpByte() # The blinkenlights and sound on/off state
# vCPU interpreter
vTicks = zpByte() # Interpreter ticks are units of 2 clocks
vPC = zpByte(2) # Interpreter program counter, points into RAM
vAC = zpByte(2) # Interpreter accumulator, 16-bits
vLR = zpByte(2) # Return address, for returning after CALL
vSP = zpByte(1) # Stack pointer
vTmp = zpByte()
vReturn = zpByte() # Return into video loop (in page of vBlankStart)
# Scratch
frameX = zpByte() # Starting byte within page
frameY = zpByte() # Page of current pixel line (updated by videoA)
# Vertical blank (reuse some variables used in the visible part)
videoSync0 = frameX # Vertical sync type on current line (0xc0 or 0x40)
videoSync1 = frameY # Same during horizontal pulse (0x80 or 0x00)
# Versioning for GT1 compatibility
# Please refer to Docs/GT1-files.txt for interpreting this variable
romType = zpByte(1)
# The low 3 bits are repurposed to select the actively updated sound channels.
# Valid bit combinations are:
# xxxxx011 Default after reset: 4 channels (page 1,2,3,4)
# xxxxx001 2 channels at double update rate (page 1,2)
# xxxxx000 1 channel at quadruple update rate (page 1)
# The main application for this is to free up the high bytes of page 2,3,4.
channelMask = symbol('channelMask_v4')
assert romType == channelMask
# SYS function arguments and results/scratch
sysFn = zpByte(2)
sysArgs = zpByte(8)
# Play sound if non-zero, count down and stop sound when zero
soundTimer = zpByte()
# Fow now the LED state machine itself is hard-coded in the program ROM
ledTimer = zpByte() # Number of ticks until next LED change
ledState_v2 = zpByte() # Current LED state
ledTempo = zpByte() # Next value for ledTimer after LED state change
# All bytes above, except 0x80, are free for temporary/scratch/stacks etc
userVars = zpByte(0)
#-----------------------------------------------------------------------
#
# RAM page 1: video line table
#
#-----------------------------------------------------------------------
# Byte 0-239 define the video lines
videoTable = 0x0100 # Indirection table: Y[0] dX[0] ..., Y[119] dX[119]
vReset = 0x01f0
vIRQ_v5 = 0x01f6
ctrlBits = 0x01f8
videoTop_v5 = 0x01f9 # Number of skip lines
# Highest bytes are for sound channel variables
wavA = 250 # Waveform modulation with `adda'
wavX = 251 # Waveform modulation with `xora'
keyL = 252 # Frequency low 7 bits (bit7 == 0)
keyH = 253 # Frequency high 8 bits
oscL = 254 # Phase low 7 bits
oscH = 255 # Phase high 8 bits
#-----------------------------------------------------------------------
# Memory layout
#-----------------------------------------------------------------------
userCode = 0x0200 # Application vCPU code
soundTable = 0x0700 # Wave form tables (doubles as right-shift-2 table)
screenMemory = 0x0800 # Default start of screen memory: 0x0800 to 0x7fff
#-----------------------------------------------------------------------
# Application definitions
#-----------------------------------------------------------------------
maxTicks = 28//2 # Duration of vCPU's slowest virtual opcode (ticks)
minTicks = 14//2 # vcPU's fastest instruction
v6502_maxTicks = 38//2 # Max duration of v6502 processing phase (ticks)
runVcpu_overhead = 5 # Caller overhead (cycles)
vCPU_overhead = 9 # Callee overhead of jumping in and out (cycles)
v6502_overhead = 11 # Callee overhead for v6502 (cycles)
v6502_adjust = (v6502_maxTicks - maxTicks) + (v6502_overhead - vCPU_overhead)//2
assert v6502_adjust >= 0 # v6502's overhead is a bit more than vCPU
def runVcpu(n, ref=None, returnTo=None):
"""Macro to run interpreter for exactly n cycles. Returns 0 in AC.
- `n' is the number of available Gigatron cycles including overhead.
This is converted into interpreter ticks and takes into account
the vCPU calling overheads. A `nop' is inserted when necessary
for alignment between cycles and ticks.
- `returnTo' is where program flow continues after return. If not set
explicitely, it will be the first instruction behind the expansion.
- If another interpreter than vCPU is active (v6502...), that one
must adjust for the timing differences, because runVcpu wouldn't know."""
overhead = runVcpu_overhead + vCPU_overhead
if returnTo == 0x100: # Special case for videoZ
overhead -= 2
if n is None:
# (Clumsily) create a maximum time slice, corresponding to a vTicks
# value of 127 (giving 282 cycles). A higher value doesn't work because
# then SYS functions that just need 28 cycles (0 excess) won't start.
n = (127 + maxTicks) * 2 + overhead
n -= overhead
assert n > 0
if n % 2 == 1:
nop() # Tick alignment
n -= 1
assert n % 2 == 0
print('runVcpu at $%04x net cycles %3s info %s' % (pc(), n, ref))
if returnTo != 0x100:
if returnTo is None:
returnTo = pc() + 5 # Next instruction
ld(lo(returnTo)) #0
st([vReturn]) #1
n //= 2
n -= maxTicks # First instruction always runs
assert n < 128
assert n >= v6502_adjust
ld([vCpuSelect],Y) #2
jmp(Y,'ENTER') #3
ld(n) #4
assert runVcpu_overhead == 5
#-----------------------------------------------------------------------
# v6502 definitions
#-----------------------------------------------------------------------
# Registers are zero page variables
v6502_PC = vLR # Program Counter
v6502_PCL = vLR+0 # Program Counter Low
v6502_PCH = vLR+1 # Program Counter High
v6502_S = vSP # Stack Pointer (kept as "S+1")
v6502_A = vAC+0 # Accumulator
v6502_BI = vAC+1 # B Input Register (used by SBC)
v6502_ADL = sysArgs+0 # Low Address Register
v6502_ADH = sysArgs+1 # High Address Register
v6502_IR = sysArgs+2 # Instruction Register
v6502_P = sysArgs+3 # Processor Status Register (V flag in bit 7)
v6502_Qz = sysArgs+4 # Quick Status Register for Z flag
v6502_Qn = sysArgs+5 # Quick Status Register for N flag
v6502_X = sysArgs+6 # Index Register X
v6502_Y = sysArgs+7 # Index Register Y
v6502_Tmp = vTmp # Scratch (may be clobbered outside v6502)
# MOS 6502 definitions for P register
v6502_Cflag = 1 # Carry Flag (unsigned overflow)
v6502_Zflag = 2 # Zero Flag (all bits zero)
v6502_Iflag = 4 # Interrupt Enable Flag (1=Disable)
v6502_Dflag = 8 # Decimal Enable Flag (aka BCD mode, 1=Enable)
v6502_Bflag = 16 # Break (or PHP) Instruction Flag
v6502_Uflag = 32 # Unused (always 1)
v6502_Vflag = 64 # Overflow Flag (signed overflow)
v6502_Nflag = 128 # Negative Flag (bit 7 of result)
# In emulation it is much faster to keep the V flag in bit 7
# This can be corrected when importing/exporting with PHP, PLP, etc
v6502_Vemu = 128
# On overflow:
# """Overflow is set if two inputs with the same sign produce
# a result with a different sign. Otherwise it is clear."""
# Formula (without carry/borrow in!):
# (A ^ (A+B)) & (B ^ (A+B)) & 0x80
# References:
# http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
# http://6502.org/tutorials/vflag.html
# Memory layout
v6502_Stack = 0x0000 # 0x0100 is already used in the Gigatron
#v6502_NMI = 0xfffa
#v6502_RESET = 0xfffc
#v6502_IRQ = 0xfffe
#-----------------------------------------------------------------------
#
# $0000 ROM page 0: Boot
#
#-----------------------------------------------------------------------
align(0x100, size=0x80)
# Give a first sign of life that can be checked with a voltmeter
ld(0b0000) # LEDs |OOOO|
ld(syncBits^hSync,OUT) # Prepare XOUT update, hSync goes down, RGB to black
ld(syncBits,OUT) # hSync goes up, updating XOUT
# Setup I/O and RAM expander
ctrl(0b01111100) # Disable SPI slaves, enable RAM, bank 1
# ^^^^^^^^
# |||||||`-- SCLK
# ||||||`--- Not connected
# |||||`---- /SS0
# ||||`----- /SS1
# |||`------ /SS2
# ||`------- /SS3
# |`-------- B0
# `--------- B1
# bit15 --------- MOSI = 0
# Simple RAM test and size check by writing to [1<<n] and see if [0] changes or not.
ld(1) # Quick RAM test and count
label('.countMem0')
st([memSize],Y) # Store in RAM and load AC in Y
ld(255)
xora([Y,0]) # Invert value from memory
st([Y,0]) # Test RAM by writing the new value
st([0]) # Copy result in [0]
xora([Y,0]) # Read back and compare if written ok
bne(pc()) # Loop forever on RAM failure here
ld(255)
xora([Y,0]) # Invert memory value again
st([Y,0]) # To restore original value
xora([0]) # Compare with inverted copy
beq('.countMem1') # If equal, we wrapped around
ld([memSize])
bra('.countMem0') # Loop to test next address line
adda(AC) # Executes in the branch delay slot!
label('.countMem1')
# Momentarily wait to allow for debouncing of the reset switch by spinning
# roughly 2^15 times at 2 clocks per loop: 6.5ms@10MHz to [email protected]
# Real-world switches normally bounce shorter than that.
# "[...] 16 switches exhibited an average 1557 usec of bouncing, with,
# as I said, a max of 6200 usec" (From: http://www.ganssle.com/debouncing.htm)
# Relevant for the breadboard version, as the kit doesn't have a reset switch.
ld(255) # Debounce reset button
label('.debounce')
st([0])
bne(pc())
suba(1) # Branch delay slot
ld([0])
bne('.debounce')
suba(1) # Branch delay slot
# Update LEDs (memory is present and counted, reset is stable)
ld(0b0001) # LEDs |*OOO|
ld(syncBits^hSync,OUT)
ld(syncBits,OUT)
# Scan the entire RAM space to collect entropy for a random number generator.
# The 16-bit address space is scanned, even if less RAM was detected.
ld(0) # Collect entropy from RAM
st([vAC+0],X)
st([vAC+1],Y)
label('.initEnt0')
ld([entropy+0])
bpl('.initEnt1')
adda([Y,X])
xora(191)
label('.initEnt1')
st([entropy+0])
ld([entropy+1])
bpl('.initEnt2')
adda([entropy+0])
xora(193)
label('.initEnt2')
st([entropy+1])
adda([entropy+2])
st([entropy+2])
ld([vAC+0])
adda(1)
bne('.initEnt0')
st([vAC+0],X)
ld([vAC+1])
adda(1)
bne('.initEnt0')
st([vAC+1],Y)
# Update LEDs
ld(0b0011) # LEDs |**OO|
ld(syncBits^hSync,OUT)
ld(syncBits,OUT)
# vCPU reset handler
ld((vReset&255)-2) # Setup vCPU reset handler
st([vPC])
adda(2,X)
ld(vReset>>8)
st([vPC+1],Y)
st('LDI', [Y,Xpp])
st('SYS_Reset_88', [Y,Xpp])
st('STW', [Y,Xpp])
st(sysFn, [Y,Xpp])
st('SYS', [Y,Xpp]) # SYS -> SYS_Reset_88 -> SYS_Exec_88
st(256-88//2+maxTicks,[Y,Xpp])
st(0, [Y,Xpp]) # vIRQ_v5: Disable interrupts
st(0, [Y,Xpp]) # vIRQ_v5
st(0b11111100, [Y,Xpp]) # Control register
st(0, [Y,Xpp]) # videoTop
ld(hi('ENTER')) # Active interpreter (vCPU,v6502) = vCPU
st([vCpuSelect])
ld(255) # Setup serial input
st([frameCount])
st([serialRaw])
st([serialLast])
st([buttonState])
st([resetTimer]) # resetTimer<0 when entering Main.gcl
ld(0b0111) # LEDs |***O|
ld(syncBits^hSync,OUT)
ld(syncBits,OUT)
ld(0)
st([0]) # Carry lookup ([0x80] in 1st line of vBlank)
st([channel])
st([soundTimer])
ld(0b1111) # LEDs |****|
ld(syncBits^hSync,OUT)
ld(syncBits,OUT)
st([xout]) # Setup for control by video loop
st([xoutMask])
ld(hi('startVideo'),Y) # Enter video loop at vertical blank
jmp(Y,'startVideo')
st([ledState_v2]) # Setting to 1..126 means "stopped"
#-----------------------------------------------------------------------
# Extension SYS_Reset_88: Soft reset
#-----------------------------------------------------------------------
# SYS_Reset_88 initiates an immediate Gigatron reset from within the vCPU.
# The reset sequence itself is mostly implemented in GCL by Reset.gcl,
# which must first be loaded into RAM. But as that takes more than 1 scanline,
# some vCPU bootstrapping code gets loaded with SYS_Exec_88.
# !!! This function was REMOVED from interface.json
# !!! Better use vReset as generic entry point for soft reset
# ROM type (see also Docs/GT1-files.txt)
romTypeValue = symbol('romTypeValue_ROMv5')
label('SYS_Reset_88')
assert pc()>>8 == 0
assert (romTypeValue & 7) == 0
ld(romTypeValue) #15 Set ROM type/version and clear channel mask
st([romType]) #16
ld(0) #17
st([vSP]) #18 vSP
ld(hi('videoTop_v5'),Y) #19
st([Y,lo('videoTop_v5')]) #20 Show all 120 pixel lines
st([Y,vIRQ_v5]) #21 Disable vIRQ dispatch
st([Y,vIRQ_v5+1]) #22
st([soundTimer]) #23 soundTimer
assert userCode&255 == 0
st([vLR]) #24 vLR
ld(userCode>>8) #25
st([vLR+1]) #26
ld('nopixels') #27 Video mode 3 (fast)
st([videoModeB]) #28
st([videoModeC]) #29
st([videoModeD]) #30
ld('SYS_Exec_88') #31 SYS_Exec_88
st([sysFn]) #32 High byte (remains) 0
ld('Reset') #33 Reset.gt1 from EPROM
st([sysArgs+0]) #34
ld(hi('Reset')) #35
st([sysArgs+1]) #36
ld([vPC]) #37 Force second SYS call
suba(2) #38
st([vPC]) #39
# Return to interpreter
ld(hi('NEXTY'),Y) #40
jmp(Y,'NEXTY') #41
ld(-44/2) #42
#-----------------------------------------------------------------------
# Placeholders for future SYS functions. This works as a kind of jump
# table. The indirection allows SYS implementations to be moved around
# between ROM versions, at the expense of 2 clock cycles (or 1). When
# the function is not present it just acts as a NOP. Of course, when a
# SYS function must be patched or extended it needs to have budget for
# that in its declared maximum cycle count.
#
# Technically the same goal can be achieved by starting each function
# with 2 nop's, or by overdeclaring their duration in the first place
# (a bit is still wise to do). But this can result in fragmentation
# of future ROM images. The indirection avoids that.
#
# An added advantage of having these in ROM page 0 is that it saves one
# byte when setting sysFn: LDI+STW (4 bytes) instead of LDWI+STW (5 bytes)
#-----------------------------------------------------------------------
align(0x80, size=0x80)
assert pc() == 0x80
ld(hi('REENTER'),Y) #15 slot 0x80
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x83
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x86
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x89
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x8c
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x8f
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x92
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x95
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x98
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x9b
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0x9e
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xa1
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xa4
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xa7
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xaa
jmp(Y,'REENTER') #16
ld(-20/2) #17
#-----------------------------------------------------------------------
# Extension SYS_Exec_88: Load code from ROM into memory and execute it
#-----------------------------------------------------------------------
#
# This loads the vCPU code with consideration of the current vSP
# Used during reset, but also for switching between applications or for
# loading data from ROM from within an application (overlays).
#
# ROM stream format is [<addrH> <addrL> <n&255> n*<byte>]* 0
# on top of lookup tables.
#
# Variables:
# sysArgs[0:1] ROM pointer (in)
# sysArgs[2:3] RAM pointer (changed)
# sysArgs[4] State counter (changed)
# vLR vCPU continues here (in)
label('SYS_Exec_88')
ld(hi('sys_Exec'),Y) #15
jmp(Y,'sys_Exec') #16
ld(0) #17 Address of loader on zero page
#-----------------------------------------------------------------------
# More placeholders for future SYS functions
#-----------------------------------------------------------------------
ld(hi('REENTER'),Y) #15 slot 0xb0
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xb3
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xb6
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xb9
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xbc
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xbf
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xc2
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xc5
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xc8
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xcb
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xce
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xd1
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xd4
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xd7
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xda
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xdd
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xe0
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xe3
jmp(Y,'REENTER') #16
ld(-20/2) #17
ld(hi('REENTER'),Y) #15 slot 0xe6
jmp(Y,'REENTER') #16
ld(-20/2) #17
#-----------------------------------------------------------------------
# Extension SYS_StoreBytes_DEVROM_XXX
#-----------------------------------------------------------------------
ld(hi('REENTER'),Y) #15 slot 0xe9
jmp(Y,'REENTER') #16
ld(-20/2) #17
#-----------------------------------------------------------------------
# Extension SYS_LoadBytes_DEVROM_XXX
#-----------------------------------------------------------------------
# Load object variables into zero-page
# XXX Unfinished
#
# Variables
# vLR Pointer to size byte + object variables
# $30...$30+n-1 Target location
label('SYS_LoadBytes_DEVROM_XXX')
ld(hi('sys_LoadBytes'),Y) #15
jmp(Y,'sys_LoadBytes') #16
ld([vLR+1],Y) #17
#-----------------------------------------------------------------------
# Extension SYS_ReadRomDir_v5_80
#-----------------------------------------------------------------------
# Get next entry from ROM file system. Use vAC=0 to get the first entry.
# Variables:
# vAC Start address of current entry (inout)
# sysArgs[0:7] File name, padded with zeroes (out)
label('SYS_ReadRomDir_v5_80')
ld(hi('sys_ReadRomDir'),Y) #15
jmp(Y,'sys_ReadRomDir') #16
ld([vAC+1]) #17
fillers(until=symbol('SYS_Out_22') & 255)
#-----------------------------------------------------------------------
# Extension SYS_Out_22
#-----------------------------------------------------------------------
# Send byte to output port
#
# Variables:
# vAC
label('SYS_Out_22')
ld([sysArgs+0],OUT) #15
nop() #16
ld(hi('REENTER'),Y) #17
jmp(Y,'REENTER') #18
ld(-22/2) #19
#-----------------------------------------------------------------------
# Extension SYS_In_24
#-----------------------------------------------------------------------
# Read a byte from the input port
#
# Variables:
# vAC
label('SYS_In_24')
st(IN, [vAC]) #15
ld(0) #16
st([vAC+1]) #17
nop() #18
ld(hi('REENTER'),Y) #19
jmp(Y,'REENTER') #20
ld(-24/2) #21
assert pc()&255 == 0
#-----------------------------------------------------------------------
#
# $0100 ROM page 1: Video loop vertical blank
#
#-----------------------------------------------------------------------
align(0x100, size=0x100)
# Video off mode (also no sound, serial, timer, blinkenlights, ...).
# For benchmarking purposes. This still has the overhead for the vTicks
# administration, time slice granularity etc.
label('videoZ')
videoZ = pc()
runVcpu(None, '---- novideo', returnTo=videoZ)
label('startVideo') # (Re)start of video signal from idle state
ld(syncBits)
# Start of vertical blank interval
label('vBlankStart')
st([videoSync0]) #32 Start of vertical blank interval
ld(syncBits^hSync) #33
st([videoSync1]) #34
# Reset line counter before vCPU can see it
ld(videoYline0) #35
st([videoY]) #36
# Update frame count and [0x80] (4 cycles)
ld(1) #37 Reinitialize carry lookup, for robustness
st([0x80]) #38
adda([frameCount]) #39 Frame counter
st([frameCount]) #40
# Mix entropy (11 cycles)
xora([entropy+1]) #41 Mix entropy
xora([serialRaw]) #42 Mix in serial input
adda([entropy+0]) #43
st([entropy+0]) #44
adda([entropy+2]) #45 Some hidden state
st([entropy+2]) #46
bmi(pc()+3) #47
bra(pc()+3) #48
xora(64+16+2+1) #49
xora(64+32+8+4) #49(!)
adda([entropy+1]) #50
st([entropy+1]) #51
# LED sequencer (18 cycles)
ld([ledTimer]) #52 Blinkenlight sequencer
beq(pc()+3) #53
bra(pc()+3) #54
suba(1) #55
ld([ledTempo]) #55(!)
st([ledTimer]) #56
beq(pc()+3) #57
bra(pc()+3) #58
ld(0) #59 Don't advance state
ld(1) #59(!) Advance state when timer passes through 0