sourcelibs/Hex1000/src/Hex1000.xtl
1⍝# Hex1000: HEX1000, a 4-bit machine in the spirit of the TMS1000: its state, one instruction as a pure step, a run harness, an assembler
2⍝# Import it with an alias of your choice: "hx:" u_se< "Hex1000".
3⍝# Put libs/Hex1000/src on XETAL_PATH ("just path"); the reference is libs/Hex1000/docs.
4⍝# Names with l: are exported; the others are private to this file.
5⍝#
6⍝# The machine is docs/machine.md's: a 4-bit accumulator A, a RAM
7⍝# index Y, a RAM file X, a status flag S, a 6-bit program counter PC
8⍝# that steps as a shift register, a page PA and a page buffer PB, one
9⍝# return address SR with its call latch CL, the output register O, 16
10⍝# output latches R and 64 words of RAM; the program is a ROM of 1024
11⍝# bytes, 16 pages of 64. Its state is a tuple of those twelve parts
12⍝# (docs/tuple-plan.md): in hardware a register per part, each its own
13⍝# width. s_tep is one instruction, pure: what the circuit does in its
14⍝# two clocks. The run harness and the assembler are the testbench's,
15⍝# never lowered.
16ˢ⁼u̲se< "Strings"
17ʳˣ⁼u̲se< "Radix"
18
19⍝## The program counter
20
21⍝# n_extPc pc: the address after pc, the shift register's rule: shifted
22⍝# left, the new low bit the XNOR of the two top bits, inverted when
23⍝# the low five bits are all 1 (so all 64 addresses are visited).
24⍝# >> "hx:" u_se< "Hex1000"
25⍝# >> hx:n_extPc 0 1 3 31 63 62
26⍝# 1 3 7 63 62 61
27ˡn̲extPc ← { pc →
28 f ← 1 − a̲bs (pc d̲iv 32) − (pc d̲iv 16) m̲od 2
29 (2 × pc m̲od 32) + a̲bs f − 31 = pc m̲od 32
30}
31
32⍝# Order: the 64 addresses in the order the program counter visits
33⍝# them from 0. The assembler puts a page's n-th instruction at
34⍝# address n s_elect Order, so a program is written in reading order.
35⍝# >> "hx:" u_se< "Hex1000"
36⍝# >> 12 t_ake hx:Order
37⍝# 0 1 3 7 15 31 63 62 61 59 55 47
38ˡOrder ← 63 '{ v → v c̲at ˡn̲extPc f̲irst (n̲eg 1) t̲ake v } p̲ower 1 r̲eshape 0
39
40⍝## The instructions
41
42⍝# Names: the mnemonics, in the order of their kinds: the 29 fixed
43⍝# instructions (code = kind), then the bit group, LDP, TCY, TCMIY,
44⍝# YNEC, ACC, BR and CALL, which take an operand.
45⍝# >> "hx:" u_se< "Hex1000"
46⍝# >> t_ally hx:Names
47⍝# 40
48ˡNames ← ˢw̲ords "NOP TAY TYA CLA TAM TMA TMY TAMIY TAMZA XMA AMAAC SAMAN IMAC DMAN IA IYC DYN CPAIZ ALEM MNEZ YNEA KNEZ TKA SETR RSTR TDO CLO COMX RETN SBIT RBIT TBIT LDX LDP TCY TCMIY YNEC ACC BR CALL"
49
50⍝# Base: each kind's first code; an instruction's code is its base
51⍝# plus its operand.
52Base ← (o̲ffsets 29) c̲at 32 36 40 44 48 64 80 96 112 128 192
53
54⍝# Top: each kind's largest operand (0: none).
55Top ← (29 r̲eshape 0) c̲at 3 3 3 3 15 15 15 15 15 63 63
56
57⍝# k_ind op: the kind of an instruction code, its place in Names from
58⍝# 0 (the unused fixed codes 29 to 31 are NOP).
59⍝# >> "hx:" u_se< "Hex1000"
60⍝# >> hx:k_ind 0 1 28 31 32 47 48 127 128 255
61⍝# 0 1 28 0 29 32 33 37 38 39
62ˡk̲ind ← { op →
63 fixed ← op × op < 29
64 bits ← (op ≥ 32) × (op < 48) × 29 + (op − 32) d̲iv 4
65 imm ← (op ≥ 48) × (op < 128) × 30 + op d̲iv 16
66 jump ← (op ≥ 128) × 38 + op ≥ 192
67 fixed + bits + imm + jump
68}
69
70⍝# d_isasm op: an instruction code as text (BR and CALL show the
71⍝# address they go to, as PC holds it).
72⍝# >> "hx:" u_se< "Hex1000"
73⍝# >> hx:d_isasm 113
74⍝# ACC 1
75ˡd̲isasm ← { op →
76 i ← ˡk̲ind op
77 name ← d̲isclose (1 + i) s̲elect ˡNames
78 0 = (1 + i) s̲elect Top ? name
79 name c̲at " " c̲at f̲ormat op − (1 + i) s̲elect Base
80}
81
82⍝## The state
83
84⍝# Reset: the state after reset, the tuple (A, Y, X, S, PC, PA, PB, SR,
85⍝# CL, O, R, RAM): everything 0 but S, which is 1.
86⍝# >> "hx:" u_se< "Hex1000"
87⍝# >> hx:r_egs hx:Reset
88⍝# 0 0 0 1 0 0 0 0 0 0
89ˡReset ← (0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 16 r̲eshape 0, 64 r̲eshape 0)
90
91⍝# r_egs st: the registers as numbers, A Y X S PC PA PB SR CL O (the
92⍝# view a trace shows).
93⍝# >> "hx:" u_se< "Hex1000"
94⍝# >> hx:r_egs (1, 2, 3, 0, 4, 5, 6, 7, 1, 8, 16 r_eshape 0, 64 r_eshape 0)
95⍝# 1 2 3 0 4 5 6 7 1 8
96ˡr̲egs ← { (a, y, x, s, pc, pa, pb, sr, cl, o, r, ram) →
97 a c̲at y c̲at x c̲at s c̲at pc c̲at pa c̲at pb c̲at sr c̲at cl c̲at o
98}
99
100⍝# r_am st: the RAM as four files of 16 words, a row per file.
101⍝# >> "hx:" u_se< "Hex1000"
102⍝# >> s_hape hx:r_am hx:Reset
103⍝# 4 16
104ˡr̲am ← { (a, y, x, s, pc, pa, pb, sr, cl, o, r, ram) → 4 16 r̲eshape ram }
105
106⍝# l_atches st: the 16 output latches R, R0 first.
107⍝# >> "hx:" u_se< "Hex1000"
108⍝# >> hx:l_atches hx:Reset
109⍝# 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
110ˡl̲atches ← { (a, y, x, s, pc, pa, pb, sr, cl, o, r, ram) → r }
111
112⍝## One instruction
113
114⍝# rom s_tep st k: the state after one instruction, with k (0 to 15)
115⍝# on the K inputs: the instruction at 64 PA + PC is fetched and
116⍝# executed (two clocks in the circuit). S goes back to 1 after every
117⍝# instruction that does not set it; BR and CALL go when S is 1 and
118⍝# otherwise only set S to 1. A word of RAM is 16 X + Y. Bind the ROM
119⍝# once: u:s_tep := rom hx:s_tep, then st u:s_tep k.
120⍝# >> "hx:" u_se< "Hex1000"
121⍝# >> rom := hx:a_ssemble "TCY 9\nTYA\nIA"
122⍝# >> hx:r_egs (rom hx:s_tep hx:Reset)_ 0
123⍝# 0 9 0 1 1 0 0 0 0 0
124ˡs̲tep ← { rom (a, y, x, s, pc, pa, pb, sr, cl, o, r, ram) k →
125 op ← (1 + pc + 64 × pa) s̲elect rom
126 i ← ˡk̲ind op
127 n ← op m̲od 16
128 at ← y + 16 × x
129 m ← (1 + at) s̲elect ram
130 bit ← 2 ^ op m̲od 4
131 set ← 1 = (m d̲iv bit) m̲od 2
132 go ← op m̲od 64
133 p1 ← ˡn̲extPc pc
134 y1 ← (y + 1) m̲od 16
135 w̲r ← { v → ram + (v − m) × at = o̲ffsets 64 }
136 i = 1 ? (a, a, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TAY
137 i = 2 ? (y, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TYA
138 i = 3 ? (0, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ CLA
139 i = 4 ? (a, y, x, 1, p1, pa, pb, sr, cl, o, r, w̲r a) ⍝ TAM
140 i = 5 ? (m, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TMA
141 i = 6 ? (a, m, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TMY
142 i = 7 ? (a, y1, x, 1, p1, pa, pb, sr, cl, o, r, w̲r a) ⍝ TAMIY
143 i = 8 ? (0, y, x, 1, p1, pa, pb, sr, cl, o, r, w̲r a) ⍝ TAMZA
144 i = 9 ? (m, y, x, 1, p1, pa, pb, sr, cl, o, r, w̲r a) ⍝ XMA
145 i = 10 ? ((a + m) m̲od 16, y, x, 0 + (a + m) > 15, p1, pa, pb, sr, cl, o, r, ram) ⍝ AMAAC
146 i = 11 ? ((m − a) m̲od 16, y, x, 0 + m ≥ a, p1, pa, pb, sr, cl, o, r, ram) ⍝ SAMAN
147 i = 12 ? ((m + 1) m̲od 16, y, x, 0 + m = 15, p1, pa, pb, sr, cl, o, r, ram) ⍝ IMAC
148 i = 13 ? ((m − 1) m̲od 16, y, x, 0 + m > 0, p1, pa, pb, sr, cl, o, r, ram) ⍝ DMAN
149 i = 14 ? ((a + 1) m̲od 16, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ IA
150 i = 15 ? (a, y1, x, 0 + y = 15, p1, pa, pb, sr, cl, o, r, ram) ⍝ IYC
151 i = 16 ? (a, (y − 1) m̲od 16, x, 0 + y > 0, p1, pa, pb, sr, cl, o, r, ram) ⍝ DYN
152 i = 17 ? ((16 − a) m̲od 16, y, x, 0 + a = 0, p1, pa, pb, sr, cl, o, r, ram) ⍝ CPAIZ
153 i = 18 ? (a, y, x, 0 + a ≤ m, p1, pa, pb, sr, cl, o, r, ram) ⍝ ALEM
154 i = 19 ? (a, y, x, 0 + m ≠ 0, p1, pa, pb, sr, cl, o, r, ram) ⍝ MNEZ
155 i = 20 ? (a, y, x, 0 + y ≠ a, p1, pa, pb, sr, cl, o, r, ram) ⍝ YNEA
156 i = 21 ? (a, y, x, 0 + k ≠ 0, p1, pa, pb, sr, cl, o, r, ram) ⍝ KNEZ
157 i = 22 ? (k, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TKA
158 i = 23 ? (a, y, x, 1, p1, pa, pb, sr, cl, o, r m̲ax 0 + y = o̲ffsets 16, ram) ⍝ SETR
159 i = 24 ? (a, y, x, 1, p1, pa, pb, sr, cl, o, r × y ≠ o̲ffsets 16, ram) ⍝ RSTR
160 i = 25 ? (a, y, x, 1, p1, pa, pb, sr, cl, a + 16 × s, r, ram) ⍝ TDO
161 i = 26 ? (a, y, x, 1, p1, pa, pb, sr, cl, 0, r, ram) ⍝ CLO
162 i = 27 ? (a, y, 3 − x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ COMX
163 (i = 28) ∧ cl = 1 ? (a, y, x, 1, sr, pb, pb, sr, 0, o, r, ram) ⍝ RETN
164 i = 29 ? (a, y, x, 1, p1, pa, pb, sr, cl, o, r, w̲r m + bit × n̲ot set) ⍝ SBIT
165 i = 30 ? (a, y, x, 1, p1, pa, pb, sr, cl, o, r, w̲r m − bit × set) ⍝ RBIT
166 i = 31 ? (a, y, x, 0 + set, p1, pa, pb, sr, cl, o, r, ram) ⍝ TBIT
167 i = 32 ? (a, y, op m̲od 4, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ LDX
168 i = 33 ? (a, y, x, 1, p1, pa, n, sr, cl, o, r, ram) ⍝ LDP
169 i = 34 ? (a, n, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ TCY
170 i = 35 ? (a, y1, x, 1, p1, pa, pb, sr, cl, o, r, w̲r n) ⍝ TCMIY
171 i = 36 ? (a, y, x, 0 + y ≠ n, p1, pa, pb, sr, cl, o, r, ram) ⍝ YNEC
172 i = 37 ? ((a + n) m̲od 16, y, x, 0 + (a + n) > 15, p1, pa, pb, sr, cl, o, r, ram) ⍝ ACC
173 (i = 38) ∧ s = 1 ? (a, y, x, 1, go, pa + (1 − cl) × pb − pa, pb, sr, cl, o, r, ram) ⍝ BR
174 (i = 39) ∧ (s = 1) ∧ cl = 0 ? (a, y, x, 1, go, pb, pa, p1, 1, o, r, ram) ⍝ CALL
175 (i = 39) ∧ s = 1 ? (a, y, x, 1, go, pa, pb, sr, cl, o, r, ram) ⍝ CALL in a subroutine: a branch in the page
176 (a, y, x, 1, p1, pa, pb, sr, cl, o, r, ram) ⍝ NOP, and BR or CALL with S 0
177}
178
179⍝## Running a program
180
181⍝# rom r_un (st, k, n): the state after n instructions with k held on
182⍝# the K inputs.
183⍝# >> "hx:" u_se< "Hex1000"
184⍝# >> rom := hx:a_ssemble "loop: IA\nBR loop"
185⍝# >> f_irst hx:r_egs rom hx:r_un (hx:Reset, 0, 9)
186⍝# 5
187ˡr̲un ← { rom (st, k, n) → n '{ t → (rom ˡs̲tep t)_ k } p̲ower st }
188
189⍝# rom r_unKeys (st, ks): the state after a run of key presses: ks is a
190⍝# matrix with a row per press, the K value and how many instructions
191⍝# it is held (K 0 for no key).
192⍝# >> "hx:" u_se< "Hex1000"
193⍝# >> rom := hx:a_ssemble "loop: TKA\nTAMIY\nBR loop"
194⍝# >> 8 t_ake f_irst hx:r_am rom hx:r_unKeys (hx:Reset, 2 2 r_eshape 7 3 5 3)
195⍝# 7 5 0 0 0 0 0 0
196ˡr̲unKeys ← { rom (st, ks) →
197 (i, last) ← (t̲ally ks) '{ (j, t) → (j + 1, rom ʰp̲ress (t, j s̲elect ks)) } p̲ower (1, st)
198 last
199}
200
201⍝# One press: a row of ks, the K value and how many instructions.
202ʰp̲ress ← { rom (t, row) → rom ˡr̲un (t, f̲irst row, f̲irst 1 d̲rop row) }
203
204⍝# rom t_race (st, k, n): the registers (r_egs) after reset and after
205⍝# each of n instructions, a row each, with k on the K inputs.
206⍝# >> "hx:" u_se< "Hex1000"
207⍝# >> rom := hx:a_ssemble "TCY 3\nDYN\nBR 1"
208⍝# >> rom hx:t_race (hx:Reset, 0, 3)
209⍝# 0 0 0 1 0 0 0 0 0 0
210⍝# 0 3 0 1 1 0 0 0 0 0
211⍝# 0 2 0 1 3 0 0 0 0 0
212⍝# 0 2 0 1 1 0 0 0 0 0
213ˡt̲race ← { rom (st, k, n) →
214 (last, rows) ← n '{ (t, m) → ʰn̲ext (rom, t, m, k) } p̲ower (st, 1 10 r̲eshape ˡr̲egs st)
215 rows
216}
217
218⍝# One more instruction, its registers added as a row.
219ʰn̲ext ← { (rom, t, m, k) →
220 t1 ← (rom ˡs̲tep t)_ k
221 (t1, m c̲at ˡr̲egs t1)
222}
223
224⍝## Showing it
225
226⍝# RegNames: the names of r_egs's columns, for t_able.
227⍝# >> "hx:" u_se< "Hex1000"
228⍝# >> hx:RegNames
229⍝# A Y X S PC PA PB SR CL O
230ˡRegNames ← "A Y X S PC PA PB SR CL O"
231
232⍝# names t_able m: the rows of a matrix of numbers (or one vector) as
233⍝# text under column names, each column five characters wide.
234⍝# >> "hx:" u_se< "Hex1000"
235⍝# >> "K held" hx:t_able 2 2 r_eshape 12 8 0 9
236⍝# K held
237⍝# 12 8
238⍝# 0 9
239ˡt̲able ← { names m →
240 rows ← ʰm̲atrix m
241 ws ← ˢw̲ords names
242 head ← '{ j → 5 ˢp̲adLeft d̲isclose j s̲elect ws } ʰj̲oin t̲ally ws
243 head c̲at '{ i → "\n" c̲at ʰr̲ow i s̲elect rows } ʰj̲oin t̲ally rows
244}
245
246⍝# A vector as a matrix of one row.
247ʰm̲atrix ← { m →
248 1 = t̲ally s̲hape m ? (1 c̲at t̲ally m) r̲eshape m
249 m
250}
251
252⍝# One row of numbers, five characters each.
253ʰr̲ow ← { row → '{ j → 5 ˢp̲adLeft f̲ormat j s̲elect row } ʰj̲oin t̲ally row }
254
255⍝# 'f j_oin n: the texts f 1, f 2, ... f n, one after another (a loop).
256ʰj̲oin ← { f̲ n →
257 (j, t) ← n '{ (j, t) → (j + 1, t c̲at f̲ j) } p̲ower (1, "")
258 t
259}
260
261⍝# p l_isting rom: page p of a ROM in the order it runs, up to its last
262⍝# instruction that is not NOP: each instruction's PC, code and text.
263⍝# >> "hx:" u_se< "Hex1000"
264⍝# >> 0 hx:l_isting hx:a_ssemble "loop: TDO\nIA\nBR loop"
265⍝# PC code instruction
266⍝# 0 25 TDO
267⍝# 1 14 IA
268⍝# 3 128 BR 0
269ˡl̲isting ← { p rom →
270 codes ← (1 + (64 × p) + ˡOrder) s̲elect rom
271 n ← 'm̲ax r̲/ (codes ≠ 0) × 1 + o̲ffsets 64
272 l̲ine ← { j → "\n" c̲at (4 ˢp̲adLeft f̲ormat j s̲elect ˡOrder) c̲at (5 ˢp̲adLeft f̲ormat j s̲elect codes) c̲at " " c̲at ˡd̲isasm j s̲elect codes }
273 " PC code instruction" c̲at 'l̲ine ʰj̲oin n
274}
275
276⍝## The assembler
277
278⍝# a_ssemble text: the 1024-byte ROM of a program. A line is an
279⍝# optional label ending in ":", then an instruction and its operand
280⍝# (a number, or a label: BR and CALL go to it, LDP takes its page),
281⍝# then an optional comment after ";". "PAGE n" starts page n (from 0);
282⍝# a page's instructions are placed in the program counter's order
283⍝# (Order), so they run in the order they are written. Unused bytes
284⍝# are 0, NOP. A mistake stops the program with the line it is on.
285⍝# >> "hx:" u_se< "Hex1000"
286⍝# >> 8 t_ake hx:a_ssemble "CLA\nTCY 5\nloop: IA\nBR loop"
287⍝# 3 69 0 14 0 0 0 131
288ˡa̲ssemble ← { text →
289 lines ← ˢl̲ines text
290 n ← t̲ally lines
291 (i, page, slot, names, places) ← n '{ t → lines ʰl̲abel t } p̲ower (1, 0, 0, 0 r̲eshape e̲nclose "", 0 r̲eshape 0)
292 (j, pg, sl, addrs, codes) ← n '{ t → (lines, names, places) ʰp̲lace t } p̲ower (1, 0, 0, 0 r̲eshape 0, 0 r̲eshape 0)
293 (addrs i̲ndexOf o̲ffsets 1024) s̲elect codes c̲at 0
294}
295
296⍝# The words of a line, its comment dropped.
297ʰw̲ords ← { t → ˢw̲ords ('∧ s̲\ t ≠ f̲irst ";") r̲eplicate t }
298
299⍝# Whether a line's first word is a label.
300ʰl̲abel? ← { ws →
301 0 = t̲ally ws ? 0 = 1
302 (f̲irst ":") = f̲irst (n̲eg 1) t̲ake d̲isclose f̲irst ws
303}
304
305⍝# Where the instruction words start, past a label.
306ʰr̲est ← { ws → (0 + ʰl̲abel? ws) d̲rop ws }
307
308⍝# The first pass, one line: a label's name and place (64 page +
309⍝# slot) recorded; the slot advanced past an instruction; PAGE n starts
310⍝# page n.
311ʰl̲abel ← { lines (i, page, slot, names, places) →
312 ws ← ʰw̲ords d̲isclose i s̲elect lines
313 rest ← ʰr̲est ws
314 named ← 0 + ʰl̲abel? ws
315 name ← (n̲eg 1) d̲rop d̲isclose f̲irst ws c̲at e̲nclose " "
316 nn ← names c̲at named t̲ake 1 r̲eshape e̲nclose name
317 np ← places c̲at named t̲ake 1 r̲eshape slot + 64 × page
318 0 = t̲ally rest ? (i + 1, page, slot, nn, np)
319 "PAGE" m̲atch d̲isclose f̲irst rest ? (i + 1, ʰn̲umber 1 d̲rop rest, 0, nn, np)
320 (i + 1, page, slot + 1, nn, np)
321}
322
323⍝# The second pass, one line: the instruction's address and code.
324ʰp̲lace ← { (lines, names, places) (i, page, slot, addrs, codes) →
325 rest ← ʰr̲est ʰw̲ords d̲isclose i s̲elect lines
326 0 = t̲ally rest ? (i + 1, page, slot, addrs, codes)
327 word ← d̲isclose f̲irst rest
328 "PAGE" m̲atch word ? (i + 1, ʰn̲umber 1 d̲rop rest, 0, addrs, codes)
329 where ← "line " c̲at (f̲ormat i) c̲at ": "
330 slot > 63 ? ⎕P̲ANIC where c̲at "page " c̲at (f̲ormat page) c̲at " is full"
331 kind ← (ˡNames i̲ndexOf e̲nclose word) − 1
332 kind ≥ t̲ally ˡNames ? ⎕P̲ANIC where c̲at "unknown instruction " c̲at word
333 v ← (names, places) ʰo̲perand (where, kind, 1 d̲rop rest)
334 (v < 0) ∨ v > (1 + kind) s̲elect Top ? ⎕P̲ANIC where c̲at "operand out of range for " c̲at word
335 addr ← (64 × page) + (1 + slot) s̲elect ˡOrder
336 (i + 1, page, slot + 1, addrs c̲at addr, codes c̲at v + (1 + kind) s̲elect Base)
337}
338
339⍝# An operand: a number, or a label (BR and CALL: its address as PC
340⍝# holds it; LDP: its page); none is 0.
341ʰo̲perand ← { (names, places) (where, kind, ws) →
342 0 = t̲ally ws ? 0
343 w ← d̲isclose f̲irst ws
344 '∧ r̲/ w m̲ember? ⎕D ? ʰn̲umber ws
345 j ← names i̲ndexOf e̲nclose w
346 j > t̲ally names ? ⎕P̲ANIC where c̲at "unknown label " c̲at w
347 place ← j s̲elect places
348 kind = 33 ? place d̲iv 64
349 kind ≥ 38 ? (1 + place m̲od 64) s̲elect ˡOrder
350 ⎕P̲ANIC where c̲at "a label is an operand only of BR, CALL and LDP"
351}
352
353⍝# A decimal number, the first word.
354ʰn̲umber ← { ws → 10 d̲ecode (⎕D i̲ndexOf d̲isclose f̲irst ws) − 1 }
355
356⍝## The calculator
357
358⍝# Calc: the calculator program's source (rom/calc.asm): an
359⍝# eight-digit hexadecimal calculator, Radix's behavior key for key.
360⍝# >> "hx:" u_se< "Hex1000"
361⍝# >> 5 t_ake hx:Calc
362⍝# ; HEX
363ˡCalc ← @ i̲nclude< "../rom/calc.asm"
364
365⍝# CalcRom: the calculator program assembled, 1024 bytes.
366⍝# >> "hx:" u_se< "Hex1000"
367⍝# >> hx:d_isasm 4 s_elect hx:CalcRom
368⍝# CALL 0
369ˡCalcRom ← ˡa̲ssemble ˡCalc
370
371⍝# m_atrix (c, r): the K inputs the keyboard gives with key c held
372⍝# and the latches r: the keys are a matrix of 6 columns, selected by
373⍝# R8 to R13, of 4 rows, one K bit each; key c is in column c / 4 and
374⍝# row c mod 4 (the codes are Radix's, rx:k_eys).
375⍝# >> "hx:" u_se< "Hex1000"
376⍝# >> hx:m_atrix (18, (12 r_eshape 0) c_at 1 0 0 0)
377⍝# 4
378ˡm̲atrix ← { (c, r) → ((9 + c d̲iv 4) s̲elect r) × 2 ^ c m̲od 4 }
379
380⍝# rom t_ype (st, keys, n): the machine typed at, as a terminal does:
381⍝# each key is held from when R15 says the program is ready until it
382⍝# says busy, and the display latches each digit's O when its latch
383⍝# R0 to R7 rises. Gives (the state, the eight digit latches, digit 0
384⍝# first, whether every key was taken and the program is ready again,
385⍝# the instructions it took) after at most n instructions; once done
386⍝# it stops.
387⍝# >> "hx:" u_se< "Hex1000"
388⍝# >> (st, digits, done, took) := hx:CalcRom hx:t_ype (hx:Reset, 1 2, 2000)
389⍝# >> hx:s_creen r_ev digits
390⍝# 12
391ˡt̲ype ← { rom (st, keys, n) → rom ˡt̲ypeOn (st, 8 r̲eshape 0, keys, n) }
392
393⍝# rom t_ypeOn (st, digits, keys, n): t_ype from a display already
394⍝# showing digits (a terminal keeps its screen between keys: a key the
395⍝# program ignores changes nothing on it).
396⍝# >> "hx:" u_se< "Hex1000"
397⍝# >> (st, digits, done, took) := hx:CalcRom hx:t_ype (hx:Reset, 1 2, 2000)
398⍝# >> (st2, digits2, done2, took2) := hx:CalcRom hx:t_ypeOn (st, digits, 21 r_eshape 0, 0)
399⍝# >> hx:s_creen r_ev digits2
400⍝# 12
401ˡt̲ypeOn ← { rom (st, digits, keys, n) →
402 start ← (st, digits, ˡl̲atches st, 0, 1, 0)
403 (t, d, r, ph, i, took) ← n '{ v → (rom, keys) ʰt̲ick v } p̲ower start
404 (t, d, ph = 3, took)
405}
406
407⍝# rom r_ecord (st, keys, n): t_ype's first n instructions as the
408⍝# circuit sees them at its ports: per instruction, the K value given,
409⍝# and O and the R latches after it, (ks, os, rs) (rs a row per
410⍝# instruction). n is t_ype's count for the keys, so nothing is idle.
411⍝# >> "hx:" u_se< "Hex1000"
412⍝# >> (ks, os, rs) := hx:CalcRom hx:r_ecord (hx:Reset, 0 r_eshape 0, 3)
413⍝# >> ks
414⍝# 0 0 0
415ˡr̲ecord ← { rom (st, keys, n) →
416 start ← ((st, 8 r̲eshape 0, ˡl̲atches st, 0, 1, 0), 0 r̲eshape 0, 0 r̲eshape 0, 0 16 r̲eshape 0)
417 (v, ks, os, rs) ← n '{ (v, ks, os, rs) → ʰr̲ec (rom, keys, v, ks, os, rs) } p̲ower start
418 (ks, os, rs)
419}
420
421⍝# One recorded instruction: t_ick, and what crossed the ports.
422ʰr̲ec ← { (rom, keys, (t, digits, r, ph, i, took), ks, os, rs) →
423 c ← (i s̲elect keys c̲at 0) × ph = 1
424 k ← (ph = 1) × ˡm̲atrix (c, r)
425 v1 ← (rom, keys) ʰt̲ick (t, digits, r, ph, i, took)
426 (t1, d1, r1, ph1, i1, took1) ← v1
427 (v1, ks c̲at k, os c̲at f̲irst (n̲eg 1) t̲ake ˡr̲egs t1, rs c̲at r1)
428}
429
430⍝# One instruction of t_ype: phase 0 waits for R15, 1 holds key i, 3
431⍝# is done.
432ʰt̲ick ← { (rom, keys) (t, digits, r, ph, i, took) →
433 ph = 3 ? (t, digits, r, ph, i, took)
434 c ← (i s̲elect keys c̲at 0) × ph = 1
435 k ← (ph = 1) × ˡm̲atrix (c, r)
436 t1 ← (rom ˡs̲tep t)_ k
437 r1 ← ˡl̲atches t1
438 rose ← (8 t̲ake r1) > 8 t̲ake r
439 o ← f̲irst (n̲eg 1) t̲ake ˡr̲egs t1
440 d1 ← digits + rose × o − digits
441 ready ← 1 = 16 s̲elect r1
442 (ph = 0) ∧ ready ? (t1, d1, r1, 1 + 2 × i > t̲ally keys, i, took + 1)
443 (ph = 1) ∧ n̲ot ready ? (t1, d1, r1, 0, i + 1, took + 1)
444 (t1, d1, r1, ph, i, took + 1)
445}
446
447⍝# s_creen digits: latched digits as text, in the order given (the
448⍝# display's is digit 7 first: hx:s_creen r_ev digits); a digit latched
449⍝# with O's bit 4 clear is blank.
450⍝# >> "hx:" u_se< "Hex1000"
451⍝# >> hx:s_creen 0 0 0 0 0 31 17 2
452⍝# F1
453ˡs̲creen ← { d → (1 + (d ≥ 16) × 1 + d m̲od 16) s̲elect " " c̲at ⎕D c̲at 6 t̲ake ⎕A }
454
455⍝# c_alc text: the screen after the keys of a text such as "12+34=",
456⍝# typed from reset at HEX1000 running CalcRom: the machine's answer to
457⍝# Radix's 16 rx:c_alc text. Characters that are not keys are skipped.
458⍝# The run is given 2000 instructions and 1500 a key, and 12000 more
459⍝# for each *; it stops with an error if the program is not ready
460⍝# again by then.
461⍝# >> "hx:" u_se< "Hex1000"
462⍝# >> hx:c_alc "12+34="
463⍝# 46
464
465⍝# c_ost text: how many instructions the calculator takes for the keys
466⍝# of a text, from reset until it is ready after the last key (at
467⍝# 13.5 million instructions a second on a board, two clocks each at
468⍝# 27 MHz).
469⍝# >> "hx:" u_se< "Hex1000"
470⍝# >> hx:c_ost "12+34="
471⍝# 1752
472ˡc̲alc ← { text →
473 codes ← ʳˣk̲eys text
474 keys ← (codes < 21) r̲eplicate codes
475 n ← 2000 + (1500 × t̲ally keys) + 12000 × '+ r̲/ text = f̲irst "*"
476 (st, digits, done, took) ← ˡCalcRom ˡt̲ype (ˡReset, keys, n)
477 n̲ot done ? ⎕P̲ANIC "hx:c_alc: the program did not finish " c̲at text c̲at " in " c̲at f̲ormat n
478 ˡs̲creen r̲ev digits
479}
480
481ˡc̲ost ← { text →
482 codes ← ʳˣk̲eys text
483 keys ← (codes < 21) r̲eplicate codes
484 n ← 2000 + (1500 × t̲ally keys) + 12000 × '+ r̲/ text = f̲irst "*"
485 (st, digits, done, took) ← ˡCalcRom ˡt̲ype (ˡReset, keys, n)
486 took
487}
i̲nclude< expands to
("; HEX1000's calculator program: an eight-digit hexadecimal calculator\n; with + - * = and X (clear), the behavior of the Radix library\n; (libs/Radix) key for key. Assembled by hx:a_ssemble; the library\n; includes this file as hx:Calc.\n;\n; RAM: file 0 the accumulator, file 1 the entry, file 2 the\n; multiplier's digits while multiplying, each eight words, word 0 the\n; least significant digit. File 3 holds the control words:\n; word 0 the column of the key just read (8 to 13)\n; word 1 the pending operation: 0 none, 1 +, 2 -, 3 *\n; word 2 the phase: 0 after an operator, 1 entering digits, 2 after =\n; word 3 the digit just keyed\n; word 4 the operator key's row: 0 +, 1 -, 2 *, 3 =\n; word 5 the multiplier digit being worked on\n; words 8 to 13: the K bits read from that column\n;\n; The R latches: R0 to R7 strobe the display's digits (digit 0 on the\n; right): a digit takes O when its latch rises; O's bit 4 (S at TDO)\n; set shows the digit A, clear blanks it. R8 to R13 select the key\n; columns: the key with code c is in column c / 4 and drives K's bit\n; c mod 4 while its column is selected (codes as Radix's: 0 to 15 the\n; digits, 16 + 17 - 18 * 19 =, 20 X). R15 is 1 while the program waits\n; for a key, so a terminal (or the test harness) types the next key\n; only then.\n;\n; One subroutine level: the subroutines (pages 4 to 6) call nothing;\n; the main code (pages 0 to 3) calls them, after LDP of their page.\n\nPAGE 0 ; reset, the key scan, what kind of key\n\ninit: LDX 1\n LDP show\n CALL show ; show the entry: 0\nready: TCY 8 ; wait until every key is up\nrl: SETR\n KNEZ\n BR held\n RSTR\n IYC\n YNEC 14\n BR rl\n TCY 15\n SETR ; R15: ready for a key\nscan: TCY 8\ncol: SETR ; select a column\n KNEZ\n BR hit ; a key in it\n RSTR\n IYC\n YNEC 14\n BR col\n BR scan\nheld: RSTR\n BR ready\nhit: TKA ; the key's row bit\n RSTR\n LDX 3\n TAM ; kept in file 3 at the column's word\n TYA\n TCY 0\n TAM ; the column\n TCY 15\n RSTR ; R15 0: busy\n TCY 0\n TMY ; Y: the column again\n CLA ; A: the row, from the bit\n TBIT 0\n BR rowok\n IA\n TBIT 1\n BR rowok\n IA\n TBIT 2\n BR rowok\n IA\nrowok: YNEC 12\n BR notop\n LDP opkey ; column 12: + - * =\n BR opkey\nnotop: YNEC 13\n BR digit\n ACC 15 ; column 13: only row 0 is a key, X\n BR ready\n LDP clear\n BR clear\ndigit: YNEC 8 ; the digit: the row plus 4 per column\n BR dm\n LDP dgo\n BR dgo\ndm: ACC 4\n DYN\n BR digit\n\nPAGE 1 ; a digit key\n\ndgo: LDX 3\n TCY 3\n TAM ; keep the digit\n TCY 2\n TMA\n ACC 14\n BR fresh ; phase 2: a new calculation\n TMA\n ACC 15\n BR app ; phase 1: append\n LDX 1 ; phase 0: a new entry\n LDP zero\n CALL zero\n BR app2\nfresh: LDX 0\n LDP zero\n CALL zero\n LDX 1\n LDP zero\n CALL zero\n LDX 3\n TCY 1\n TCMIY 0 ; nothing pending\n BR app2\napp: LDX 1\n TCY 7\n MNEZ\n BR full ; eight digits already: ignored\napp2: LDX 1\n LDP shl\n CALL shl ; the entry times 16\n LDX 3\n TCY 3\n TMA\n LDX 1\n TCY 0\n TAM ; plus the digit\n LDX 3\n TCY 2\n TCMIY 1 ; phase 1\n LDX 1\n LDP show\n CALL show ; show the entry\n LDP ready\n BR ready\nfull: LDP ready\n BR ready\n\nPAGE 2 ; an operator key, or =: settle the pending operation\n\nopkey: LDX 3\n TCY 4\n TAM ; keep the row\n TCY 2\n TMA\n ACC 14\n BR settled ; phase 2: nothing keyed since =\n TMA\n ACC 15\n BR p1 ; phase 1: settle\n BR settled ; phase 0: nothing keyed since the operator\np1: TCY 1\n TMA ; the pending operation\n ACC 15\n BR doop\n LDP copy10\n CALL copy10 ; none: the entry is the result\n BR settled\ndoop: ACC 15\n BR nadd\n LDP add\n CALL add\n BR settled\nnadd: ACC 15\n BR domul\n LDP sub\n CALL sub\n BR settled\ndomul: LDP mul\n BR mul\nsettled: LDX 3\n TCY 4\n TMA\n ACC 13\n BR equals ; row 3: =\n TMA\n IA\n TCY 1\n TAM ; pending: the row plus 1\n TCY 2\n TCMIY 0 ; phase 0\n BR cle\nequals: TCY 1\n TCMIY 0 ; nothing pending\n TCMIY 2 ; phase 2\ncle: LDX 1\n LDP zero\n CALL zero ; the entry starts at 0\n LDX 0\n LDP show\n CALL show ; show the result\n LDP ready\n BR ready\n\nPAGE 3 ; multiply, and clear\n\nmul: LDP copy12\n CALL copy12 ; the multiplier (the entry) to file 2\n LDP copy01\n CALL copy01 ; the multiplicand (the accumulator) to file 1\n LDX 0\n LDP zero\n CALL zero ; the product\n LDX 3\n TCY 5\n TCMIY 7 ; from the multiplier's top digit\nmloop: LDX 0\n LDP shl\n CALL shl ; the product times 16\ninner: LDX 3\n TCY 5\n TMY\n LDX 2\n MNEZ\n BR mdo ; this digit not yet used up\n LDX 3\n TCY 5\n DMAN\n BR mnext ; the next digit down\n LDP settled\n BR settled ; done: the product is in file 0\nmnext: TAM\n BR mloop\nmdo: DMAN\n TAM ; one fewer\n LDP add\n CALL add ; the product plus the multiplicand\n BR inner\nclear: LDX 0\n LDP zero\n CALL zero\n LDX 1\n LDP zero\n CALL zero\n LDX 3\n TCY 1\n TCMIY 0 ; nothing pending\n TCMIY 0 ; phase 0\n LDX 1\n LDP show\n CALL show\n LDP ready\n BR ready\n\nPAGE 4 ; subroutines: show, shl, zero (on file X)\n\nshow: TCY 7 ; from the top digit: leading zeros blank\nlead: TMA\n MNEZ\n BR digs ; the first digit that is not 0\n YNEC 0\n BR blank\n TDO ; the number is 0: its last digit shows 0\n SETR\n RSTR\n RETN\nblank: MNEZ ; S 0: blank\n TDO\n SETR\n RSTR\n DYN\n BR lead\ndigs: TMA\n TDO ; S 1: shown\n SETR\n RSTR\n DYN\n BR digs\n RETN\nshl: TCY 6 ; each word up one, 0 into word 0\nsl: TMA\n IYC\n TAM\n DYN\n DYN\n BR sl\n TCY 0\n TCMIY 0\n RETN\nzero: TCY 0\nzl: TCMIY 0\n YNEC 8\n BR zl\n RETN\n\nPAGE 5 ; subroutines: file 0 plus file 1, file 0 minus file 1\n\nadd: TCY 0\nan: LDX 1 ; no carry in\n TMA\n LDX 0\n AMAAC\n BR ac\n TAMIY\n YNEC 8\n BR an\n RETN\nac: TAMIY ; a carry out\n YNEC 8\n BR ci\n RETN\nci: LDX 1 ; a carry in\n TMA\n LDX 0\n AMAAC\n BR cc\n ACC 1\n BR ac\n TAMIY\n YNEC 8\n BR an\n RETN\ncc: IA\n BR ac\nsub: TCY 0\nsn: LDX 1 ; no borrow in\n TMA\n LDX 0\n SAMAN\n BR snb\n TAMIY ; a borrow out\n YNEC 8\n BR sb\n RETN\nsnb: TAMIY\n YNEC 8\n BR sn\n RETN\nsb: LDX 1 ; a borrow in\n TMA\n LDX 0\n SAMAN\n BR sb1\n ACC 15\n TAMIY\n YNEC 8\n BR sb\n RETN\nsb1: ACC 15\n BR snb\n TAMIY\n YNEC 8\n BR sb\n RETN\n\nPAGE 6 ; subroutines: copies between files\n\ncopy10: TCY 0 ; file 1 to file 0\nc10: LDX 1\n TMA\n LDX 0\n TAMIY\n YNEC 8\n BR c10\n RETN\ncopy12: TCY 0 ; file 1 to file 2\nc12: LDX 1\n TMA\n LDX 2\n TAMIY\n YNEC 8\n BR c12\n RETN\ncopy01: TCY 0 ; file 0 to file 1\nc01: LDX 0\n TMA\n LDX 1\n TAMIY\n YNEC 8\n BR c01\n RETN\n")