-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathriscy.tcl
executable file
·2599 lines (2180 loc) · 79.7 KB
/
riscy.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/local/bin/tclkit
# Riscy Pygness
# Copyright (c) 2004-2010 Frank C. Sergeant
# Freely available under a modified BSD/MIT/X license.
# Details at http://pygmy.utoh.org/license20040130.txt.
# See the files COPYING and license20040130.txt in the distribution.
# *****************************************************************
# *
# See the Riscy Pygness User Manual for documentation, especially *
# about variables you may need to change for your environment. *
# *
# *****************************************************************
# ******************** user-editable items ************************
# *
set isUnix1 1
set ::debug 0
#set ::debug 1
set ::serialPort1 "/dev/ttyS0"
# note, serial port can be overridden on the command line with the
# -port option (e.g., ./riscy.tcl ... -port /dev/ttyS0)
set ::baudrate 38400
proc filenameFromBlockNumber {num} {
if {[between $num 0 999]} {return kernel.fth}
# Edit from here down to change which block numbers are mapped to
# which filenames.
if {[between $num 1000 1999]} {return s1.fth}
if {[between $num 2000 2999]} {return s2.fth}
abort "bad block number: $num"
}
# * *
# ***************** end of user-editable items ********************
proc DEBUG {} {
if {$::debug} {
set ::debug 0
} else {
set ::debug 1
}
}
## Source other Tcl files and discover platform
set scriptDir [file dirname [info script]]
# General utility functions
source [file join $scriptDir util.tcl]
proc aligned? {{divisor 4}} {
# Answer true if ::HH is already on an address evenly divisible by DIVISOR
expr ($::HH % $divisor) == 0
}
proc alignHalfWord {} {
# If we are at an odd address, comma in a zero to skip one byte. This
# is needed for strings.
if {! [aligned? 2]} {
ccomma 0
}
}
proc align {} {
# If we are not aligned on a full-word boundary, then compile a NOP
# to use up 2 bytes so we will be aligned on a full-word boundary.
# Caller should be sure we are compiling.
if {! [aligned?]} {
compile NOP
}
}
proc misalign {} {
# If we are aligned on a full-word boundary, then compile a NOP to
# use up 2 bytes so we will no longer be aligned on a full-word
# boundary. This is needed when compiling a 32-bit literal. By
# making sure the lit32 token starts misaligned, the following
# in-line 32 bit number will be on a full-word boundary. Caller
# should be sure we are compiling.
dputs "misalign -- at start, ::HH is [asHex $::HH]"
if {[aligned?]} {
compile NOP
}
dputs "misalign -- afterwards, ::HH is [asHex $::HH]"
}
proc alignRam {} {
# Increment ::ram, if necessary, so it lies on a full word boundary
dputs "alignRam -- at start, ::ram is [asHex $::ram]"
set mod4 [expr $::ram % 4]
if {$mod4} {
incr ::ram [expr 4 - $mod4]
}
dputs "alignRam -- afterwards, ::ram is [asHex $::ram]"
}
## TIB
# ::TIB holds the remaining string (or block) currently being
# interpreted, as 'word' collects and removes a word from the
# beginning of the string.
set ::TIB ""
# Keep a target image on the host. We compile into the target image
# and then send the new data to the target to extend its dictionary.
# Note, lines to be interpreted and lines to be compiled are both
# compiled. The difference is that only the latter extends the
# dictionary permanently. When interpreting, the top of the
# dictionary on up is used as a temporary buffer. We need to keep
# *two* pointers to the top of dictionary. The permanent one is ::H
# and the temporary one is ::HH. 'comma' uses ::HH, which advances
# :HH byte by byte. If we are compiling rather interpreting, after
# the new part of the dictionary is sent to the target, ::H is set to
# ::HH to make the extension permanent.
proc interpreting? {} {
return [string equal $::mode interpreting]
}
proc compiling? {} {
return [expr ! [interpreting?]]
}
# push and pop are used for saving and restoring the string being
# interpreted (i.e., ::TIB) for LOAD, and also for storing numbers and
# strings sent to the host by the target.
proc push {val} {
dputs "about to push '$val' to local stack"
lappend ::stack $val
}
proc pop {} {
set val [lindex $::stack end]
set ::stack [lrange $::stack 0 end-1]
return $val
}
proc popn {} {
# Pop an item from the host's local stack and guarantee it is a integer.
set val [pop]
if {[string is integer -strict $val]} {
return $val
} else {
abort "popn -- expected integer but got '$val'"
}
}
proc swap {} {
# Swap the top two values on the host's stack
set tos [pop]
set sos [pop]
push $tos
push $sos
}
proc word {{token " "}} {
# Return first token remaining in the TIB string, set TIB to the
# remaining string. Eat leading tokens.
while {[string index $::TIB 0] == $token} {
set ::TIB [string range $::TIB 1 end]
}
set pos [string first $token $::TIB]
if {$pos < 0} {
set w $::TIB
set ::TIB ""
return $w
} else {
set w [string range $::TIB 0 [expr $pos - 1]]
set ::TIB [string range $::TIB [expr $pos + 1] end]
return $w
}
}
proc quit {} {
# This is the host's version of the typical 'get some input and
# interpret it' loop which is common to Forths.
set ::bye 0
puts -nonewline "> "
flush stdout
fileevent stdin readable {
if {[catch {
processLine
} result]} {
puts $result
puts -nonewline "> "
flush stdout
}
}
vwait ::bye
}
proc processLine {} {
# This is one pass of the 'get some input and interpret it' loop.
# It is invoked by the fileevent system when the user has
# finished entering an entire line. See the procedure 'quit'.
flush stdout
if {[eof stdin]} {
# In normal use, this should not happen.
# Do nothing for now. Otherwise, should
# we set ::bye to 1?
} elseif {[gets stdin line] < 0} {
# Only a partial line was available, so do nothing yet
} else {
# We got a full line, so interpret it
set ::mode interpreting
interpret $line
if {$::bye} {
puts "Exiting Forth"
} else {
puts " ok"
puts -nonewline "> "
}
}
flush stdout
}
proc cold {} {
# COLD is immediate word and is run only when interpreting. It
# causes the host to reset its ::H and ::HH after asking the
# target to run its COLD.
# The result of running COLD is a complete reloading and rebooting
# of the Forth kernel. In the following, we ask the target to
# reset first, then we reset the host.
if {[compiling?]} {
abort "attempting to run COLD in compile mode."
}
puts " restarting ..."
compile COLD
sendToTarget
startUp
}
proc interpret {str} {
# Process a string, one word at a time, until the string is empty.
# For each word, if it is "immediate", process it on the
# host. Otherwise, see if it is defined as a Forth word on the
# target, if so, compile it on the host (so it can be sent to the
# target for either "interpreting" or "compiling"). Failing that,
# see if we can treat it as a number of some sort and, if so,
# compile it as a literal.
dputs "Starting to interpret \[$str\]"
# Replace any control character, including CR and LF with a space.
# This lets an editor pass multiple lines to be interpreted.
set fixedStr [regsub -all {[[:cntrl:]]} $str { }]
dputs "Fixed string is |$fixedStr|"
set ::TIB $fixedStr
set ::mode interpreting
while {1} {
set w [word]
if {[empty $w]} {
# We reached the end of the string we were interpreting, so
# send what we have compiled to the target
sendToTarget
break
}
if {[compiledByteCount] > 250} {
# Packets have a 1-byte length field, so when we get close
# to the maximum length, go ahead and send what we have
# compiled so far.
sendToTarget
}
if {! [immediate $w]} {
# It was not immediate so try looking it up in the target
# dictionary
dputs " --interpret - $w was not immediate"
if {! [forth $w]} {
# It was not in target dictionary so try looking it up
# in the table of constants
dputs " --interpret - 'forth $w' returned false"
if {! [constant $w]} {
# It was not a constant, so see if it is number in the current base
number $w
# Number will abort if it is not successful
}
}
}
}
}
## Immediate words
proc immediate {w} {
# Check to see if the word w is immediate. If so, execute it and
# return true. If not, return false. An "immediate" word is a
# word that is executed on the host rather than being compiled to
# be sent to the target. We might wish to change the 'switch' to
# a separate array that maps the immediate word name to the
# procedure.
# Note we have made the "dot" words immediate so we can apply cut
# to them. Without that, we get an error illustrated by $200 ?
# on a line by itself (which does not occur if ? is on a line by
# itself). Maybe, though, we should just run 'cut' after any word
# that is immediate. It won't hurt anything where it isn't needed.
# Put it at the end just before sending the result
set result 1
switch -- $w {
BYE {set ::bye 1}
{(} {comment}
":" {colon}
ALLOT {ALLOT}
VARIABLE {VARIABLE}
2VARIABLE {2VARIABLE}
CREATE {CREATE}
TABLE {TABLE}
HERE {number $::H}
";" {semicolon}
";;" {doublesemi}
{"} quote
{."} dotquote
{ABORT"} abortquote
{'} tick
{C,} C,
{,} ,
PUSH {compile PUSH}
POP {compile POP}
R@ {compile R@}
I {compile I}
IF {doIF}
THEN {doTHEN}
ELSE {doELSE}
BEGIN {doBEGIN}
AGAIN {doAGAIN}
UNTIL {doUNTIL}
WHILE {doWHILE}
REPEAT {doREPEAT}
FOR {doFOR}
NEXT {doNEXT}
LOAD {LOAD}
THRU {thru}
WORDS {words}
TCL {puts [eval [word 0]]}
COLD {cold}
RESET {cold}
SAVE-IMAGE {SAVE-IMAGE}
DEBUG {DEBUG}
{.} {compile .}
{.R} {compile .R}
{U.} {compile U.}
{U.R} {compile U.R}
default {
set result 0
}
}
if {$result} {
dputs "running 'cut' after immediate word $w"
cut
}
return $result
}
proc doBEGIN {} {
# Mark the address of the following instruction by pushing the
# address to the host's data stack.
push $::HH
cut
}
proc doIF {} {
# Compile a zerobranch
cut
compile 0branch
# Remember where the destination address will go, once we know it
# (doTHEN fills it in).
doBEGIN
# Fill in a dummy destination address for now.
comma 0
}
proc doTHEN {} {
# Fill in the offset to the destination for a previous 0branch or
# branch instruction. The local stack holds the address of the
# slot. Note, this resolves a forward branch of some sort (either
# 0branch or branch) where the destination is not known at the
# time the forward branch is compiled. See doAGAIN for a word
# that compiles a backward branch (where the destination *is*
# known).
set slotAddr [popn]
store [expr $::HH - (2 + $slotAddr)] $slotAddr
cut
}
proc doELSE {} {
cut
# Compile a branch
compile branch
# Remember where the offset to the destination address will go,
# once we know it (doTHEN fills it in).
doBEGIN
# Fill in a dummy 16-bit offset as a place holder.
comma 0
# Host stack contains address of 0branch slot followed by the
# address of branch slot. Swap them so that doTHEN can fill in
# the 0branch destination. Later, another doTHEN will fill in the
# branch destination.
swap
doTHEN
}
proc doAGAIN {} {
# Lay down the 2-byte token for an unconditional branch followed
# by the 2-byte offset to be added to IP. For example, in
#
# BEGIN BLINK BLINK AGAIN
#
# when branch begins to execute, IP will be pointing just past the
# two bytes holding the offset, so the offset will be -8.
cut
compile branch
comma [expr [popn] - (2 + $::HH)]
cut
}
proc doUNTIL {} {
# Whenever we do something like 'compile 0branch' we cut first so
# the 0branch will not merge with a previously compiled word and
# then we cut again so whatever follows it will not be merged with
# the 0branch.
cut
compile 0branch
comma [expr [popn] - (2 + $::HH)]
cut
}
proc doWHILE {} {
doIF
swap
}
proc doREPEAT {} {
doAGAIN
doTHEN
}
proc doFOR {} {
cut
compile for
# Save address of following slot that will hold offset to 'next'.
doBEGIN
# Lay down a 16-bit place holder for the offset to 'next'.
# It will be filled in by doNEXT.
comma 0
# Save address of loop body. It will used by doNEXT.
doBEGIN
# the local stack now holds two address but the earlier one will
# need to be resolved first, so swap them
swap
}
proc doNEXT {} {
# When 'next' executes, the loop count is on the return stack and
# the 16-bit offset to the loop's body follows the call to 'next'
# in line. 'next' first checks to see if the count is zero. If
# so, it cleans up and skips over the in-line address. Else, it
# decrements the loop count and jumps back to the body of the
# loop.
# First, fill in the 16-bit slot that holds the offset to 'next'
doTHEN
# Then, lay down a call to 'next'
compile next
cut
# Finally, lay down the 16-bit offset back to the loop's body
set bodyAddress [popn]
comma [expr $bodyAddress - (2 + $::HH)]
}
proc semicolon {} {
# When a semicolon is found, we would like to change the preceding
# call into a jump (i.e., perform "tail-call optimization").
# However, there are several circumstances where this must not be
# done. In those cases, we lay down an explicit exit. We will
# use the function 'cut' to mark those cases where we need an
# explicit exit. The variable ::last holds the address of that
# last instruction.
# The variable ::last holds the address where the previously
# compiled instruction starts. If 'cut' was used, then ::last
# will be zero and so we will lay down an explicit exit instead.
if {$::last == 0} {
# Here we must not set the exit bit of the preceding
# instruction, so just compile 'exit'
dputs "Compiling semicolon as an explicit 'exit'"
compile EXIT
} else {
dputs "Compiling semicolon by changing previous instruction to a jump, i.e., by setting its exit flag."
# Here, ::last holds the address of the previous instruction.
# We wish to set its 'exit' bit, bit0, (by ORing a 1).
store [expr [fetch $::last] | 1] $::last
}
cut
}
proc compiledByteCount {} {
# Answer the number of bytes that have been laid down so far into
# the compile buffer.
return [expr $::HH - $::H]
}
proc resetCompileBuffer {} {
# Throw away whatever has been compiled into the current compile
# buffer.
set ::HH $::H
}
proc colon {} {
# Start or continue the definition of a new word in the
# dictionary. If we had been interpreting, then first send
# anything in the compile buffer to the target -- we must not mix
# interpreting and compiling in the same packet.
# Make a new entry in the ::labels array to associate the current
# value of ::H with a label.
# If we are in interactive mode, we send an interpreting message
# to the target to cause it can add the new label entry to its RAM
# token table (see addLabel).
# Multiple labels are allowed within a definition.
sendToTarget
set name [word]
if {[empty $name]} {
abort "Missing label for colon."
}
set lab [addLabel $name $::HH]
# At this point, we have updated the host's table and, if running
# interactively, the target's table.
dputs "Switching to compiling mode"
set ::mode compiling
cut
}
proc LOAD {} {
# The procedure 'getblock' takes a block number and returns a
# string with the contents of the requested block. The block
# comes from the file that the block number maps to. See the
# filenameFromBlockNumber procedure to adjust the mapping. For
# example, 1 LOAD would use the file named kernel.fth.
# We must be in interpret mode
if {[compiling?]} {
abort "attempting to LOAD while in compile mode."
}
# Before sending whatever is ready to be interpreted, we also
# compile the word that makes the target send back a "number"
# message, so we can find which block to load.
compile number
# Also reset base. The interpreting of each block begins in
# decimal, thus changing the base within a block (e.g., with HEX)
# only affects the remainder of that one block, not following
# blocks.
compile DECIMAL
# Close out anything already in the buffer (so the number
# preceding LOAD (e.g. 27 LOAD) will wind up on the target's data
# stack).
sendToTarget
# Now, the block number is on the host's local data stack.
if {[llength $::stack] < 1} {
abort "LOAD block number is missing from host's stack."
}
set n [popn]
dputs "LOAD just popped '$n' off the local stack"
dputs "LOAD is about to set newstring to block $n"
set newstring [getblock $n]
# Save on the host's stack the string we had been processing
push $::TIB
interpret $newstring
# We may have been in compile mode but we switch back to interpret
# mode so we can continue interpreting the interrupted block when
# we restore the previous string
set ::mode interpreting
set ::TIB [pop]
dputs "About to exit LOAD and continue with remainder of previous ::TIB = \{$::TIB\}"
}
proc thru {} {
# Load a range of blocks.
# We need two numbers from the target, so compile 'number' twice
compile number
compile number
# Close out any partial compilation (so the two numbers we need will
# be on the target's data stack)
sendToTarget
if {[llength $::stack] < 2} {
error "Host's stack has only [llength $::stack] item(s) but needs at least two items."
}
set endingBlock [popn]
dputs "endingBlock is $endingBlock"
set startingBlock [popn]
dputs "startingBlock is $startingBlock"
# Save on the host's stack the string we had been processing
push $::TIB
dputs "About to load blocks $startingBlock through $endingBlock"
set b $startingBlock
while {$b <= $endingBlock} {
set newstring [getblock $b]
dputs " THRU is about to interpret block $b"
# Note, we prepend DECIMAL so that loaded via LOAD or via
# THRU, the block will start off in decimal.
interpret "DECIMAL $newstring"
incr b
}
# Restore previous string
if {[llength $::stack] < 1} {
error "Host's stack has only [llength $::stack] items but needs at least one item."
}
set ::TIB [pop]
}
proc vec {slot} {
# return the address for the vector with the given zero-based slot number
expr 4 * $slot
}
proc vectorChecksum {} {
# Calculate the proper value of vector number 5 and store the value
# into that vector is ::image. This is not *really* necessary, since
# the flash utilities usually do this automatically. However, it makes
# it more convenient for comparing a file on the host with the contents
# of the target flash if the correct checksum is in the file on the host.
set s 0
foreach slot {0 1 2 3 4 6 7} {
# read every slot except for slot 5
incr s [lfetch [vec $slot]]
}
# now take its two's complement
set s [expr (0x100000000 - ($s & 0xFFFFFFFF)) & 0xFFFFFFFF]
# and store the result into slot 5
lstore $s [vec 5]
dputs "the sum is $s"
dputs "the sum in hex is [asHex $s]"
dputs "the sum truncated to 32 bits in hex is [asHex [expr $s & 0xFFFFFFFF]]"
}
proc writeImageBinary {imageName} {
# The imageName will be something like "kernel" or "myapp2". We
# convert that to the name of the binary file (suitable for
# burning into the flash on the ARM chip) named something like
# "kernel.bin" or "myapp2.bin".
# Note, this will change if the image does not begin at address
# zero (as would be the case if, for example, the ARM had a
# monitor program or custom bootloader sitting at address zero).
# fix up the bootloader checksum in the image
vectorChecksum
dputs "writeImageBinary for $imageName"
set f [open $imageName.bin w]
fconfigure $f -translation binary -encoding binary
foreach b [lrange $::image [expr 0 - $::imageBase] [expr $::H - $::imageBase - 1]] {
puts -nonewline $f [char $b]
}
close $f
dputs "writeImageBinary finished for $imageName"
}
proc writeImageDictionary {imageName} {
# The imageName will be something like "kernel" or "myapp2". We
# convert that to the name of the dictionary file named something
# like "kernel.dictionary" or "myapp2.dictionary".
dputs "writeImageDictionary for $imageName"
set f [open $imageName.dictionary w]
set ::ff $f
fconfigure $f -translation auto
dputs " about to set timestamp"
# The 'clock format' command seems to fail in Tcl 8.5, so we work
# around it if necessary.
set timestamp "<missing timestamp>"
if {[catch {set timestamp [clock format [clock scan now]]}]} {
dputs "The 'clock format' command failed"
if {[catch {set timestamp [exec date]}]} {
dputs "The 'exec date' command failed also"
}
}
dputs " the timestamp is $timestamp"
dputs " about to write header line 1"
puts $f "# This file, $imageName.dictionary, was generated automatically"
puts $f "# on $timestamp, by the writeImageDictionary"
puts $f "# procedure in riscy.tcl when the image was saved with the"
puts $f "# Forth command"
puts $f "# SAVE-IMAGE <imagename> <bootword>"
puts $f "\n# It will be loaded when Riscy Pygness is started up in interactive"
puts $f "# mode with a command such as"
puts $f "# tclkit riscy.tcl -image <imagename>"
puts $f "\n# It maps the Forth word names to addresses in the target flash memory and"
puts $f "# sets several other variables."
puts $f "\n# Statistics:"
puts $f "# [llength $::forthnamelist] names in ::forthnamelist"
puts $f "# [llength $::ramLabels] names in ::ramLabels"
puts $f "# [llength $::flashLabels] names in ::flashLabels"
puts $f "# [llength [array names ::labels]] names in ::labels\n"
# We dump the ::labels array (in token number order) so it can be reloaded.
# Make a list of pairs where the first is the token number and the second is the key
set pairsList {}
foreach {k v} [array get ::labels] {
lappend pairsList [list [labelTokenNumber $v] $k]
}
dputs "about to dump ::labels"
puts $f "\narray unset ::labels"
puts $f "\narray set ::labels {"
foreach pair [lsort -integer -index 0 $pairsList] {
set k [second $pair]
set v $::labels($k)
puts $f [format "%15s {%s}" $k [formatList "%15s %10s %2s %2s %5s" $v]]
}
puts $f "}"
# dump the ::constants array so it can be reloaded
dputs "about to dump ::constants"
puts $f "\narray unset ::constants"
puts $f "\narray set ::constants {"
foreach {k v} [array get ::constants] {
puts $f [format "%25s %s" $k $v]
}
puts $f "}"
# dump the ::label2address array so it can be reloaded
dputs "about to dump ::label2address"
puts $f "\narray unset ::label2address"
puts $f "\narray set ::label2address {"
foreach {k v} [array get ::label2address] {
puts $f [format "%25s %s" $k $v]
}
puts $f "}"
# set up ::ramTokenTableBase
puts $f "set ::ramTokenTableBase $::label2address(RTOKENS)"
puts $f "set ::H $::ram"
puts $f "set ::HH $::ram"
puts $f "set ::ram $::ram"
puts $f "set ::chip $::chip"
puts $f "\n# end of $imageName.dictionary"
close $f
dputs "writeImageDictionary finished for $imageName"
}
proc SAVE-IMAGE {} {
# This word is used in kernel.fth like this
# SAVE-IMAGE <image-name> <boot-word>
# for example,
# SAVE-IMAGE kernel QUIT
# Which indicates the image should be saved to the names
# kernel.bin and kernel.dictionary
# and that when the kernel boots it will execute the word QUIT.
# This word is allowed only when flash? is true. If we are
# in compiling mode, we will switch to interpreting mode.
# To save an image we have just created, we create
# 1. a *.bin file (to be burned into the flash)
# 2. a matching *.dictionary file to be loaded on the host when
# starting to run interactively.
# Saving a new image is allowed only when in flash
# (non-interactive) mode.
if {[interactive?]} {
abort "attempting to SAVE-IMAGE while in interactive mode."
}
# If we had been in compiling mode, we should close it out first
# then change to interpreting mode.
dputs "We are about to SAVE-IMAGE. The mode is $::mode."
dputs " The values of ::H and ::HH are $::H and $::HH"
dputs "in SAVE-IMAGE, get the two following words"
set imageName [word]
set bootword [word]
dputs "The imageName is $imageName and the bootword is $bootword"
# close out the current compile mode
sendToTarget
# Before we can write the ::image list, we must close it out by appending
# the flash token table and storing the address of the flash token table at
# the assembly label 'ptokens'. Before we do that, we must align :H to
# a full-word boundary.
dputs "about to save ::H to ptokens"
align
set ::H $::HH
lstore $::H $::label2address(ptokens)
# build the flash token table.
dputs "about to store flash token table to image"
set lastAddress 0
foreach lab $::flashLabels {
# ::flashLabels is in address order, but we will verify that
set newAddress [labelAddress $lab]
if {$newAddress < $lastAddress} {
puts "** flash table warning: (newAddress < lastAddress): ($newAddress < $lastAddress) **"
}
lcomma $newAddress
set lastAddress $newAddress
}
# update ::H
set ::H $::HH
# Set up the 'prtokens:' parm that points to the beginning of the RAM token table.
dputs "about to store value of ram token table to the prtokens parm"
lstore $::label2address(RTOKENS) $::label2address(prtokens)
# ::ram was advanced as VARIABLE etc allotted RAM. We now store its value
# into the 'pfreeram:' parm.
# guarantee ::ram is full-word aligned
alignRam
dputs "about to store new value of free ram to the pfreeram parm"
lstore $::ram $::label2address(pfreeram)
# That is also the initial value for HERE when starting to run
# interactively, (as we will then be compiling to RAM and not to
# flash). The RAM variable h will be initialized by riscy.asm.
set bootlab $::labels($bootword)
set bootaddr [labelAddress $bootlab]
dputs "about to store $bootaddr (the address of bootword $bootword) into boot variable at $::label2address(boot) "
lstore $bootaddr $::label2address(boot)
puts "Saving new kernel image"
writeImageBinary $imageName
writeImageDictionary $imageName
exit 0
# The image file was written from the ::image array, with an
# origin of zero. This is what we want when Forth sits at address
# zero in the target. Be aware that we might want an alternative
# version that sits above some sort of monitor on the target. So,
# we may to adjust for this later.
}
array unset ::labels
array set ::labels { }
## label object
# A label is a quintuple containing
# name
# address
# primitiveFlag (1=primitive, 0=high-level)
# ramFlag (1=ram, 0=flash)
# tokenNumber
### Accessors for label object
proc labelName {lab} { first $lab }
proc labelAddress {lab} { second $lab }
proc labelPrimitive {lab} { third $lab }
proc labelRAM {lab} { fourth $lab }
proc labelTokenNumber {lab} { fifth $lab }
set ::isPrimitive 1
set ::isRAM 0
set ::tokenNumber 0
set ::flashLabels ""
set ::ramLabels ""
proc addLabel {name address} {
# Note,
# ::ramLabels is a list
# ::flashLabels is a list
# ::labels is an array
# For each primitive or colon or variable definition Forth word
# label, we create an entry in the ::labels array.
# Do we need to keep a separate list of flashLabels and ramLabels?
# Let's do it for now.
# The ::flashLabels list will be used in flash mode to generate
# the flash token table.
# In flash or interactive mode, the ::labels array is used to find
# the token number when compiling a Forth word.
# first, send any pending buffer to the target
sendToTarget
set lab [list $name $address $::isPrimitive $::isRAM $::tokenNumber]
incr ::tokenNumber
# If a word is being redefined, inform the user.
if {[info exists ::labels($name)]} {
puts " redefining $name"
}
array set ::labels [list $name $lab]
if {$::isRAM} {
dputs "addLabel -- Adding $name to the list of RAM labels"
lappend ::ramLabels $lab
} else {
dputs "addLabel -- Adding $name to the list of Flash labels"
lappend ::flashLabels $lab
}
dputs "addLabel -- finished adding $name to a list"
if {[interactive?]} {
# If we are in interactive mode, we must also update the
# target's token table in RAM. We compile the Forth code to
# store the new word's address into the target's token table
# and then let the target do the work, i.e.,
# "<new-word's-address> <new-slot's-address> !"
dputs "addLabel -- we are in interactive mode"
set oldmode $::mode
# save mode so we can restore it
set ::mode interpreting
dputs "the address of the new word is [labelAddress $lab]"
lit [labelAddress $lab]
# Each entry in the RAM token table on the target occupies 4 bytes.
set slotAddress [expr $::ramTokenTableBase + (4 * [labelTokenNumber $lab])]
dputs "addLabel -- token table slotAddress for the new word is $slotAddress"
lit $slotAddress
compile "!"
sendToTarget
set ::mode $oldmode
# restore the mode
}
dputs "addLabel -- finished adding label, (name,addr) = ($name, [asHex $address])"
return $lab
}