-
Notifications
You must be signed in to change notification settings - Fork 4
/
firmware.asm
2123 lines (1928 loc) · 110 KB
/
firmware.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
PAGE 0 ; suppress page headings in ASW listing file
;---------------------------------------------------------------------------------------------------------------------------------
; Copyright 2020 Jim Loos
;
; Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
; (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
; publish, distribute, sub-license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
; so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
; IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
;---------------------------------------------------------------------------------------------------------------------------------
;--------------------------------------------------------------------------------------------------
; Firmware for the Intel 4004 Single Board Computer.
; Requires the use of a terminal emulator connected to the SBC
; set for 9600 bps, 8 data bits, no parity, 1 stop bit.
; 9600 bps serial I/O functions 'putchar' and 'getchar' adapted from
; Ryo Mukai's code at https://github.com/ryomuk/test4004
; Syntax is for the Macro Assembler AS V1.42 http://john.ccac.rwth-aachen.de:8000/as/
;----------------------------------------------------------------------------------------------------
cpu 4040 ; Tell the Macro Assembler AS that this source is for the Intel 4040.
; Conditional jumps syntax for Macro Assembler AS:
; jcn t jump if test = 0 - positive voltage or +5VDC
; jcn tn jump if test = 1 - negative voltage or -10VDC
; jcn c jump if cy = 1
; jcn cn jump if cy = 0
; jcn z jump if accumulator = 0
; jcn zn jump if accumulator != 0
include "bitfuncs.inc" ; Include bit functions so that FIN can be loaded from a label (upper 4 bits of address are loped off).
include "reg4004.inc" ; Include 4004 register definitions.
CR equ 0DH
LF equ 0AH
ESCAPE equ 1BH
; I/O port addresses
SERIALPORT equ 00H ; Address of the serial port. The least significant bit of port 00 is used for serial output.
LEDPORT equ 40H ; Address of the port used to control the red LEDs. "1" turns the LEDs on.
GPIO equ 80H ; 4265 General Purpose I/O device address
; 4265 Modes: WMP Port: W X Y Z
GPIOMODE0 equ 0000B ; In In In In (reset)
GPIOMODE4 equ 0100B ; Out Out Out Out
GPIOMODE5 equ 0101B ; In Out Out Out
GPIOMODE6 equ 0110B ; In In Out Out
GPIOMODE7 equ 0111B ; In In In Out
; each 4002 RAM chip consists of 4 registers.
; each register consists of 16 main memory characters plus 4 status characters
CHIP0REG0 equ 00H ; 4002 data ram chip 0, registers 00-0FH
CHIP0REG1 equ 10H ; 4002 data ram chip 0, registers 10-1FH
CHIP0REG2 equ 20H ; 4002 data ram chip 0, registers 20-2FH
CHIP0REG3 equ 30H ; 4002 data ram chip 0, registers 30-3FH
CHIP1REG0 equ 40H ; 4002 data ram chip 1, registers 40-4FH
CHIP1REG1 equ 50H ; 4002 data ram chip 1, registers 50-5FH
CHIP1REG2 equ 60H ; 4002 data ram chip 1, registers 60-6FH
CHIP1REG3 equ 70H ; 4002 data ram chip 1, registers 70-7FH
accumulator equ CHIP0REG0 ; multi-digit addition demo
addend equ CHIP0REG1 ; multi-digit addition demo
minuend equ CHIP0REG1 ; multi-digit subtraction demo
subtrahend equ CHIP0REG2 ; multi-digit subtraction demo
multiplicand equ CHIP0REG1 ; multi-digit multiplication demo
multiplier equ CHIP0REG2 ; multi-digit multiplication demo
product equ CHIP0REG0 ; multi-digit multiplication demo
dividend equ CHIP0REG0 ; multi-digit division demo
divisor equ CHIP0REG2 ; multi-digit division demo
quotient equ CHIP0REG3 ; multi-digit division demo
remainder equ CHIP0REG1 ; multi-digit division demo
randomnumber equ CHIP0REG0 ; number guessing game
increment equ CHIP0REG1 ; number guessing game
guess equ CHIP0REG2 ; number guessing game
attempts equ CHIP0REG3 ; number guessing game
grid equ CHIP0REG2 ; tic-tac-toe game
calcResults equ CHIP0REG3 ; tic-tac-toe game
org 0000H ; beginning of 2732 EPROM
;--------------------------------------------------------------------------------------------------
; Power-on-reset Entry
;--------------------------------------------------------------------------------------------------
nop ; "To avoid problems with power-on reset, the first instruction at
; program address 0000 should always be an NOP." (dont know why)
reset: fim P0,SERIALPORT
src P0
ldm 1
wmp ; set RAM serial output high to indicate 'MARK'
jms halfsecond ; 500 millisecond delay
fim P0,GPIO ; address of the 4265 GPIO device
src P0
ldm GPIOMODE6 ; from the table above ports W and X inputs, ports Y and Z outputs
wmp ; program the 4265 for mode 6 (two 4 bit input ports, two 4-bit output ports)
ldm 1
wr3 ; set alternate serial output (pin 14 of 4265) high to indicate 'MARK'
jms newline
jms banner ; print "Intel 4004 SBC" or "Intel 4040 SBC"
reset2: jms ledsoff ; all LEDs off
reset3: jms menu ; print the menu
reset4: jms getchar ; wait for a character from serial input, the character is returned in P1
testfor0: fim P2,'0'
jms compare ; is the character from the serial port '0'?
jcn nz,testfor1 ; jump if no match
jun reset2 ; no menu item assigned to '0' yet
testfor1: fim P2,'1'
jms compare ; is the character from the serial port '1'?
jcn nz,testfor2 ; jump if no match
jun leddemo1 ; '1' selects LED demo 1
testfor2: fim P2,'2'
jms compare ; is the character from the serial port '2'?
jcn nz,testfor3 ; jump if no match
jun leddemo2 ; '2' selects LED demo 2
testfor3: fim P2,'3'
jms compare ; is the character from the serial port '3'?
jcn nz,testfor4 ; jump if no match
jun adddemo ; '3' selects decimal addition demo
testfor4: fim P2,'4'
jms compare ; is the character from the serial port '4'?
jcn nz,testfor5 ; jump if no match
jun subdemo ; '4' selects decimal subtraction demo
testfor5: fim P2,'5'
jms compare ; is the character from the serial port '5'?
jcn nz,testfor6 ; jump if no match
jun multdemo ; '5' selects decimal multiplication demo
testfor6: fim P2,'6'
jms compare ; is the character from the serial port '6'?
jcn nz,testfor7 ; jump if no match
jun divdemo ; '6' selects decimal division demo
testfor7: fim P2,'7'
jms compare ; is the character from the serial port '7'?
jcn nz,testfor8 ; jump if no match
jun TTTGame ; '7' selects Tic-Tac-Toe game
testfor8: fim P2,'8'
jms compare ; is the character from the serial port '8'?
jcn nz,testfor9 ; jump if no match
jun guessgame ; '8' selects number guessing game
testfor9: fim P2,'9'
jms compare ; is the character from the serial port '9'?
jcn nz,nomatch ; jump if no match
jun switchdemo ; '9' selects rotary switch demo
nomatch: ld R9 ; 'state' is kept in R9
jcn z,state0 ; jump if 'state' is 0
ldm 1
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R9 ; compare 'state' in R9 to 1 by subtraction
jcn z,state1 ; jump if 'state' is 1
ldm 2
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R9 ; compare 'state' in R9 to 2 by subtraction
jcn z,state2 ; jump if 'state' is 2
ldm 0 ; else reset 'state' back to zero
xch R9
jun reset2 ; display the menu options, go back for the next character
state0: fim P2,ESCAPE ; state=0, we're waiting for the 1st ESCAPE
jms compare ; is the character from the serial port ESCAPE?
jcn nz,reset2 ; if not ESCAPE, display the menu options, go back for the next character
ldm 1 ; the 1st ESCAPE has been received
xch R9 ; advance 'state' from 0 to 1
jun reset4 ; go back for the next character
state1: fim P2,ESCAPE ; the 1st ESCAPE has been received, we're waiting for the 2nd ESCAPE
jms compare
jcn nz,state1a ; is the character ESCAPE?
ldm 2 ; else advance 'state' from 1 to 2
xch R9
jun reset4 ; the 2nd ESCAPE has been received, go back for the next character
state1a: ldm 0 ; else reset 'state' back to 0
xch R9
jun reset2 ; display the menu options, go back for the next character
state2: ldm 0 ; state=2, the 2nd ESCAPE has been received, now we're waiting for the "?"
xch R9 ; reset 'state' back to 0
fim P2,"?"
jms compare ; was it '?'
jcn nz,reset2 ; not '?', display the menu options, go back for the next character
jms newline ; ESCAPE,ESCAPE,? has been detected
jms banner
jms builtby ; display the "built by" message
jun reset2 ; display the menu options, go back for the next character
;--------------------------------------------------------------------------------------------------
; detects 4004 or 4040 CPU by using the "AN7" instruction.
; available on the 4040 but not on the 4004.
; returns 1 for 4004 CPU. returns 0 for 4040 CPU.
;--------------------------------------------------------------------------------------------------
detectCPU: ldm 0
xch R7 ; R7 now contains 0000
ldm 1111b ; accumulator now contains 1111
an7 ; logical AND the contents of the accumulator (1111) with contents of R7 (0000)
; if 4040, the accumulator now contains 0000; if 4004, accumulator remains at 1111
rar ; rotate the least significant bit of the accumulator into carry
jcn c,detectCPU1 ; if carry is set, logical AND failed, must be a 4004
bbl 0 ; return indicating 4040
detectCPU1: bbl 1 ; return indicating 4004
;--------------------------------------------------------------------------------------------------
; turn off all four LEDs
;--------------------------------------------------------------------------------------------------
ledsoff: fim P0,LEDPORT
src P0
ldm 0
wmp ; write data to RAM LED output port, set all 4 outputs low to turn off all four LEDs
bbl 0
;--------------------------------------------------------------------------------------------------
; Compare the contents of P1 (R2,R3) with the contents of P2 (R4,R5).
; Returns 0 if P1 = P2.
; Returns 1 if P1 < P2.
; Returns 2 if P1 > P2.
; Overwrites the contents of P2.
; Adapted from code in the "MCS-4 Micro Computer Set Users Manual" on page 166:
;--------------------------------------------------------------------------------------------------
compare: xch R4 ; contents of R7 (high nibble of P3) into accumulator
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R2 ; compare the high nibble of P1 (R2) to the high nibble of P3 (R6) by subtraction
jcn cn,greater ; no carry means that R2 > R6
jcn zn,lesser ; jump if the accumulator is not zero (low nibbles not equal)
clc ; clear carry in preparation for 'subtract with borrow' instruction
xch R5 ; contents of R6 (low nibble of P3) into accumulator
sub R3 ; compare the low nibble of P1 (R3) to the low nibble of P3 (R7) by subtraction
jcn cn,greater ; no carry means R3 > R7
jcn zn,lesser ; jump if the accumulator is not zero (high nibbles not equal)
bbl 0 ; 0 indicates P1=P3
lesser: bbl 1 ; 1 indicates P1<P3
greater: bbl 2 ; 2 indicates P1>P3
;-----------------------------------------------------------------------------------------
; position the cursor to the start of the next line
;-----------------------------------------------------------------------------------------
newline: fim P1,CR
jms putchar
fim P1,LF
jun putchar
;-----------------------------------------------------------------------------------------
; This function is used by all the text string printing functions. If the character in P1
; is zero indicating the end of the string, returns 0. Otherwise prints the character and
; increments P0 to point to the next character in the string then returns 1.
;-----------------------------------------------------------------------------------------
txtout: ld R2 ; load the most significant nibble into the accumulator
jcn nz,txtout1 ; jump if not zero (not end of string)
ld R3 ; load the least significant nibble into the accumulator
jcn nz,txtout1 ; jump if not zero (not end of string)
bbl 0 ; end of text found, branch back with accumulator = 0
txtout1: jms putchar ; print the character in P1
inc R1 ; increment least significant nibble of pointer
ld R1 ; get the least significant nibble of the pointer into the accumulator
jcn zn,txtout2 ; jump if zero (no overflow from the increment)
inc R0 ; else, increment most significant nibble of the pointer
txtout2: bbl 1 ; not end of text, branch back with accumulator = 1
;-------------------------------------------------------------------------------
; this is the function that performs the multi-digit decimal subtraction
; for the subtraction demo below
;-------------------------------------------------------------------------------
subtract: ldm 0
xch R11 ; R11 is the loop counter (0 gives 16 times thru the loop for 16 digits)
stc ; set carry=1
subtract1: tcs ; accumulator = 9 or 10
src P2 ; select the subtrahend
sbm ; produce 9's or l0's complement
clc ; clear carry in preparation for 'add with carry' instruction
src P1 ; select the minuend
adm ; add minuend to accumulator
daa ; adjust accumulator
wrm ; write result to replace minuend
inc R3 ; address next digit of minuend
inc R5 ; address next digit of subtrahend
isz R11,subtract1 ; loop back for all 16 digits
jcn c,subtract2 ; carry set means no underflow from the 16th digit
bbl 1 ; overflow, the difference is negative
subtract2: bbl 0 ; no overflow, the difference is positive
org 0100H ; next page
;-------------------------------------------------------------------------------
; Decimal subtraction demo.
; P1 points to the minuend stored in RAM register 10H least significant digit at 10H,
; most significant digit at 1FH.
; P2 points to the subtrahend is stored in RAM register 20H least significant digit at 20H,
; most significant digit at 2FH.
; the subtrahend is subtracted from the minuend. The difference replaces the minuend i.e. *P1=*P1-*P2
; Adapted from code in "MCS-4 Micro Computer Set Users Manual, Feb. 73" page 4-23.
;--------------------------------------------------------------------------------
subdemo: jms subinstr
subdemo1: fim P2,minuend ; P2 points the memory register where the minuend digits are stored (10H-1FH)
jms clrram ; clear RAM 10H-1FH
fim P2,subtrahend ; P2 points the memory register where the subtrahend digits are stored (20H-2FH)
jms clrram ; clear RAM 20H-1FH
jms newline ; position carriage to beginning of next line
jms newline ; blank line
jms firstnum ; prompt for the first number (minuend)
fim P2,minuend ; destination address for minuend: 1FH down to 10H
ldm 0 ; up to 16 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the first number (minuend)
jcn z,subdemo1a
jun reset2 ; control C exits
subdemo1a: jms newline
jms secondnum ; prompt for the second number (subtrahend)
fim P2,subtrahend ; destination address for subtrahend: 2FH down to 20H
ldm 0 ; up to 16 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the second number (subtrahend)
jcn z,subdemo1b
jun reset2 ; control C exits
subdemo1b: jms newline
jms prndiff ; print "Difference:"
fim P1,minuend ; P1 points to the 16 digit minuend (number from which another is to be subtracted)
fim P2,subtrahend ; P2 points to the 16 digit subtrahend (number to be subtracted from another)
jms subtract ; subtract subtrahend from minuend
jcn z,subdemo3 ; zero means no overflow, the difference is a positive number
; the difference is a negative number
; convert from 10's complement...
fim P2,subtrahend
jms clrram ; zero RAM 20H-2FH
fim P1,subtrahend ; P1 points to the 16 digit minuend (all zeros)
fim P2,minuend ; P2 points to the 16 digit subtrahend (the negative result from subtraction above)
jms subtract ; subtract the negative number from zero
fim P1,'-' ; minus sign
fim P3,subtrahend ; the result is in RAM at 20H-2FH
jun subdemo4 ; go print the converted result
; the difference is a positive number
subdemo3: fim P3,minuend ; P3 points to the result in RAM at 10H-1FH
fim P1,' ' ; space
subdemo4: jms putchar ; print a space
jms prndigits ; print the 16 digits of the difference
jun subdemo1 ; go back for another pair of numbers
;-------------------------------------------------------------------------------
; Decimal addition demo.
; P1 points to the first integer (the accumulator) stored in RAM register 10H (CHIP0REG1)
; least significant digit at 10H, most significant digit at 1FH.
; P2 points to the second integer (the addend) stored in RAM register 20H (CHIP0REG2)
; least significant digit at 20H, most significant digit at 2FH.
; The 16 digit sum replaces the first integer in RAM register 10H (CHIP0REG1) least significant digit at 10H,
; most significant digit at 1FH.
; Adapted from the code in "MCS-4 Micro Computer Set Users Manual, Feb. 73" page 77.
;--------------------------------------------------------------------------------
adddemo: jms addinstr
adddemo1: fim P2,accumulator ; P2 points the memory register where the first number (and sum) digits are stored (10H-1FH)
jms clrram ; clear RAM 10H-1FH
fim P2,addend ; P2 points the memory register where the second number digits are stored (20H-2FH)
jms clrram ; clear RAM 20H-2FH
jms newline ; position carriage to beginning of next line
jms newline
jms firstnum ; prompt for the first number
fim P2,accumulator ; destination address for first number
ldm 0 ; up to 16 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the first number
jcn z,adddemo1a
jun reset2 ; control C exits
adddemo1a: jms newline
jms secondnum ; prompt for the second number
fim P2,addend ; destination address for second number
ldm 0 ; up to 16 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the second number
jcn z,adddemo1b
jun reset2 ; control C exits
adddemo1b: jms newline
jms prnsum ; print "Sum: "
fim P1,accumulator ; P1 points to the first 16 digit number (called the accumulator)
fim P2,addend ; P2 points to the second 16 digit number (called the addend) to be added to the first
jms addition ; add the two numbers
jcn zn,adddemo2 ; jump if overflow
fim P3,accumulator ; P3 points to the sum
jms prndigits ; print the 16 digits of the sum
jun adddemo1 ; go back for another pair of numbers
adddemo2: jms prnoverflow ; the sum of the two numbers overflows 16 digits
jun adddemo1 ; go back for another pair of numbers
;-------------------------------------------------------------------------------
; this is the function that performs the multi-digit decimal addition
; for the addition demo above
;-------------------------------------------------------------------------------
addition: ldm 16-16
xch R11 ; R6 is the loop counter (0 gives 16 times thru the loop for all 16 digits)
clc ; clear carry in preparation for 'add with carry' instruction
addition1: src P2 ; P2 points to the addend digits
rdm ; read the addend digit
src P1 ; P1 points to the "accumulator"
adm ; add the digit from the "accumulator" to the addend
daa ; convert the sum from binary to decimal
wrm ; write the sum back to the "accumulator"
inc R3 ; point to next "accumlator" digit
inc R5 ; point to next addend digit to be added to the accumulator
isz R11,addition1 ; loop 16 times (do all 16 digits)
jcn cn,addition2 ; no carry means no overflow from the 16th digit addition
bbl 1 ; 16 digit overflow
addition2: bbl 0 ; no overflow
;-------------------------------------------------------------------------------
; Decimal multiplication demo.
; P1 points to the multiplicand stored in RAM register 10H (CHIP0REG1), characters 15H
; through 1CH where the digit at location 15H is the least significant digit
; and the digit at location 1CH is the most significant digit.
; P2 points to the multiplier stored in RAM register 20H (CHIP0REG2), characters 24H through 2BH
; where the digit at location 24H is the least significant digit and the digit
; at location 2BH is the most significant digit.
; P3 points to the product stored in RAM register 00H (CHIP0REG0), characters 00H through 0FH
; where the digit at location 00H is the least significant digit and the digit
; at location 0FH is the most significant digit.
; The actual multiplication is done by the "MLRT" routine taken from:
; "A Microcomputer Solution to Maneuvering Board Problems" by Kenneth Harper Kerns, June 1973
; Naval Postgraduate School Monterey, California.
;--------------------------------------------------------------------------------
multdemo: jms multinstr
multdemo1: fim P2,multiplicand ; P2 points the memory register where the multiplicand is stored (10H-1FH)
jms clrram ; clear RAM 10H-1FH
fim P2,multiplier ; P2 points the memory register where the multiplier is stored (20H-2FH)
jms clrram ; clear RAM 20H-2FH
jms newline ; position carriage to beginning of next line
jms newline
jms firstnum ; prompt for the multiplicand
fim P2,multiplicand+5 ; destination address for multiplicand (15H)
ldm 16-8 ; up to 8 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the multiplicand (8 digits max) into RAM at 15H-1CH
jcn z,multdemo1a
jun reset2 ; control C exits
multdemo1a: jms newline
jms secondnum ; prompt for the multiplier
fim P2,multiplier+4 ; destination address for multiplier (24H)
ldm 16-8 ; up to 8 digits
xch R13 ; R13 is the digit counter
jms getnumber ; get the multiplier (8 digits max) into RAM at 24H-2BH
jcn z,multdemo1b
jun reset2 ; control C exits
multdemo1b: jms newline
jms prnproduct ; print "Product: "
fim P1,multiplicand ; multiplicand
fim P2,multiplier ; multiplier
fim P3,product ; product goes here
jms MLRT ; the function that does the actual multiplication
fim P3,product ; P3 points to the product at RAM address 00H-0FH
jms prndigits ; print the 16 digits of the product
jun multdemo1 ; go back for another pair of numbers
;--------------------------------------------------------------------------------------------------
; 9600 bps N-8-1 serial function 'putchar'
; send the character in P1 to the console serial port (the least significant bit of port 0)
; in addition to P1 (R2,R3) also uses P7 (R14,R15)
; preserves the character in P1.
;--------------------------------------------------------------------------------------------------
putchar: fim P7,SERIALPORT
src P7 ; set port address
ldm 16-5
xch R14 ; 5 bits (start bit plus bits 0-3)
ld R3
clc ; clear carry to make the start bit
ral
; send 5 bits; the start bit and bits 0-3. each bit takes 9 cycles
putchar1: nop
nop
nop
nop
nop
wmp
rar
isz R14, putchar1
ldm 16-5 ; 5 bits (bits 4-8 plus stop bit)
xch R14
ld R2
stc
nop
nop
; send 5 bits; bits 4-7 and the stop bit. each bit takes 10 cycles
putchar2: wmp
nop
nop
nop
nop
nop
nop
rar
isz R14, putchar2
bbl 0
org 0200H ; next page
;-------------------------------------------------------------------------------
; Decimal division demo.
; P1 points to the dividend in RAM register 00H (CHIP0REG0), characters 00H through 06H
; (least significant digit at location 00H, most significant digit at location 06H).
; P3 points to the divisor in RAM register 20H (CHIP0REG2), characters 20H through 27H
; (least significant digit at location 20H, most significant digit at location 27H).
; P4 points to the quotient in RAM register 30H (CHIP0REG3) least significant digit
; at 30H, most significant digit at 3FH.
; P2 points to the remainder in RAM register 10H (CHIP0REG1) least significant digit
; at 10H, most significant digit at 1FH.
; The actual division is done by the "DVRT" routine taken from:
; "A Microcomputer Solution to Maneuvering Board Problems" by Kenneth Harper Kerns, June 1973
; Naval Postgraduate School Monterey, California.
;--------------------------------------------------------------------------------
divdemo: jms divinstr
divdemo1: fim P2,dividend ; P2 points the memory register where the dividend is stored (00H-0FH)
jms clrram ; clear RAM 10H-1FH
fim P2,divisor ; P2 points the memory register where the divisor is stored (20H-2FH)
jms clrram ; clear RAM 20H-2FH
jms newline
jms newline
jms firstnum ; prompt for the dividend
fim P2,dividend ; destination address for the dividend (00H-06H)
ldm 16-7 ; maximum of 7 digits for the dividend
xch R13 ; R13 is the digit counter for the getnumber function
jms getnumber ; get the dividend
jcn z,divdemo1a
jun reset2 ; control C exits
divdemo1a: jms newline
jms secondnum ; prompt for the divisor
fim P2,divisor ; destination address for the divisor (20H-27H)
ldm 16-8 ; maximum of 8 digits for the divisor
xch R13 ; R13 is the digit counter for the getnumber function (8 digits)
jms getnumber ; get the divisor
jcn z,divdemo1b
jun reset2 ; control C exits
divdemo1b: jms newline
jms prnquotient ; print "Quotient:"
fim P1,dividend ; points to dividend
fim P2,remainder ; points to remainder
fim P3,divisor ; points to divisor
fim P4,quotient ; points to quotient
jms DVRT ; the function that does the actual division
fim P3,quotient ; P3 points to the quotient
jms prnquot ; print the 16 digits of the quotient
jun divdemo1 ; go back for more of numbers
;-----------------------------------------------------------------------------------------
; Flashing LED demo.
; Flash the LEDs from right to left and then from left to right in a "Knight Rider"
; or "Cylon" type pattern. Exit when a key is pressed.
;-----------------------------------------------------------------------------------------
leddemo1: ldm 0001B ; start with the first LED
fim P0,LEDPORT
src P0
leddemo11: wmp ; output to port to turn on LED
xch R0 ; the accumulator need to be saved in R0 since the 'bbl' instruction overwrites the accumulator
jms leddelay ; delay for 100 milliseconds. abort by jumping to reset if start bit detected
jcn z,$+4 ; jump around the next instruction if the start bit not detected
jun reset2 ; a key has been pressed (start bit detected), go back to the beginning
xch R0 ; restore the accumulator from R0
clc ; the carry bit needs to be cleared since the delay subroutine sets the carry bit
ral ; rotate the accumulator left thru carry
jcn cn,leddemo11 ; jump if cy=0
ldm 0100B ; change directions, start shifting right.
leddemo12: wmp
xch R0
jms leddelay ; delay for 100 milliseconds. abort by jumping to reset if start bit detected
jcn z,$+4 ; jump around the next instruction if the start bit not detected
jun reset2 ; a key has been pressed (start bit detected), go back to the beginning
xch R0
clc
rar
jcn cn,leddemo12
ldm 0010B ; change directions, go back to shifting left
jun leddemo11
;-----------------------------------------------------------------------------------------
; Another flashing LED demo.
; Flash the LEDs from right to left in a "chaser" pattern.
; Exit when a key is pressed.
;-----------------------------------------------------------------------------------------
leddemo2: fim P0,LEDPORT ; define the led port for port writes
src P0
ldm 0001B ; one LED
jms leddemo21
ldm 0011B ; two LEDs
jms leddemo21
ldm 0111B ; three LEDs
jms leddemo21
ldm 1111b ; all four LEDs
jms leddemo21
ldm 1110B ; back to three LEDs
jms leddemo21
ldm 1100B ; back to two LEDs
jms leddemo21
ldm 1000B ; back to one LED
jms leddemo21
ldm 0000B ; all LEDs off
jms leddemo21
jun leddemo2 ; go back and repeat
leddemo21: wmp ; output to port to turn on LEDs
jms leddelay ; delay for 100 milliseconds
jcn z,$+4 ; jump around the next instruction if the start bit not detected
jun reset2 ; a key has been pressed (start bit detected), go back to the beginning
bbl 0
;-----------------------------------------------------------------------------------------
; 100 millisecond delay for the flashing LED demos.
; Check the 4004's TEST input for detection of the start bit every millisecond.
; Returns 1 if the start bit has been detected, otherwise returns 0.
; Uses P6 (R12,R13) and P7 (R14,R15)
;-----------------------------------------------------------------------------------------
leddelay: ldm 15-10 ; 10 times through the outer loop
xch R13 ; counter for the outer loop
leddelay1: ldm 15-10 ; 10 times through the inner loop
xch R12 ; counter for the inner loop
leddelay2: jcn t,$+3 ; skip the following instruction if TEST = 0 (the start bit has not been received)
bbl 1 ; the start bit has been detected, return 1
fim P7,07DH
leddelay3: isz R14,leddelay3 ; inner loop 1 millisecond delay
isz R15,leddelay3 ;
isz R12,leddelay2 ; inner loop executed 10 times (10 milliseconds)
isz R13,leddelay1 ; outer loop executed 10 times (100 milliseconds)
bbl 0
;-----------------------------------------------------------------------------------------
; Display the position of the 16 position rotary switch using the serial port and LEDs.
; R10 holds the current switch reading. R11 holds the previous switch reading.
;-----------------------------------------------------------------------------------------
switchdemo: ldm 0
fim P2, LEDPORT
src P2
wmp ; turn off all four LEDs
xch R11 ; initialize R11 to zero
ldm 0001B
fim P0,SERIALPORT
src P0
wmp ; set serial port output high (MARK)
readsw: jms newline ; position the cursor to the beginning of the next line
fim P3,GPIO
src P3 ; address of the 4265 GPIO
readsw1: rd0 ; read port W of the 4265 GPIO
xch R10 ; save the current switch reading in R10
jms tenmsec ; ten millisecond delay for switch de-bouncing
jcn zn,exitswdemo ; exit if start bit is detected
jms tenmsec ; ten millisecond delay for switch de-bouncing
jcn zn,exitswdemo ; exit if start bit is detected
jms tenmsec ; ten millisecond delay for switch de-bouncing
jcn zn,exitswdemo ; exit if start bit is detected
rd0 ; re-read the switches
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R10 ; R10 contains the switch reading from 30 milliseconds ago
jcn nz,readsw1 ; go back if two readings 30 milliseconds apart don't match (contacts are still bouncing)
ld R11 ; recall the previous switch reading
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R10 ; compare to the current reading by subtraction
jcn z,readsw1 ; go back if the switch has not changed
ld R10 ; recall the current switch reading from R10
xch R11 ; save it in R11 for next time
ld R10
cma ; complement the switch reading since closed contacts pull low (i.e. position '0' = 1111, position 'F' = 0000)
fim P2, LEDPORT
src P2
wmp ; turn on LEDs to indicate switch position
fim P0,lo(positions) ; lo byte of the address "positions"
clc ; clear carry in preparation for 'add with carry' instruction
add R1
jcn cn,nocarry ; jump if no carry (overflow) from the addition of R1 to the accumulator
inc R0
nocarry: xch R1
fin P1 ; get the character indexed by the switch setting into P1
jms putchar ; print the character in P1
jun readsw ; go back and do it again if TEST input is 0 (the start bit has not been received)
exitswdemo jun reset2
positions: data "0123456789ABCDEF"
;-------------------------------------------------------------------------------
; Returns with zero if what remains of the fractional part of the quotient part
; is all zeros and thus does not need to be printed, otherwise returns with 1.
; used by the prnquot (print quotient) function as part of the division demo.
;-------------------------------------------------------------------------------
zeros: ld R6
xch R2
ld R7
xch R3 ; P1 now points next digit of the fractional part not yet printed
zeros1: src P1
rdm ; read the digit of the fractional part
jcn zn,zeros2 ; exit if not zero
ld R3
dac
xch R3 ; next digit
ldm 0FH
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R3 ; have we come to the end (has R3 wrapped around to 0FH)?
jcn zn,zeros1 ; no, go back for the next digit
bbl 0 ; return with zero
zeros2: bbl 1 ;return with non-zero
org 0300H ; next page
;-------------------------------------------------------------------------------
; Multi-digit multiplication function taken from:
; "A Microcomputer Solution to Maneuvering Board Problems" by Kenneth Harper Kerns, June 1973
; Naval Postgraduate School Monterey, California.
; On entry, P1 points to the multiplicand, P2 points to the multiplier, P3 points to the product.
; Sorry about the lack of comments. That's how it was done back in the day of slow teletypes.
;-------------------------------------------------------------------------------
MLRT clb
xch R7
ldm 0
xch R14
ldm 0
ZLPM src P3
wrm
isz R7,ZLPM
wr0
ldm 4
xch R5
src P1
rd0
rar
jcn cn,ML4
ld R2
xch R0
ldm 0
xch R1
jms CPLRT
stc
ldm 0FH
src P1
wr3
ML4 ral
xch R15
src P2
rd0
rar
jcn cn,ML6
ld R4
xch R0
ldm 0
xch R1
jms CPLRT
stc
ldm 0FH
src P2
wr3
ML6 ral
clc
add R15
src P3
wr0
ML1 src P2
rdm
xch R15
ML2 ld R15
jcn z,ML3
dac
xch R15
ldm 5
xch R3
ld R14
xch R7
jms MADRT
jun ML2
ML3 inc R14
isz R5,ML1
src P3
rd0
rar
jcn cn,ML5
ldm 0
wr0
xch R1
ld R6
xch R0
jms CPLRT
ML5 src P1
rd3
jcn z,ML8
ld R2
xch R0
ldm 0
xch R1
jms CPLRT
ldm 0
src P1
wr3
ML8 src P2
rd3
jcn z,ML7
ld R4
xch R0
ldm 0
xch R1
jms CPLRT
ldm 0
src P2
wr3
nop
nop
ML7 bbl 0
MADRT clc
STMAD src P1
rdm
src P3
adm
daa
wrm
isz R3,SKIPML
bbl 0
SKIPML isz R7,STMAD
bbl 0
CPLRT clc
COMPL src P0
ldm 6
adm
cma
wrm
isz R1,COMPL
stc
TENS ldm 0
src P0
adm
daa
wrm
inc R1
jcn c,TENS
src P0
rd0
rar
cmc
ral
wr0
bbl 0
;-------------------------------------------------------------------------------
; Print the 16 digit quotient in RAM register pointed to by P3. The most significant
; digit is at location 0FH, therefore it's the first digit printed. The least significant
; digit is at location 00H, so it's the last digit printed. Prints the first 7 digits
; (the whole number part), then the decimal point, then the remaining 9 digits
; (the fractional part). Suppresses leading and trailing zeros. R11 serves as a
; leading zero flag (1 means skip leading zeros).
; Adapted from code in the "MCS-4 Micro Computer Set Users Manual, Feb. 73".
;-------------------------------------------------------------------------------
prnquot: ldm 0
xch R10 ; R10 is the loop counter (0 gives 16 times thru the loop for all 16 digits of the register)
ldm 0FH
xch R7 ; make P3 point to the most significant digit of the quotient
ldm 1
xch R11 ; set the leading zero flag
prnquot1: src P3 ; P3 points to the digit to be printed
ldm 9 ; units digit (the one immediately to the left of the decimal point) is at address 9
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R7 ; compare by subtraction
jcn zn,prnquot2 ; jump if this is not the units digit
ldm 0
xch R11 ; since this is the units digit, clear the leading zero flag
prnquot2: ld R11 ; get the leading zero flag
rar ; rotate the flag into carry
rdm ; read the digit to be printed
jcn zn,prnquot3 ; jump if this digit to be printed is not zero
jcn c,prnquot4 ; this digit is zero, jump if the leading zero flag is set
prnquot3: xch R3 ; this digit is not zero OR the leading zero flag is not set. put the digit as least significant nibble into R3
ldm 3
xch R2 ; most significant nibble ("3" for ASCII characters 30H-39H)
jms putchar ; print the ASCII code for the digit
src P3
ldm 0
xch R11 ; now that a digit has been printed, reset the leading zero flag
prnquot4: ld R7 ; least significant nibble of the pointer to the digit
dac ; next digit
xch R7 ; back into R7, P3 now points to the next digit of the quotient to be printed
ldm 8 ; the fractional part of the quotient begins at address 8
clc ; clear carry in preparation for 'subtract with borrow' instruction
sub R7 ; compare by subtraction. acc is zero if R7 equals 8. the carry flag is set if R7 less than or equal 8
jcn zn,prnquot5 ; jump if R7 != 8 (the next digit to be printed is not the tenths digit)
jms zeros ; the next digit to be printed is the tenths digit. check if the fractional part of the quotient is all zeros
jcn z,prnquot7 ; if the fractional part is all zeros, skip to the end and exit
fim P1,'.' ; else use a decimal point before the tenths digit to separate the whole number and fractional parts
jms putchar ; print the decimal point
jun prnquot6 ; go increment counter
prnquot5: jcn cn,prnquot6 ; jump if the next digit to be printed is not part of the fractional part
jms zeros ; we're printing the fractional part. is the rest of fractional part all zeros?
jcn z,prnquot7 ; if the rest of the fractional part is all zeros, skip to the end
prnquot6: isz R10,prnquot1 ; loop 16 times to print all 16 digits
prnquot7: bbl 0 ; finished with all 16 digits, return to caller
;-----------------------------------------------------------------------------------------
; 9600 bps N-8-1 serial function 'getchar'
; wait for a character from the serial input port (TEST input on the 4004 CPU).
; NOTE: the serial input line is inverted by hardware before it gets to the TEST input;
; i.e. TEST=0 when the serial line is high and TEST=1 when the serial line is low,
; therefore the sense of the bit needs to be inverted in software.
; returns the 8 bit received character in P1 (R2,R3). also uses P7 (R14,R15).
;-----------------------------------------------------------------------------------------
getchar: jcn t,$ ; wait for the start bit
getchar0: ldm 16-4 ; 4 bits
xch R14 ; R14 is the counter for the first four bits (0-3)
ldm 16-3
xch R15
isz R15,$ ; 12 cycles between start bit and bit 0
; receive bits 0-3
getchar1: jcn tn,getchar2 ; jump if the test input==1
stc ; if test input==0, then cy=1
jun getchar3
getchar2: clc ; if test input==1, then cy=0
jun getchar3
getchar3: rar ; rotate carry into accumulator
nop ; 9 cycles/bit (error=-0.645 cycle/bit)
isz r14, getchar1 ; repeat until all 4 bits (0-3) received. phase(here)= 2.355 -0.645*3 = 0.42cycle
xch R3 ; save received bits 0-3 in R3
ldm 16-4
xch R14 ; R14 is the counter for the next 4 bits (bits 4-8)
; receive bits 4-8
getchar4: jcn tn,getchar5 ; jump if the test input==1
stc ; if test input==0, then cy=1
jun getchar6
getchar5: clc ; if test input==1, then cy=0
nop
nop
getchar6: rar ; rotate received bit into accumulator
nop ; 9 cycles/bit
isz R14,getchar4 ; repeat until 4 bits (4-8) received.
xch R2 ; save received bits 4-7 in R2
; check the stop bit...
jcn tn, getchar7 ; jump if the stop bit = 1
bbl 1 ; else return 1 to indicate stop bit was 0 (timing error)
getchar7: bbl 0 ; return 0 to indicate correct timing
org 0400H ; next page
;-------------------------------------------------------------------------------
; Multi-digit division routine taken from:
; "A Microcomputer Solution to Maneuvering Board Problems" by Kenneth Harper Kerns, June 1973
; Naval Postgraduate School Monterey, California.
; P1 points to the dividend, P2 points to the remainder,
; P3 points to the divisor, P4 points to the quotient
;-------------------------------------------------------------------------------
; DIVIDE ROUTINE, SETS UP TO USE DECDIV
DVRT src P1
rd0
rar
jcn cn,DV4
ld R2
xch R0
ldm 0
xch R1
jms CPLRT
stc
ldm 1
wr1
DV4 ral
xch RF
src P3
rd0
rar
jcn cn,DV6
ld R6
xch R0
ldm 0
xch R1