-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcp1.asm
executable file
·1775 lines (1633 loc) · 75.8 KB
/
cp1.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
; ROM Listing
; ===========
;
; This is the reverse engineered and fully commented ROM listing of the ROM
; contents of the Intel 8049 used in the Kosmos CP1 experimental computer.
;
; To recreate the ROM content, you can use this 8048 cross assembler:
; https://sourceforge.net/projects/asm48/
;
; Code (C) 1983, Franckh'sche Verlagshandlung, W. Keller & Co., Stuttgart, Germany
; Comments (C) 2017, Andreas Signer <[email protected]>
;
; 8049 RAM Memory Map
; -------------------
; 0x00 - 0x07: Register Bank 0 (R0 - R7)
; 0x08 - 0x17: Stack (8 levels)
; 0x18 - 0x1f: Register Bank 1 (R0 - R7)
;
; 0x20 - 0x25: 6 Digit "Video RAM"
; 0x26: ?
; 0x27 - 0x2b: decoded entered digits. most significant digit at 0x27
;
; 0x30 - 0x35: last interrupt's key presses per line
; 0x36: Accu MSB
; 0x37: Accu LSB
; 0x38: PC
; 0x39: Last byte read from external RAM
; 0x3a: status register
; bit 7: ?
; bit 6: IO on 8155s enabled
; bit 5: result of last comparison
; bit 4: accessed RAM extension
; bit 3: Memory full
; bit 2: Address overflow
; bit 1: STEP pressed
; bit 0: STP pressed
; 0x3b: memory size
; 0x3c - 0x3e: buffer for atoi
; 0x3f: Last byte written to port 1
; 0x40: Last byte written to port 2
; 0x41: Last byte written to port 4
; 0x42: Last byte written to port 5
; 0x43: Reaction test delay in secs
; 0x44: last key pressed
;
; Global Register usage:
; ----------------------
; R0: Address pointer
; R1: Address pointer
; R2: -
; R3: -
; R4: -
; R5: -
; R6: Number of digits entered
; R7: current input/output position
; Listing
; -------
.equ VRAM, $20
.equ KBUF, $27
.equ ACCU, $36
.equ ACCU_LSB, $37
.equ PC, $38
.equ STATUS, $3a
.equ MEM_SIZE, $3b
.equ LAST_BYTE_P1, $3f
.equ LAST_BYTE_P2, $40
.equ LAST_BYTE_P4, $41
.equ LAST_BYTE_P5, $42
.org $0
JMP init ; Reset entry point: Execution starts at 0.
NOP
RETR ; Interrupt entry point
NOP
NOP
NOP
JMP timer ; Timer/Counter entry point
NOP
; JMPP Jump-Table for key presses
keypress_jmpp_table:
.db <wait_key ; ??? this is pointing to the wait_key function
.db <digit_handler ; key '0'
.db <digit_handler ; key '1'
.db <digit_handler ; key '2'
.db <digit_handler ; key '3'
.db <digit_handler ; key '4'
.db <digit_handler ; key '5'
.db <digit_handler ; key '6'
.db <digit_handler ; key '7'
.db <digit_handler ; key '8'
.db <digit_handler ; key '9'
.db <out_handler_trampoline ; key 'OUT'
.db <inp_handler ; key 'INP'
.db <cal_handler_trampoline ; key 'CAL'
.db <step_handler_trampoline ; key 'STEP'
.db <wait_key ; key 'STP'
.db <run_handler_trampoline ; key 'RUN'
.db <cas_handler_trampoline ; key 'CAS'
.db <clr_handler ; key 'CLR'
.db <pc_handler ; key 'PC'
.db <acc_handler ; key 'ACC'
run_handler_trampoline: JMP run_handler
out_handler_trampoline: JMP out_handler
cal_handler_trampoline: JMP cal_handler
step_handler_trampoline: JMP step_handler
cas_handler_trampoline: JMP cas_handler
; Initialization code
; -------------------
init:
MOV R0, #$1e
MOV @R0, #$00 ; Set R6' to 0
MOV R0, #$20 ; Clear 0x28 ...
MOV R3, #$28 ;
init1: MOV @R0, #$00 ;
INC R0 ;
DJNZ R3, init1 ; ... bytes of RAM
MOV R6, #$00
MOV R7, #$00
MOV R0, #$1a
MOV @R0, #$01 ; Set R1' to 1
CALL print_pc
ANL P2, #$cf ; P2 &= 1100 1111 --> 8155 /CE == 0, /CE == 0
ANL P2, #$bf ; P2 &= 1011 1111 --> 8155 /Reset == 0
ANL P2, #$ef ; P2 &= 1110 1111 --> 8155 /CE == 0: Enable "internal" 8155
ORL P2, #$20 ; P2 |= 0010 0000 --> /CE == 1: Disable CP3 8155
MOV R0, #$00
MOV A, #$0f
MOVX @R0, A ; Write 0000 1111 to command register: Port A and B are OUTPUT, Port C is ALT2
MOV R0, #$02
MOV A, #$ff
MOVX @R0, A ; Write 1111 1111 to Port B
ANL P2, #$df ; P2 &= 1101 1111 --> /CE == 0: Enable CP3 8155
ORL P2, #$10 ; P2 |= 0001 0000 --> 8155 /CE == 1: Disable "internal" 8155
MOV R0, #$00
MOV A, #$0d
MOVX @R0, A ; Write 0000 1101 to command register: Port A is OUTPUT, Port B is INPUT, Port C is ALT2
MOV R0, #$01
MOV A, #$ff
MOVX @R0, A ; Write 1111 1111 to Port A
MOV R0, #$03
MOVX @R0, A ; Write 1111 1111 to Port A
; Test whether extension is available:
ANL P2, #$5f ; P2 &= 0101 1111 --> /CE == 0, IO == 0
ORL P2, #$10 ; P2 |= 0001 0000 --> 8155 /CE == 1
MOV R0, #$13
MOV A, #$5a
MOVX @R0, A ; Store $5a at $13
CLR A
MOVX A, @R0 ; Read again from $13
XRL A, #$5a ; Test if it is what we wrote?
MOV R0, #MEM_SIZE ; Load address of "memory size"
JZ cp3_present
MOV @R0, #$7f ; 127 words of memory
init_cont: MOV R3, #$04 ; 4 ports in total
MOV R0, #LAST_BYTE_P1 ; load address of "last byte written to port 1"
MOV A, #$ff ; set all bits
portloop: MOV @R0, A ; store it in "last byte written to port 1"
INC R0 ; move to next port byte
DJNZ R3, portloop ; and continue while there are still ports left.
ANL P2, #$4f ; P2 &= 0100 1111 --> 8155 /CE == 0, /CE == 0, IO == 0: Enable both 8155 in Memory mode
CALL clear_ram
EN TCNTI ; enable Timer/Counter interrupts
STRT T ; and start the timer
wait_key: MOV R0, #$1e ; Wait for key press: 0x1e is R6', used in interrupt
MOV A, @R0 ; check for key
JZ wait_key ; no key pressed, continue waiting
MOV R1, #$44 ;
MOV @R1, A ; Store pressed key in 0x44
MOV A, <keypress_jmpp_table ; Load jump table base
ADD A, @R0 ; Add the currently pressed key
MOV @R0, #$00 ; clear R6'
JMPP @A ; jump to key handler
cp3_present: MOV @R0, #$ff ; 255 words of memory
JMP init_cont
pc_handler: MOV A, R6 ; Load # of digits entered
JZ zero_digits ; jump if zero
XRL A, #$03 ; is it 3 digits?
JNZ error_f001 ; error if not
MOV R0, #KBUF ; Load address of 1st decoded digit
CALL convert_and_check_address
MOV R0, #STATUS ; Load status byte...
MOV A, @R0 ; ... to A
JB2 clear_addr_ovfl_error_f004_trampoline
MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$73 ; ... to 'P'
MOV R0, #KBUF ; address of 1st decoded digit
MOV R2, #$02 ; 3 digits to convert
CALL digits_to_number
MOV R0, #PC ; load address of PC
MOV @R0, A ; store entered number in PC
MOV R6, #$00 ; reset # of entered digits
JMP wait_key
clear_addr_ovfl_error_f004_trampoline:
JMP clear_addr_ovfl_error_f004
zero_digits: CALL print_pc
JMP wait_key
acc_handler: CALL print_accu
JMP wait_key
clr_handler: CALL clear_display ; Clear display
MOV R6, #$00 ; Clear entered digits
JMP wait_key
error_f001: MOV R4, #$01
JMP show_error ; F-001
first_digit: CALL clear_display
JMP digit_handler_cont
digit_handler: MOV A, R6 ; load # of inputted keys
JZ first_digit ; if it's the first key, clear screen first
CLR C ; clear carry
ADD A, #$fb ; add 251 (== -5 % 256) to check if we entered more than 5 digits
JC error_f001 ; jump if >5 digits.
digit_handler_cont:
MOV A, R6 ; load # of inputted keys
ADD A, #KBUF ; add base address of decoded keys
MOV R0, A ; and load this address
MOV R1, #$44 ; load address of "last key pressed"
MOV A, @R1 ; load last key pressed
DEC A ; subtract 1 ('0' is keycode 1, '1' is keycode 2, ... see jump table)
MOV @R0, A ; and store it in the number area
MOV R2, A ; move it to R2 ...
CALL append_digit ; ... and print it out.
JMP wait_key ;
clear_addr_ovfl_error_f004_trampoline2:
JMP clear_addr_ovfl_error_f004
inp_not3_trampoline:
JMP inp_not3
inp_handler: MOV A, R6 ; Load # of entered digits
XRL A, #$03 ; is it 3?
JNZ inp_not3_trampoline ; Jump if not.
MOV A, #$f7 ; 1111 0111: "Memory full" bit ...
CALL clear_status_bits ; ... is cleared from status
MOV R0, #KBUF ; load address of entered digits
CALL convert_and_check_address
MOV R0, #STATUS ; load ...
MOV A, @R0 ; status byte
JB2 clear_addr_ovfl_error_f004_trampoline2
MOV R0, #KBUF ; address of 1st decoded digit
MOV R2, #$02 ; 3 digits to convert
CALL digits_to_number ; convert them
MOV R7, A ; set current I/O pos to entered addres.
inp_done: MOV R6, #$00 ; Reset # of entered digits
MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$79 ; ... to 'E'
JMP wait_key
inp_not3: MOV A, R6 ; Load # of entered digits
XRL A, #$05 ; is it 5?
JNZ error_f001_trampoline ; Jump if not.
MOV R0, #STATUS ; load status byte...
MOV A, @R0 ; ... to A
JB3 error_f004 ; bail out if memory was full
MOV R0, #KBUF ; load address of entered digits
MOV R2, #$01 ; 2 digits to convert
CALL digits_to_number
CLR C ; clear carry
MOV R1, A ; save number in R1
ADD A, #$e7 ; add (256 - 25) to check for max opcode
JC error_f004 ; if carry is set, value was > 25 -> invalid opcode
MOV R0, #$29 ; load address if 3rd entered digit
MOV R2, #$02 ; 3 digits to convert
CALL digits_to_number
JF0 error_f004 ; report F-004 if there was an overflow
MOV R4, A ; Store operand in R4
MOV A, R1 ; Move opcode ...
MOV R2, A ; ... to R2
MOV A, R7 ; move R7 (current I/O pointer) to A
CALL compute_effective_address
MOV A, R2 ; Store opcode in...
MOVX @R1, A ; ... current pos' MSB
INC R1 ; move to LSB
MOV A, R4 ; Store operand in...
MOVX @R1, A ; ... current pos' LSB
MOV R0, #MEM_SIZE ; Load mem-size address
MOV A, R7 ; compare current i/o pos ...
XRL A, @R0 ; ... with mem-size
JZ inp_mark_mem_full ; if it's the same, mark "memory full"
INC R7 ; otherwise, move to the next position
JMP inp_done
inp_mark_mem_full:
MOV A, #$08 ; 0000 1000: "memory full" bit
CALL set_status_bits
JMP inp_done
error_f001_trampoline:
JMP error_f001
clear_addr_ovfl_error_f004:
MOV A, #$fb ; 1111 1011
CALL clear_status_bits ; clear "address overflow" bit
error_f004: MOV R4, #$04
JMP show_error ; F-004
; Convert 3 single digits to a number
; -----------------------------------
; Converts single digits to a number, e.g. from 1,3,7 to 137
; Inputs:
; - R0: Address of most significant digit
; - R2: Number of digits - 1, must be <= 2
; Outputs
; - A: the resulting number
; - F0: Set if there was an overflow
digits_to_number:
MOV A, R1 ; save R1 in...
MOV R3, A ; ... R3
MOV R1, #$3c ; Destination Adresse: scratch area
MOV R4, #$03 ; Size: 3
MOV A, @R0 ; Copy from (R0)
MOV @R1, A ; to (R1)
INC R0 ;
INC R1 ;
DJNZ R4, $0154 ; Continue while not 3 bytes copied
MOV A, R3 ;
MOV R1, A ; Restore R1
MOV R0, #$3c ; Load address of first digit
MOV A, @R0 ; Load digit
CLR C ; Clear carry...
CLR F0 ; ... and F0. No overflow yet
RLC A ; * 2
JC dtn_overflow
RLC A ; * 2
JC dtn_overflow
ADD A, @R0 ; + digit
JC dtn_overflow
RLC A ; * 2. A is now: (@R0*2*2+@R0)*2 == 10*@R0
JC dtn_overflow
INC R0 ; move to next digit
ADD A, @R0 ; add next digit to A
JC dtn_overflow
MOV @R0, A ; current digit = 10 * prev digit + current digit
DJNZ R2, $0161 ; continue loop while we have digits left.
MOV A, @R0 ; Load final result to A
RET ; Return
dtn_overflow: CPL F0 ; Set F0 to mark overflow
CLR C ; Clear carry
RET ; Return
; Clear display
; --------------
;
clear_display: MOV R2, #$06 ; 6 digits to clear
MOV R0, #VRAM ; load address of "Video RAM"
cd_loop: MOV @R0, #$00 ; clear segments of that Video RAM address
INC R0 ; move to next address
DJNZ R2, cd_loop ; continue while there's more to clear
RET
; Print one byte
; --------------
; Print a value from internal RAM.
;
; Input:
; - R0: Address of value to be printed
; - R6: Offset into "Video RAM"
; - F0: If set, don't print hundreds
;
; Output:
; - R6: Set to Zero
print_value:
MOV A, @R0 ; Load value to print
CALL itoa
JF0 no_hundreds
CALL print_digit
print_tens_and_ones:
MOV A, R4 ; Load 10s
CALL print_digit ; Print them
MOV A, R2 ; Load 1s
CALL print_digit ; Print
MOV R6, #$00 ; Set digits entered to 0 again. ??????
RET
no_hundreds:
CLR F0
JMP print_tens_and_ones
; Fetch and print external RAM
; ----------------------------
; Read 2 bytes from external RAM and print it.
;
; Input:
; - R1: External address
;
; Output:
; - $39: Content of last byte read
fetch_and_print_ram:
MOVX A, @R1 ; Read from external RAM
MOV R0, #$39 ; Address to store value in is 0x39
MOV @R0, A ; Store value
MOV R6, #$00 ; print "high byte": start at 0 in display RAM
CLR F0 ; and set...
CPL F0 ; ... F0: "no hundreds" for print
CALL print_value ; print it
INC R1 ; read next address
MOVX A, @R1 ; from external RAM
MOV R0, #$39 ; store it...
MOV @R0, A ; ... in 0x39
MOV R6, #$02 ; print "low byte": start at 2
CALL print_value ; print value
RET
; Append digit to Video RAM
; -------------------------
; Shift number in Video RAM to the left and append 1 digit.
;
; Input:
; - R2: Digit to append
; - R6: Number of digits shown (not used)
;
; Output:
; - R6: Number of digits shown
append_digit:
MOV R0, #$21 ; Target address: 2nd digit of Video RAM
MOV R1, #$22 ; Source address: 3nd digit of Video RAM
MOV R3, #$04 ; 4 digits to copy
MOV A, @R1 ; move from source
MOV @R0, A ; to target
INC R0 ; increment target pointer
INC R1 ; increment source pointer
DJNZ R3, $01b0 ; continue while there are digits left
MOV A, #$00 ; Move R2...
ADD A, R2 ; ... to A
MOVP3 A, @A ; Load segments for that digit ...
MOV R0, #$25 ; ... and store it...
MOV @R0, A ; ... in the right-most digit
INC R6 ; increment digits entered.
RET
cas_stop_pressed:
CLR F0
CALL clear_access_ram_extension_status_bit
JMP save_done
cas_handler: CALL clear_display
CALL clear_access_ram_extension_status_bit
MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$23 ; .. to 'CAS' symbol
ORL P1, #$c0 ; P1 |= 1100 0000 --> CassData == 1, CassWR == 1
MOV R0, #STATUS
MOV R4, #$3c ; Loop max 60 times
MOV R3, #$fa ; Delay for...
CALL delay_millis ; ... 250 millis
MOV A, @R0 ; check content of #STATUS
JB0 cas_stop_pressed ; if bit 0 is set
DJNZ R4, $01d2 ; wait for max. 15 secs
CLR A ; Address 0
CALL compute_effective_address; Compute effective address to select RAM chip (internal 8155)
CALL save_memory
JF0 cas_stop_pressed
MOV R0, #MEM_SIZE ; Load memory size...
MOV A, @R0 ; ... to A
JB7 cas_block_2 ; if >128, save 2nd half as well
JMP save_done ;
cas_block_2: MOV A, #$ff ; Load address > 128
CALL compute_effective_address; Compute effective address to select RAM chip (CP3's 8155)
CALL save_memory
JF0 cas_stop_pressed_trampoline
save_done: ANL P1, #$7f
CALL clear_display
MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$63 ; ... to 'ᵒ'
MOV R6, #$00
JMP wait_key
cas_stop_pressed_trampoline:
JMP cas_stop_pressed
out_handler:
MOV A, #$f7 ; 1111 0111 -> A
CALL clear_status_bits
MOV A, R6 ; Load number of digits that were entered
JZ no_digits ; jump if zero
XRL A, #$03 ; is it 3?
JNZ not_3_digits ; go to not_3_digits if not
MOV R0, #KBUF
CALL convert_and_check_address
MOV R0, #STATUS ; load status byte ...
MOV A, @R0 ; ... to A
JB2 $023a
MOV R0, #KBUF ; address of 1st decoded digit
MOV R2, #$02 ; 3 digits to convert
CALL digits_to_number
MOV R7, A ; Save A in R7 (last in/out position)
out_print: CALL clear_display
MOV A, R7 ; Restore A
CALL compute_effective_address
CALL fetch_and_print_ram
print_C: MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$39 ; ... to 'C'
JMP wait_key
no_digits: CLR C
MOV A, R7 ; Move current in/out position to A
ADD A, #$01 ; increment
MOV R7, A ; and store it in in/out position
JC out_overflow
MOV R0, #MEM_SIZE ; load memory size...
MOV A, @R0 ; ... into A
JB7 out_print ; CP3 available, not need to check range
MOV A, R7 ; check if in/out position...
JB7 out_overflow_low ; ... is >= 128
JMP out_print ; otherwise, print it
out_overflow: MOV R7, #$ff ; set in/out pos to 255
out_f004: JMP clear_addr_ovfl_error_f004
out_overflow_low:
MOV R7, #$7f ; set in/out pos to 127
JMP out_f004 ; and report f-004
not_3_digits: MOV A, R6 ; load number of digits entered
XRL A, #$01 ; is it 1?
JZ one_digit ; yes
one_digit_f001: JMP error_f001 ; otherwise, it's an error
one_digit: MOV R0, #KBUF ; Load address of decoded keys
MOV A, @R0 ; load first digit
XRL A, #$09 ; is it 9?
JNZ one_digit_f001 ; no, error F-001
CALL clear_display
MOV R0, #VRAM ; Set left-most digit...
MOV @R0, #$39 ; ... to 'C'
MOV R0, #$07 ; Load address 7 (== R7)
MOV R6, #$02 ; 3 digits to print
CALL print_value
JMP wait_key
; Timer/Counter interrupt
; -----------------------
; - R5: Mask used for keyboard reading and display addressing
; - R3: Internal scratch register: Loop vars, computations
timer: SEL RB1 ; Switch to register bank 1
XCH A, R7 ; Restore A from last interrupt
MOV A, #$e0 ; Reset timer...
MOV T, A ; ... to 0xe0 (== 224) --> 2560 micros per timer interrupt
DJNZ R2, $026b ;
MOV R1, #$25
MOV R5, #$fe ; Init: current row selection mask
MOV R2, #$06 ; Init: 6 rows
MOV R4, #$35
ORL P2, #$a0 ; P2 |= 1010 0000 --> IO == 1, /CE == 1
ANL P2, #$ef ; P2 &= 1110 1111 --> 8155 /CE == 0
CLR A ;
MOV R0, #$01 ; Address Port A
MOVX @R0, A ; Write 0 -> Port A
MOV A, R5
MOV R0, #$03 ; Address Port C
MOVX @R0, A ; Write row selection -> Port C
MOV A, @R1
MOV R0, #$01
MOVX @R0, A ; (0x25) -> Port A; Write one character to display
ORL P2, #$0f ; P2 |= 0000 1111: Prepare to read from bit 0 - 4
MOVD A, P4 ; ????? write to lower nibble of port 2, then read from it? why not just read from it?
CLR C
MOV R3, #$04 ; Test (keypad) bits read: 4 bits in total
RRC A ; shift lowest bit to carry
JC keymask_bit_set ; jump if set
DJNZ R3, $0281 ; not set, carry on with loop.
JMP no_key_pressed ; no key pressed
keymask_bit_set:
MOV A, R2
DEC A
RL A
RL A
INC A ; A = 4 * (R2 - 1) + 1; R2 is '6 - physical row', i.e. R2 is 2..6
XCH A, R3 ; R3 = 4 * (R2 - 1) + 1; A = bit_num
CPL A ; Compute two's complement...
INC A ; ... of A --> A == -bit_num
ADD A, R3 ; A = 4 * (row - 1) + 1 - bit_num
MOV R6, A ; Store A in R6 (== last key press)
JB7 no_key_pressed ; Bit 7 Set? -> 2bc
MOV A, R4
MOV R0, A
MOV A, R6
XRL A, @R0 ; (R4) == R6 (same key pressed as during last interrupt?)
JZ key_still_pressed
MOV A, R6
MOV @R0, A ; remember key press for next interrupt
MOV A, R6 ; compare pressed key...
XRL A, #$0f ; ... with "STP".
JZ timer_stop_pressed; ; jump if equal
MOV A, R6 ; compare pressed key...
XRL A, #$0e ; ... with "STEP".
JZ step_pressed ; jump if equal
move_to_next_row:
MOV A, R5 ; Load row selection mask
RL A ; rotate to next position
MOV R5, A ; Update mask
DEC R1 ; Move to previous Video RAM pos
DEC R4 ; Move to previous 'key pressed state' position
MOV R0, #STATUS ; Load status byte...
MOV A, @R0 ; ... to A
JB4 enable_ram_extension
JB6 enable_io
ANL P2, #$7f ; otherwise, disable IO: P2 &= 0111 1111 --> IO == 0
end_of_intr: XCH A, R7 ; save A for next interrupt
SEL RB0 ; switch back to register bank 0
EN TCNTI ; enable interrupts again
RETR ; and continue execution
key_still_pressed:
MOV R6, #$00 ; don't register the key press, it wasn't released in between
JMP move_to_next_row
no_key_pressed: MOV A, R4
MOV R0, A
MOV @R0, #$00 ; clear "last keypress state"
JMP move_to_next_row
timer_stop_pressed:
MOV A, #$01 ; mask 0000 0001: 'STP pressed'
CALL set_status_bits
JMP move_to_next_row
step_pressed: MOV A, #$02 ; mask 0000 0010 'STEP pressed'
CALL set_status_bits
JMP move_to_next_row
enable_ram_extension:
ANL P2, #$df ; P2 &= 1101 1111 --> /CE == 0
ORL P2, #$10 ; P2 |= 0001 0000 --> 8155 /CE == 1
JMP $02b0
enable_io: ORL P2, #$80 ; P2 |= 1000 0000 --> IO == 1
JMP $02b4
RET
; Convert Integer value to single digits ("itoa")
; -----------------------------------------------
; Converts the value in A to 3 decimal digits.
;
; Input:
; - A: Value to convert
;
; Output:
; - A: hundreds
; - R2: ones
; - R4: tens
itoa:
CLR C
MOV R4, #$00
ADD A, #$f6 ; Subtract 10 (== Add 246 mod 256)
JNC $02e4 ; Carry not set -> A was < 10
CLR C ; reset carry
INC R4 ; Count tens
JMP $02dc ; check again.
ADD A, #$0a ; undo last subtract
MOV R2, A ; Store lowest digit (ones) in R2
CLR C
MOV A, R4 ; Bring tens to Accumulator
MOV R4, #$00 ; Set hundreds to 0
ADD A, #$f6 ; same as above, but now effectively count hundreds
JNC $02f3 ; Carry not set -> A was < 10
CLR C ; reset carry
INC R4 ; count hundreds
JMP $02eb ; check again
ADD A, #$0a ; undo last subtract
XCH A, R4 ; Store hundreds in A, tens in R4
CLR C
RET
; DEAD CODE FROM HERE ON?
NOP
ORL BUS, #$c4
NOP
NOP
NOP
NOP
INC R6
.org $300
; Segment codes for digits 0 .. 9
.db $3f ; Digit '0'
.db $06 ; Digit '1'
.db $5b ; Digit '2'
.db $4f ; Digit '3'
.db $66 ; Digit '4'
.db $6d ; Digit '5'
.db $7d ; Digit '6'
.db $27 ; Digit '7'
.db $7f ; Digit '8'
.db $6f ; Digit '9'
; Compute the effective address of a VM byte
; ------------------------------------------
; Compute the effective address, also selecting
; the correct 8155 chip
; Input:
; - A: VM address (0 .. 255)
; Output:
; - R1: 8155 address of the first byte
compute_effective_address:
JB7 upper_half ; Jump if addr >= 128
MOV R3, A
MOV A, #$ef ; mask 1110 1111 ; 'access RAM extension'
CALL clear_status_bits
ORL P2, #$20 ; Disable extension RAM: P2 |= 0010 0000 --> /CE == 1
ANL P2, #$ef ; Enable default RAM: P2 &= 1110 1111 --> 8155 /CE == 0
MOV A, R3
cea_end: RL A ; Effective Address = 2 * A
MOV R1, A
RET
upper_half: ADD A, #$80 ; bring address to lower half
CLR C ; clear carry
MOV R3, A ; store address in R3
MOV A, #$10 ; mask 0001 0000: 'access RAM extension'
CALL set_status_bits
ORL P2, #$10 ; P2 |= 0001 0000 --> 8155 /CE == 1
ANL P2, #$df ; P2 &= 1101 1111 --> /CE == 0
MOV A, R3
JMP cea_end
; Clear 256 bytes of external RAM (both main and CP3)
; ----------------------------------------------------
clear_ram: CLR A ; clear A
MOV R1, A ; Use it as counter
cr_l: MOVX @R1, A ; clear memory
DJNZ R1, cr_l ; continue until we're done.
RET
; Set bits in the status byte
;-----------------------------
; Input:
; - A: mask of bits to set in the status register
set_status_bits:
MOV R0, #STATUS
ORL A, @R0
MOV @R0, A
RET
; Clear bits in the status byte
;------------------------------
; Input:
; - A: mask of bits to keep in the status register
clear_status_bits:
MOV R0, #STATUS
ANL A, @R0
MOV @R0, A
RET
; Convert and check address
; -------------------------
; Converts the 3 entered digits into a single address
; and checks that the address is in range.
; Inputs:
; - R0: Address of most significant digit
; Outputs
; - A: the resulting number
convert_and_check_address:
MOV R2, #$02 ; 3 digits to convert
CALL digits_to_number ; convert to number
JF0 caca_ovfl ; overflow?
JB7 caca_check_cp3 ; > 127? check memory size
caca_done: RET
caca_check_cp3: MOV R0, #MEM_SIZE ; load address of memory size
MOV A, @R0 ; load memory size
JB7 caca_done ; if > 127, all is fine
JMP caca_ovfl2 ; otherwise, mark invalid address
caca_ovfl: CLR F0
caca_ovfl2: MOV A, #$04 ; mask 0000 0100, "address overflow"
CALL set_status_bits
JMP caca_done
; Checks whether address in A is valid
; ------------------------------------
; Inputs:
; - A: address to check
; Outputs:
; - Flag F0: set if address is invalid
check_address:
CLR F0 ; clear result flag
JB7 ca_2 ; check memory if >= 128
ca_end: RET ; everything < 128 is valid
ca_2: MOV R0, #MEM_SIZE ; load memory size...
MOV A, @R0 ; ... to A
JB7 ca_end ; address is valid if CP3 is installed
CPL F0 ; otherwise, set flag
JMP ca_end
; Check operand address and load effective address if valid
; -------------------------------------------------------------
; Inputs:
; - R1: address of operand
; Outputs
; - R1: Effective address
; - F0: Set if address is invalid
check_and_load_effective_address:
MOVX A, @R1 ; Load operand to A ...
MOV R3, A ; ... and to R3
CALL check_address ; check if it's valid
JF0 cacea_end ; bail out if not valid
MOV A, R3 ; Restore operand...
CALL compute_effective_address ; ... and compute address
cacea_end: RET
; Check operand address and test operand and Accu
; -----------------------------------------------
; Inputs:
; - R1: address of operand
; Outputs
; - R0: Accu LSB address
; - R1: Effective address of LSB
; - F0: Set if address is invalid
; - A: Accu MSB or operand cell MSB
check_and_test_operand_and_accu:
MOVX A, @R1 ; Load operand to A ...
MOV R3, A ; ... and to R3
CALL check_address ; check if it's valid
JF0 cata_end ; bail out if not valid
MOV A, R3 ; Restore operand...
CALL compute_effective_address ; ... and compute address
MOVX A, @R1 ; Load MSB
JNZ cata_end ; bail out if it's not data (00.xxx)
INC R1 ; move to LSB
MOV R0, #ACCU ; Load Accu MSB address
MOV A, @R0 ; Load Accu MSB
JNZ cata_end ; bail out if it's not data (00.xxx)
INC R0 ; move to Accu LSB
cata_end: RET
; Check single bit and set Accu
; -----------------------------
; Inputs:
; - A: Value to check for the bit
; - R4: Pin number (1 .. 8)
; Outputs:
; - A: 0 if bit was not set, 1 if it was set
check_bit:
DJNZ R4, cb_rot ; bit at pos 0 yet? jump to rotate if not
JB0 cb_bit_set
CLR A ; bit not set -> clear A
RET
cb_bit_set: MOV A, #$01 ; Store 1 in A
RET
cb_rot: RR A ; rotate to right
JMP check_bit ; jump to check if bit is at correct pos
; Turn internal 8155 to IO mode, disable CP3 8155
; -----------------------------------------------
enable_internal_io:
MOV A, #$40 ; mask 0100 0000 ; 'enable IO'
CALL set_status_bits
MOV A, #$ef ; mask 1110 1111 ; 'access RAM extension'
CALL clear_status_bits
ORL P2, #$a0 ; P2 |= 1010 0000 --> /CE == 1, IO == 1
ANL P2, #$ef ; P2 &= 1110 1111 --> 8155 /CE == 0
RET
; Turn CP3 8155 to IO mode, disable internal 8155
; -----------------------------------------------
enable_cp3_io:
MOV A, #$50 ; mask 0101 0000: 'enable IO' and 'access RAM extension'
CALL set_status_bits
ANL P2, #$df ; P2 &= 1101 1111 --> /CE == 0
ORL P2, #$90 ; P2 |= 1001 0000 --> 8155 /CE == 1, IO == 1
RET
; Stores R0 in VM's Accumulator
; -----------------------------
; Inputs:
; - R0: Value to be stored
save_to_accu:
MOV R0, #ACCU
MOV @R0, #$00
INC R0
MOV @R0, A
RET
; Write single bit to byte
; ------------------------
; Inputs:
; - A: the bit to write (1 .. 8)
; - R0: address of value to write (1 to set bit, 0 to clear bit)
; - R1: address of the byte where to write it
; Outputs:
; - A: the new byte value (with bit set/cleared)
write_bit:
MOV R2, A ; Save bit
CLR C ;
CPL C ; Set carry
CLR A ; Clear A
set_bit_rot: RLC A ; Shift bit in
DJNZ R2, set_bit_rot ; Continue shifting if necessary
MOV R2, A ; Move bit mask to R2
MOV A, @R0 ; load value
JZ clear_bits ; jump if bit is to be set to 0
MOV A, R2 ; Move mask to A
ORL A, @R1 ; OR with target byte
store_result: MOV @R1, A ; Store in target byte
RET
clear_bits: MOV A, R2 ; Move mask to A
CPL A ; negate mask
ANL A, @R1 ; AND with target byte
JMP store_result
; Delay for n millis
; ------------------
; Inputs:
; - R3: number of millis to delay
delay_millis:
MOV R2, #$c8 ; cycles: 2
dm_l1: DJNZ R2, dm_l1 ; cycles: + 200 * 2
DJNZ R3, delay_millis ; cycles: R3 * 201 * 2
RET ; cycles: R3 * 201 * 2 + 1
; Save one block of RAM (256 bytes) to tape
;------------------------------------------
; Correct 8155 needs to be selected already.
; Note that RAM is not written sequentially! First, Address 0 is stored, then 254 ... 1 decending!
save_memory:
MOV R1, #$00 ; Load start address of RAM
byteloop: MOV R0, #STATUS ; Load status byte...
MOV A, @R0 ; ... to A
JB0 stop_pressed_3 ; jump if stop is pressed.
CLR C
MOV R0, #$08 ; 8 bits to send
MOVX A, @R1 ; Read a byte from RAM
bitloop: RRC A ; LSB -> Carry
JNC write_0 ; jump if 0 bit
ANL P1, #$7f ; P1 &= 01111111 -> clear CassData
MOV R3, #$1e ; 30 millis
CALL delay_millis
ORL P1, #$80 ; P1 |= 10000000 -> set CassData
MOV R3, #$3c ; 60 millis
CALL delay_millis
save_cont: DJNZ R0, bitloop ; next bit
DJNZ R1, byteloop ; next byte
save_end: CLR C
RET
write_0: ANL P1, #$7f ; P1 &= 01111111 -> clear CassData
MOV R3, #$3c ; 60 millis
CALL delay_millis
ORL P1, #$80 ; P1 |= 10000000 -> set CassData
MOV R3, #$1e ; 30 millis
CALL delay_millis
JMP save_cont ; back to main loop
stop_pressed_3: CLR F0
CPL F0 ; Set F0 to mark that we were stopped
MOV A, #$fe ; mask 1110 1111 ; 'access RAM extension'
CALL clear_status_bits
JMP save_end
clear_access_ram_extension_status_bit:
MOV A, #$fe ; mask 1110 1111 ; 'access RAM extension'
CALL clear_status_bits
RET
; ????? Dead code?
NOP
.db $ef, $54
XCH A, R0
.db $d2, $36
NOP
EN I
; Dispatch table for opcodes
opcode_jmpp_table:
.db <opcode_inv_trampoline ; opcode 00 Data
.db <opcode_HLT ; opcode 01: HLT
.db <opcode_ANZ ; opcode 02: ANZ
.db <opcode_VZG ; opcode 03: VZG
.db <opcode_AKO ; opcode 04: AKO
.db <opcode_LDA ; opcode 05: LDA
.db <opcode_ABS ; opcode 06: ABS
.db <opcode_ADD ; opcode 07: ADD
.db <opcode_SUB_trampoline ; opcode 08: SUB
.db <opcode_SPU ; opcode 09: SPU
.db <opcode_VGL_trampoline ; opcode 10: VGL
.db <opcode_SPB ; opcode 11: SPB
.db <opcode_VGR_trampoline ; opcode 12: VGR
.db <opcode_VKL_trampoline ; opcode 13: VKL
.db <opcode_NEG ; opcode 14: NEG
.db <opcode_UND ; opcode 15: UND
.db <opcode_P1E_trampoline ; opcode 16: P1E
.db <opcode_P1A_trampoline ; opcode 17: P1A
.db <opcode_P2A_trampoline ; opcode 18: P2A