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"
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")
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}