-
Notifications
You must be signed in to change notification settings - Fork 0
/
riscy.asm
1883 lines (1577 loc) · 64.1 KB
/
riscy.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
;;; -*- Mode:Asm Mode:outline-minor-mode outline-regexp:";;;+" comment-start: "; " -*-
;;; Riscy Pygness -- Pygmy Forth for the ARM
;;;; Copyright
;; Copyright (c) 2004-2010 Frank C. Sergeant
;; Freely available under a modified BSD/MIT/X license.
;; Details at http://pygmy.utoh.org/license20040130.txt.
;;;; Introduction
;; This file defines all the assembly routines (i.e., the
;; primitives). The result of assembling the primitives is
;; not quite enough to produce the Forth kernel. The program
;; riscy.tcl loads some high-level Forth on top of the
;; primitives to make a kernel image that can be burned into
;; the flash on the ARM chip.
;;;; Pre-processing step
;; This file uses the semicolon as the comment character. The
;; *.asm files are preprocessed (via the makefile) by
;; preasm.tcl to change all semicolons to at-signs so that the
;; GNU ARM assembler will be happy.
;;;; CPU and board variants
.equ <BOARD>, 1
;; This file, riscy.asm, is normally not assembled directly.
;; The above .equ needs to be replaced with the appropriate
;; "lpc2106", "lpc23xx", etc., symbol. So, this file is, in
;; effect, a template.
;; This replacement is done automatically by the makefile,
;; creating files named riscy-lpc2106.asm, etc.
;; Things that are specific to a particular ARM variant or
;; development board are factored into separate files that are
;; included by this file. See the ".ifdef" and ".include"
;; directives below.
;; At the moment, the possibilities are "lpc2106", "lpc23xx",
;; "lpc2103", and "lpc2294".
;; Feel free to add additional ARM variant or board-specific
;; files, giving your new choices unique symbol names and
;; adjusting the ".ifdef" directives to include your new
;; files. Adjust makefile appropriately also.
;;;; Mapping assembler label names to Forth names
;; Because of assembler naming conventions, we cannot always
;; label the code that implements a Forth primitive with the
;; Forth name for that primitive.
;; Because of this, and because the program riscy.tcl needs a
;; list of all the primitives, the code for every primitive
;; must be preceded with a comment, in a special format, that
;; maps the assembly label to the Forth name.
;; For example, to map the label "cfetch:" to the Forth name
;; "C@", we use the following comment:
;; ";;; label cfetch is Forth word C@ ( a - c)"
;; There must be at least three semicolons but there may be
;; more. The program riscy.tcl parses this file to collect
;; the Forth word names and map them to the associated
;; assembly language labels.
;;;; Forth virtual machine
;; Register usage:
;; r0 caches TOS (top of stack)
;; r2 is TEMPREG, scratch -- used within the nxt routine
;; r3 scratch -- often used to hold an address
;; r4 scratch
;; r5 scratch
;; r6 is a scratch register for holding base addresses such as IOBASE
;; r7 UP, User Pointer, holds address of current task control block
;; r8 Forth W
;; holds xt address, in the form used by our
;; particular threading model. The left 13 bits hold
;; the token number and the rightmost 3 bits hold
;; flags. See the code at nxtTab for details.
;; r9 Forth IP (instruction pointer) (holds address of next 16-bit xt)
;; r10 Forth data stack pointer
;; r11 Forth return stack pointer
;; r12 Loop counter
;; sp, i.e., the hardware stack pointer, is r13 by default
;; we don't use it for the Forth, although actual nested
;; subroutine calls may use it.
TOS .req r0
TEMPREG .req r2
UP .req r7
W .req r8
IPTR .req r9 ; "Instruction Pointer" can't use "IP" because gas hardcodes "IP" as reg 12
DSTK .req r10
RSTK .req r11
RLOOP .req r12
;.equ RAMBASE, 0x40000200
.equ RAMBASE, 0x40000000
;; We might change this to pick up RAMBASE from the linker file.
;; 0x40000000 is the start of on-chip RAM for the NXP LPC chips. If
;; we wished to use the IAP (in-application programming) facility to
;; reprogram the chip's flash, we might need to move the RAMBASE up to
;; 0x40000200 to avoid overwriting the IAP work area.
;; Note, the Olimex LPC L2294 board has off-chip RAM starting at
;; 0x81000000, but we use only on-chip RAM for the Forth.
;;; Macros
;;;; nxt -- move from one word to the next, following IPTR
.macro nxt ; jump to the inner interpreter
b nxtTab
.endm
;;;; dpush -- push a register to the data stack
.macro dpush reg
str \reg, [DSTK, #-4]!
.endm
;;;; rpush -- push a register to the return stack
.macro rpush reg
str \reg, [RSTK, #-4]!
.endm
;;;; dpop -- pop data stack into a register
.macro dpop reg
ldr \reg, [DSTK], #4
.endm
;;;; rpop -- pop return stack into a register
.macro rpop reg
ldr \reg, [RSTK], #4
.endm
;;;; dup
; -- we keep tos in a register
.macro dup
dpush TOS
.endm
;;;; drop
.macro drop
dpop TOS
.endm
;;;; nip -- Pop a 32-bit data element from the data stack into r1
.macro nip
dpop r1
.endm
;;;; push -- Pop a 32-bit data element from the data stack and
; push it to the return stack.
.macro push
rpush TOS
drop ; refill TOS
.endm
;;;; pop -- Pop a 32-bit data element from the return stack and
; push it to the data stack.
.macro pop
dup ; make room on data stack
rpop TOS ; pop return stack into TOS
.endm
;;;; lit -- push literal to data stack
.macro lit num
dup ; make room on data stack
ldr TOS, = \num ; copy literal into TOS
.endm
;;;; Multiple registers to/from stack
; The stm and ldm instructions let us move several items to or
; from the data or return stack in one instruction.
;;;; dpop2 -- pop 2 items from the in-memory data stack into r1 and r2
; The lowest-numbered register gets the value from the lowest
; address. b is on top of the in-memory data stack (because
; c is cached in the register TOS) so b is at the lowest
; address and so gets loaded into r1. ( ... a b c - ... c)
; so afterwards, r2=a, r1=b, r0=c Considering removing this
; macro since it is used in only one place (the definition of
; -ROT).
.macro dpop2
ldmfd DSTK!, {r1, r2}
.endm
;;; Code
.code 32
.section .text
.global vectors
.org 0
.global _start
;;; Vectors
vectors:
b _start ; reset (0x00)
b . ; undefined instruction (0x04)
b . ; software interrupt (SWI) (0x08)
b . ; prefetch abort (0x0C)
b . ; data abort (0x10)
b . ; checksum (0x14)
b . ; IRQ (0x18)
b . ; FIRQ (0x1C)
.section .text
_start:
b init ; jump around system parameter area
parms:
;; These system parameters are filled in by the compiler
;; so the '.word n' is just a place holder for now, showing
;; its entry number. Compiler will replace it with actual value.
ptokens: .word 0 ; holds address of flash token table
; If a hard-coded token table is defined in this
; assembly file (rather than generated by the
; compiler later) (i.e., for testing), then
; be sure to plug in the start of the token table
; here. Otherwise, the compiler will fill it in.
prtokens: .word 1 ; holds address of RAM token table
pfreeram: .word 2 ; holds address of start of free RAM, used for initializing 'h'
;;; Include the processor-specific (and perhaps board-specific) equates
; and subroutines. Note, code at _start: above jumps around whatever
; is included here (_start: jumps to 'init:'). This means that
; executable code in the included file must be in subroutines that will
; be called from the main file.
.ifdef lpc2106
.include "custom-lpc2106.s"
.endif
.ifdef lpc23xx
.include "custom-lpc23xx.s"
.endif
.ifdef lpc2103
.include "custom-lpc2103.s"
.endif
.ifdef lpc2294
.include "custom-lpc2294.s"
.endif
;;; init
init:
; set up various stacks
; do any special register customizations
;;;; initialize hardware stack
ldr sp, = mstk
;;;; Initialize Clocks, PLL, Clock Dividers, etc
bl setup_clocks
;;;; Timer1
; Start timer needed for MS
ldr r6, = T1TCR
mov r0, #1
str r0, [r6]
;;;; Set up I/O ports and peripherals
bl setup_ports
;;;; Initialize the Forth data and return stacks and TCBs for all the tasks
;; Fill in LINK field for foreground task so it links to itself. Thus,
;; the other two tasks are not in the active task list.
initTasks:
;; background task1
ldr DSTK, = dstk1
ldr RSTK, = rstk1
ldr UP, = task1 ; User Pointer holds address of task control block
str DSTK, [UP, #0x18] ; Initialize SP0 slot in TCB
str RSTK, [UP, #0x1c] ; Initialize RP0 slot in TCB
;; background task2
ldr DSTK, = dstk2
ldr RSTK, = rstk2
ldr UP, = task2 ; User Pointer holds address of task control block
str DSTK, [UP, #0x18] ; Initialize SP0 slot in TCB
str RSTK, [UP, #0x1c] ; Initialize RP0 slot in TCB
;; foreground task
ldr DSTK, = dstk
ldr RSTK, = rstk
ldr UP, = foreground ; User Pointer holds address of task control block
str UP, [UP] ; Initial LINK slot in TCB so the active task list contains
; just the foreground task. The foreground task back to
; itself when PAUSE executes.
str DSTK, [UP, #0x18] ; Initialize SP0 slot in TCB
str RSTK, [UP, #0x1c] ; Initialize RP0 slot in TCB
;;;; Example
;; ;;; For testing, make it hang until the button is pressed. This gives
;; ;; us a chance to start the JTAG and OpenOCD. This is for the LPC2103.
;; ;; be sure to remove it for real use.
;; button:
;; ldr r1, = FIO0PIN
;; 1:
;; ldr r2, [r1] ; read the Port 0
;; tst r2, #0x8000 ; Wait for button to be pressed.
;; bne 1b ; (pin P0.15 is low when button is pressed)
;;;; Now flash LED several times
;; If you have one of the supported boards:
; Olimex LPC2106 board with the LED connected to GPIO pin 7,
; Olimex LPC P-2378 board with an LED connected to PORT1 pin 19,
; Olimex LPC P-2103 board with an LED connected to PORT0 pin 26,
; Olimex LPC L-2294 board with an LED connected to PORT1 pin 23,
;; This following blinks the LED. This can be a comfort when you
;; are bringing up a board or to remind you about opening the BSL
;; jumper after programming the flash. Comment it out if you
;; prefer.
;; If you have a different board with an LED on a different pin,
;; change the custom include files and edit ledOnSub and
;; ledOffSub appropriately.
blink:
mov r3, #6 ; how many times to blink the LED
1:
bl ledOnSub
bl delayQ
bl ledOffSub
bl delayQ
subs r3, r3, #1
bne 1b
;;;; Send greeting out the serial port for testing. Note, when
;; running riscy.tcl, these characters are silently thrown away
;; (because it is waiting for a properly formatted message).
;; To test your serial port, start a serial terminal program
;; such as minicom, reset the target board, see if "hi" shows
;; up in the terminal.
greet:
; Write several characters to the serial port
bl crlf
mov r1, #'h
bl tx
mov r1, #'i
bl tx
bl crlf
b runboot
;;; label EXECUTE is Forth word EXECUTE ( a -)
EXECUTE:
;; This takes the full 32-bit address of a Forth word to be executed.
;; This is the same form of address the ' (tick) returns.
mov r1, TOS
drop ; (refill TOS)
;; Handle Primitive
;; if the address in TOS is less than # prim_cutoff, then the word
;; is a primitive, so we just jump to it.
ldr TEMPREG, = prim_cutoff
#cmp TOS, # prim_cutoff ; low address means this is a primitive, so
cmp r1, TEMPREG ; low address means this is a primitive, so
movlo pc, r1 ; jump to it
; Otherwise, handle nesting down to called high-level word
str IPTR, [RSTK, #-4]! ; push IPTR to return stack
mov IPTR, r1 ; load IPTR with address of word to be executed
;; fall through to nxtTab
;;; Virtual machine primitives
nxtTab:
; High-level Forth words use 16-bit tokens to represent calls
; to subwords. The token is comprised of several flags plus a
; table entry number.
; (bits 15..3) Entry number (index)
; multiply it by the item width in bytes then
; add it to the start of the table.
;
; Flags:
; bit2 Primitive/high-level flag.
; If true, this token represents a primitive, so
; jump to it. Otherwise, it represents a high-level
; Forth word, so do the work to nest down (sometimes
; called "docol"), then jump back to nxtTab.
;
; bit1 RAM/flash flag
; If true, use RAM table. Else use flash table.
;
; bit0 Exit flag.
; If true, pop rstk into IPTR to effect a jump instead
; of a call, thus merging an exit (";") into the word.
; Since we look up the full 32-bit address, we do not need to
; align high-level Forth words to a 32-bit boundary. A 16-bit
; boundary suffices.
; We store the 13 bits representing the token table entry
; number in the most significant 13 bits of an unsigned
; half-word.
; We also need one bit of information to tell whether the
; destination Forth word is a primitive (in which case we wish
; to jump to it) or a high-level word (in which case we wish
; to save the current value of IPTR and reload IPTR with the
; address of the destination word).
ldrh W, [IPTR], #2 ; read unsigned half-word then bump IPTR by 2
nxtexec: ; convenient entry point for use by EXECUTE
; 16-bit token is now is W
;; handle Exit flag
movs W, W, lsr #1 ; set C flag from original bit0 (i.e., the jump flag)
ldrcs IPTR, [RSTK], #4 ; pop rstack into IPTR ("unnest") only if jump=1
;; handle RAM/flash table flag
movs W, W, lsr #1 ; set C flag from original bit1
;; Then load the address of the correct token table into TEMPREG
ldrcc TEMPREG, ptokens ; Load temporary register with base
ldrcs TEMPREG, prtokens ; address of the chosen token table.
; If RAM flag was set, use the RAM
; table. Otherwise, use the flash table.
;; Remember the Primitive flag in C
movs W, W, lsr #1 ; set C flag from original bit2
; we will test this flag after looking up
; the token's address in the token table.
;; Look up word's address in token table. The 13 bits of the
;; token number were originally in bits 15..3 of W but are
;; now in bits 12..0 because we shifted W right 3 bits. Now,
;; shift W left 2 bits to convert to a byte offset then add
;; offset to start of table, leaving address of entry table
;; item in TEMPREG.
add TEMPREG, TEMPREG, W, lsl #2
;; Handle Primitive
ldrcs pc, [TEMPREG] ; jump to the primitive if primitive flag
; was set
; Otherwise, handle nesting down to called high-level word
str IPTR, [RSTK, #-4]! ; push IPTR to return stack
ldr IPTR, [TEMPREG] ; load IPTR with address of new word
b nxtTab ; jump back to nxt to begin handling new word.
.ltorg ; force dumping of literal pool
;;; label EXIT is Forth word EXIT ( -)
EXIT:
;; unnest by popping return stack into IPTR
ldr IPTR, [RSTK], #4 ; pop rstack into IPTR
nxt
;;; label NOP is Forth word NOP ( -)
;;; NOP ( -) This serves mainly the purpose of safely occupying
; 16-bits in a high-level word list, e.g., for aligning a
; label onto a 4-byte boundary.
NOP:
nxt
;;; In Application Programming
;; ;;; label IAP is Forth word IAP ( a -)
;; ; "In Application Programming"
;; ; a is the address of the 5-word table in RAM used both for
;; ; passing parameters to the In Application Programming routine
;; ; and for receiving the result. See the Philips LPC2106 manual
;; ; for values for parameters and results.
;; ; WARNING: Interrupts should be disabled by caller.
;; ; WARNING: Do not use this if RAM is started at 0x40000000 rather than
;; ; at 0x40000200.
;; IAP:
;; mov r1, TOS ; 5-word table address used for both in and out
;; bl jump_to_IAP
;; drop
;; nxt
;; jump_to_IAP:
;; ldr r2, = 0x7ffffff1 ; entry point (odd because it is Thumb code)
;; bx r2 ; call IAP
;;; Stack primitives
;;;; label DUP is Forth word DUP ( x - x x)
DUP:
dup
nxt
;;;; label SWAP is Forth word SWAP ( a b - b a)
SWAP:
swp TOS, TOS, [DSTK]
nxt
;;;; label DROP is Forth word DROP ( a -)
DROP:
drop
nxt
;;;; label twoDROP is Forth word 2DROP ( a b -)
twoDROP:
add DSTK, DSTK, #4
drop
nxt
;;;; label threeDROP is Forth word 3DROP ( a b c -)
threeDROP:
add DSTK, DSTK, #8
drop
nxt
;;;; label fourDROP is Forth word 4DROP ( a b c d -)
fourDROP:
add DSTK, DSTK, #12
drop
nxt
;;;; label NIP is Forth word NIP ( a b - b)
NIP: ; ( a b - b)
nip
nxt
;;;; label PUSH is Forth word PUSH ( n -)
PUSH:
push
nxt
;;;; label twoPUSH is Forth word 2PUSH ( n n -)
twoPUSH:
push
push
nxt
;;;; label POP is Forth word POP ( - n)
POP:
pop
nxt
;;;; label twoPOP is Forth word 2POP ( - n n)
twoPOP:
pop
pop
nxt
;;;; label Rfetch is Forth word R@ ( - a)
; In this implementation, because we use a dedicated loop count register,
; R@ and I are not the same word. R@ accesses the return stack but
; I accesses the loop count register.
Rfetch:
dup
ldr TOS, [RSTK]
nxt
;;;; label twoRfetch is Forth word 2R@ ( - a b)
twoRfetch:
dup
ldr TOS, [RSTK]
dup
ldr TOS, [RSTK, #4]
nxt
;;;; label I is Forth word I ( - a)
; In this implementation, because we use a dedicated loop count register,
; R@ and I are not the same word. R@ accesses the return stack but
; I accesses the loop count register.
I:
dup
mov TOS, RLOOP
nxt
;;;; label ROT is Forth word ROT ( a b c - b c a)
ROT:
ldmfd DSTK!, {r1, r2} ; copy a to r2, copy b to r1, tos has c
stmfd DSTK!, {r0, r1} ; copy b and c back to physical stack
mov TOS, r2 ; copy a to TOS
nxt
;;;; label SPfetch is Forth word SP@ ( - dataStkPtr)
SPfetch:
mov r1, DSTK
dup
mov TOS, r1
nxt
;;;; label RPfetch is Forth word RP@ ( - returnStkPtr)
RPfetch:
dup
mov TOS, RSTK
nxt
;;;; label OVER is Forth word OVER ( a b - a b a)
OVER:
dup
ldr TOS, [DSTK, #4] ; r0 := a
nxt
;;;; label twoOVER is Forth word 2OVER ( a b c d - a b c d a b)
twoOVER:
dup ; ( a b c d d)
ldr TOS, [DSTK, #12] ; r0 := a, ( a b c d a )
dup ; ( a b c d a a)
ldr TOS, [DSTK, #12] ; r0 := b ( a b c d a b)
nxt
;;;; label minusROT is Forth word -ROT ( a b c - c a b)
minusROT:
dpop2 ; r2=a, r1=b, r0=c
dup ; push c to physical stack
dpush r2 ; then push a to physical stack
mov TOS, r1 ; and put b into TOS
nxt
;;;; label twoSWAP is Forth word 2SWAP ( a b c d - c d a b)
twoSWAP:
ldmfd DSTK!, {r1-r3} ; r3=a, r2=b, r1=c, r0=d
stmfd DSTK!, {r0-r1} ; push c, push d
dpush r3 ; push a
mov TOS, r2 ; put b into TOS
nxt
;;;; label twoDUP is Forth word 2DUP ( a b - a b a b)
twoDUP:
ldr r1, [DSTK] ; r1 := a, TOS (i.e., R0) holds b
dpush TOS
dpush r1
nxt
;;;; label threeDUP is Forth word 3DUP ( a b c - a b c a b c)
threeDUP:
ldr r1, [DSTK] ; r1 := b, TOS holds c
ldr r2, [DSTK, #4] ; r2 := a
dpush TOS
dpush r2
dpush r1
nxt
;;;; label fourDUP is Forth word 4DUP ( a b c d - a b c d a b c d)
fourDUP:
ldr r1, [DSTK] ; r1 := c, TOS holds d
ldr r2, [DSTK, #4] ; r2 := b
ldr r3, [DSTK, #8] ; r3 := a
dpush TOS
dpush r3
dpush r2
dpush r1
nxt
;;;; label qDUP is Forth word ?DUP ( 0 - 0) or ( nonZero - nonZero nonZero)
qDUP:
tst TOS, TOS
strne TOS, [DSTK, #-4]! ; only if TOS is non-zero do we dup it
nxt
;;; Data access
;;;; label Cfetch is Forth word C@ ( a - c)
Cfetch:
ldrb TOS, [TOS]
nxt
;;;; label fetch is Forth word @ ( a - u)
fetch:
ldr TOS, [TOS]
nxt
;;;; label twoFetch is Forth word 2@ ( a - low32bits high32bits)
twoFetch:
mov r1, TOS ; put a into r1
ldr TOS, [r1, #4] ; get low32 bits
dup
ldr TOS, [r1] ; get high32 bits
nxt
;;;; label Cstore is Forth word C! ( c a -)
Cstore:
dpop r1
strb r1, [TOS]
drop ; refill TOS
nxt
;;;; label store is Forth word ! ( u a -)
store:
dpop r1
str r1, [TOS]
drop ; refill TOS
nxt
;;;; label twoStore is Forth word 2! ( low32 high32 a -)
twoStore:
dpop r1
str r1, [TOS]
dpop r1
str r1, [TOS, #4]
drop ; refill TOS
nxt
;;;; label Wfetch is Forth word W@ ( a - unsigned16bits) (little endian)
Wfetch:
ldrh TOS, [TOS]
nxt
;;;; label Wstore is Forth word W! ( unsigned16bits a -) (little endian)
Wstore:
dpop r1
strh r1, [TOS]
drop ; refill TOS
nxt
;;; I/O
;;;; Note this section contains SUBROUTINES rather than
;; Forth primitives, i.e., these end with 'mov pc, lr' rather
;; than with 'nxt'.
;;;; LED
;; LED on and off SUBROUTINES reside in the custom-*.asm files.
;; They must be defined there even if as no-ops.
;; Subroutine to kill around one second with a 14.745 MHz clock
;; running without PLL. Used just for blinking the LED.
delay:
ldr r1, = 0x00080000 ; delay for about one second at 14.745 MHz
delay1:
subs r1, r1, #1
bne delay1
mov pc, lr
;; Subroutine to kill around one 1/4 second with a 14.745 MHz clock
;; running without PLL. Used just for blinking the LED.
delayQ:
ldr r1, = 0x00020000 ; delay for about 1/4 second at 14.745 MHz
b delay1
fast:
; flash LED once
str lr, [sp, #-4]! ; push return address onto machine stack
bl ledOnSub
bl delayQ
bl ledOffSub
bl delayQ
ldr pc, [sp], #4 ; rts
slow:
; flash LED once
str lr, [sp, #-4]! ; push return address onto machine stack
bl ledOnSub
bl delay
bl ledOffSub
bl delay
ldr pc, [sp], #4 ; rts
;;;; Serial port subroutines
; Subroutine to send the character in r1 to the serial port (uart0).
; Character is still in r1 upon return. Caller must see that r6 holds
; U0BASE else we must do it here.
tx:
ldr r6, = U0BASE
tdre:
ldrb r2, [r6, # ULSR]
tst r2, #0x20 ; Wait for transmit data register empty flag
beq tdre ; to go true.
strb r1, [r6, # UDATA] ; Write to serial port
mov pc, lr
; Subroutine to send the character in r1 to the 2nd serial port (uart1)
; Caller must see that r6 holds U1BASE else we must do it here.
tx1:
ldr r6, = U1BASE
b tdre
; Subroutine to read one character from the first serial port (uart0).
rx:
ldr r6, = U0BASE
rdr:
ldrb r1, [r6, # ULSR]
tst r1, #0x01 ; Wait for receive data ready flag
beq rdr ; to go true.
ldrb r1, [r6, # UDATA] ; Read character from serial port into r1
mov pc, lr ; no, don't echo it, just return
;; ; Subroutine to read one character from the second serial port (uart1).
;; rx1:
;; ldr r6, = U1BASE
;; b rdr
.ltorg ; force dumping of literal pool
; Subroutine to send CRLF to serial port -- used only by 'greet'
crlf:
str lr, [sp, #-4]! ; push return address onto machine stack
mov r1, #0x0d ; carriage return
bl tx
mov r1, #0x0a ; line feed
bl tx
ldr pc, [sp], #4 ; rts
.ltorg ; force dumping of literal pool
;;;; Serial port Forth words
;;;; label parenSEROUT is Forth word (SEROUT ( c -)
parenSEROUT:
mov r1, r0
bl tx
drop
nxt
;;;; label parenSEROUT1 is Forth word (SEROUT1 ( c -)
parenSEROUT1:
mov r1, r0
bl tx1
drop
nxt
;;;; label parenSERINQ is Forth word (SERIN? ( - f)
; Answer true if a character is waiting in the serial port
parenSERINQ:
dup
ldr r6, = U0BASE
ldrb TOS, [r6, # ULSR] ; move status register into TOS
and TOS, TOS, #0x01 ; isolate data ready flag in lsbit
rsb TOS, TOS, #0 ; i.e., 0 - TOS turns lsb into -1 or 0
nxt
;;;; label parenSERIN is Forth word (SERIN ( - c)
parenSERIN:
dup
bl rx
mov r0, r1
nxt
;;; Constants
;; Note, we should not define constants that might be needed by LOAD when
;; building a kernel image. -1 and -2 should be safe, though.
;;;; label minus1 is Forth word -1 ( - -1)
minus1:
dup
mov TOS, # -1
nxt
;;;; label minus2 is Forth word -2 ( - -2)
minus2:
dup
mov TOS, # -2
nxt
;;; System parameters
;; These words return certain clock-related settings. They come
;; directly or indirectly from equates in the custom-*.asm file.
;; Note that they are constants. If you change the CPU on the fly,
;; the values these words return will no longer be correct.
.equ MSTICKS, PCLK / 1000
;;;; label ticksPerMS is Forth word TICKS/MS ( - u)
ticksPerMS:
; This depends on PCLK which depends on PCLKDIVISOR and CCLK
; Beware that if PCLK is changed on the fly, this will
; no longer answer the correct value.
dup
ldr TOS, = MSTICKS
nxt
;;;; label peripheralClock is Forth word PCLK ( - u)
peripheralClock:
dup
ldr TOS, = PCLK
nxt
;;;; label cpuClock is Forth word CCLK ( - u)
cpuClock:
dup
ldr TOS, = CCLK
nxt
;;;; label busDivisor is Forth word BUS-DIVISOR ( - u)
; ratio of CCLK to PCLK so must be either 1, 2, or 4
busDivisor:
dup
;ldr TOS, = VPBDIVISOR
ldr TOS, = PCLKDIVISOR
nxt
;;;; label spiDivisor is Forth word SPIDIVISOR ( - u)
spiDivisor:
dup
ldr TOS, = SPIDIVISOR
nxt
;;;; label spiClock is Forth word SPICLK ( - u)
spiClock:
dup
ldr TOS, = SPICLK
nxt
.ltorg ; force dumping of literal pool
;;; Math
;;;; label Mplus is Forth word M+ ( a b - a+b c)
; Probably no use for this since D+ is in code, but keep it now. I
; got the idea for this word from Bill Muench.
Mplus:
ldr r1, [DSTK] ; r1 is a, r0 is b
adds TOS, TOS, r1 ; add a and b and set carry flag if carry out
str TOS, [DSTK] ; SOS gets a+b
eor TOS, TOS, TOS ; clear r0
adc TOS, TOS, TOS ; set r0 to 1 if there was a carry is
; there a way to do above two
; instructions in one?
nxt
;;;; label plus is Forth word + ( a b - a+b)
plus:
dpop r1
add TOS, TOS, r1
nxt
;;;; label Dplus is Forth word D+ ( a b c d - a+c b+d+carry) PUSH SWAP PUSH M+ POP + POP + ; ")
Dplus:
ldmfd DSTK!, {r1, r2, r3}
; r1=a, r2=b, r3=c, TOS=d
adds r1, r1, r3 ; r1=a+c
dpush r1 ; push lsword
adc TOS, TOS, r2 ; TOS=b+d+c (msword)
nxt
;;;; label minus is Forth word - ( a b - a-b)
minus:
dpop r1 ; r1=a, TOS=b
sub TOS, r1, TOS ; TOS=a-b
nxt
;;;; label twoTimes is Forth word 2* ( a - 2a)
twoTimes:
mov TOS, TOS, lsl #1
nxt
;;;; label twoSlash is Forth word 2/ (a - a/2) shift right one place, signed
twoSlash:
mov TOS, TOS, asr #1
nxt
;;;; label U2slash is Forth word U2/ ( a - a/2) shift right one place, unsigned
U2slash:
mov TOS, TOS, lsr #1
nxt
;;;; label twofiftysixslash is Forth word 256/ ( u - u/256)
twofiftysixslash:
;; unsigned division by 256
mov TOS, TOS, lsr #8
nxt
;;;; label SPLIT is Forth word SPLIT ( aaaabbbb - 0000bbbb 0000aaaa) convert 32bit word to two half words
SPLIT:
mov r1, TOS
ldr r2, = 0xffff
and r1, r1, r2
dpush r1
mov TOS, TOS, lsr #16
nxt
;;;; label plusStore is Forth word +! ( n a -)
plusStore:
dpop r1
ldr r2, [TOS]
add r1, r1, r2
str r1, [TOS]
drop
nxt
;;;; label plusUNDER is Forth word +UNDER ( a b n - a+n b)
plusUNDER:
ldr r1, [DSTK, #4] ; r1=a, TOS=n
add r1, r1, TOS ; r1=a+n
dpop TOS ; TOS=b
str r1, [DSTK] ; push a+n
nxt
;;;; label NEGATE is Forth word NEGATE ( n - -n)
NEGATE:
rsb TOS, TOS, #0 ; TOS := 0 - TOS (i.e., two's complement)
nxt
;;;; label onePlus is Forth word 1+ ( n - n+1)
onePlus:
add TOS, TOS, #1
nxt