-
Notifications
You must be signed in to change notification settings - Fork 0
/
1V0TzIV_ZXSpectrum.txt
407 lines (279 loc) · 20.3 KB
/
1V0TzIV_ZXSpectrum.txt
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
1V0 Tz IV on the Spectrum
2024-12-12
1V0 (pronounced: "Ivo") is a programming language / virtual machine particularly for small resource computers (and microcontrollers), permitting interactive computation by way of an interpreter which can be operated entirely by way of a numeric keypad (incl. a facility to enter and erase input). This present iteration is the "Tzvetanka IV" variant of it, with the Tzvetanka sub-series generally letting instructions generally have the form:
instruction address, operator, data address 1, data address 2, data address 3
and, in regard to the values stored at such data addresses, letting operations generally result in "value 3 = value 1 operation value 2".
The memory available follows the Harvard architecture principle in that "instruction at address 123" and "datum at address 123" are stored in two separate address spaces.
The maximum instruction address is 219; the maximum datum address is 219, too, in this specific implementation. The minimum address for each is 1. Datum at address 0 is reserved and assumed to be always set to 0. Instruction 0 is reserved for immediate operations: operations with address 0 execute and immediately return to request further input, implementing thus an interactive mode, with exception of jump instructions, which go to the intended jump target. Instructions with addresses > 0 are sequentially executed, incrementing the instruction counter, until the end of the instruction memory is reached, whereupon the execution terminates and returns to interactive mode. Operation can be thus separated in an "interactive mode" and a "sequential execution mode", the latter being generally entered from the former by means of a jump instruction, e.g. "0,1,0,20,6" (to unconditionally jump to the 20th instruction and continue execution from there) and will run until instruction memory has been traversed, unless such traversal is cut short e.g. via "[instruction address],1,0,219,6" in order to jump to the end of the instruction space, which upon execution ends.
Interactive mode is the default appearance of the system. There are several commands which can be issued in interactive mode, including entry and examination of data, and generally perform auxiliary and preparatory functions in regard to 1V0's execution:
1111, then instruction address: show instruction at the corresponding position (and a few further ones), in the form of A B C D, where A is the operation, and B, C, D are the respective data addresses the instruction operates upon. (If any data address consists of three digits, its display will overflow into the preceding address, but its internal representation will remain unaffected; this matters particularly for instructions like JUMP or SADR mentioned below.)
2222, then datum address: display the datum stored at a data address. The numeric format for data is SIIIIIIIIIIIIIII FFFFFFFF, whereby S is the sign (0 = +, 1 = -), I signifies integer positions and F signifies fractional positions. -2 would thus be 1000000000000002 00000000. Pi could be expressed as 0000000000000003 14159265.
4444, then datum address, then datum value: store a datum at a data address. The input is requested in blocks of four numbers each:
SIII
IIII
IIII
IIII
FFFF
FFFF
5555: clear (delete) all instructions.
6666: clear (delete) all data.
7777: toggle tracing of execution.
8888: terminate and exit the system.
9999, then new runlimit: change the runlimit (from its default 3600) - make it smaller or larger; this is the maximum count of steps executed in one sequential execution run ere the system forcibly stops execution - a safety feature against unintended infinite loops. Setting it to 0 disables the run limit and execution is unlimited.
Regarding programming, in general terms, each instruction is entered after the prompt of zeroes as five numbers:
- the address of the instruction in instruction space;
- the operation to be performed;
- the first, second and then the third datum address which shall be involved in the operation.
Thus, e.g.:
14
9
1
2
3
would mean as 14th instruction, execute addition (9), whereby you add the numbers under the 1st and the 2nd data address and store the result in the third data address. - The third data address is commonly the target of all operations, i.e. where the result is stored. It may be the same as one of the first two addresses, too. The above example would now commonly be written as 14,9,1,2,3. The data addresses shall henceforth be denoted, for brevity, as D1, D2 and D3, and the values stored at them as V1, V2 and V3. This instruction sequence, for the purpose of parsimony of use of space, is written normally as 14,9,1,2,3 in listings.
To remember them better, the operations have "names", but these are names only and are not expressed during interaction with the machine - all I/O is purely numeric. The operations available are as follows - starting with the most elementary applications:
NOOP -- 0 (or unknown number): no operation
JUMP -- 1: Depending on the value set by D3, and the value stored at D1, perform a jump to D2, interpreted as an instruction address, or the value stored at D2 interpreted as an instruction address, as follows:
If the value stored at D1 is equal to 0, and D3 itself is 0, or;
if the value stored at D1 is greater than 0, and D3 itself is 1, or;
if the value stored at D1 is less than 0, and D3 itself is 2, or;
if the value stored at D1 is greater than or equal to 0, and D3 itself is 3, or;
if the value stored at D1 is less than or equal to 0, and D3 itself is 4, or;
if the value stored at D1 is not equal to 0, and D3 itself is 5, or;
if D3 itself is 6 (and unconditionally regardless of the value stored at D1);
then jump to D2, where D2 is interpreted as an instruction address; or
if the value stored at D1 is equal to 0, and D3 itself is 7, or;
if the value stored at D1 is greater than 0, and D3 itself is 8, or;
if the value stored at D1 is less than 0, and D3 itself is 9, or;
if the value stored at D1 is greater than or equal to 0, and D3 itself is 10, or;
if the value stored at D1 is less than or equal to 0, and D3 itself is 11, or;
if the value stored at D1 is not equal to 0, and D3 itself is 12, or;
if D3 itself is 13 (and unconditionally regardless of the value stored at D1);
then jump to the value stored at D2, where the value stored at D2 is interpreted as an instruction address. This latter block of conditions implements "computed gotos".
IADR -- 2: Indirect addressing preparation; D1, D2 and D3 in this instruction are assumed to point to values V1, V2 and V3, respectively (whereby only the four lowest integer digits in each value are used, i.e. the LLLL part in a positive integer SIIIIIIIIIIILLLL FFFFFFFF); these V1, V2 and V3 shall themselves serve as D1, D2 and D3 in the NEXT instruction, regardless of what D1, D2 and D3 the next instruction comes with. So, if you have:
...
11,2,1,2,3
12,9,0,0,0
...
whereby datum 1 contains 31, datum 2 contains 23 and datum 3 contains 64, then the next instruction will be executed as if it had been:
...
11,9,31,23,64
...
This can be tested as follows:
4444,1,000000000000000100000000 - set value 1 at datum address 1
4444,2,000000000000123400000000 - set value 1234 at datum address 2
4444,3,000000000000567800000000 - set value 5678 at datum address 3
0,2,2,3,1
0,5,0,0,0
should execute as
0,5,1234,5678,1
and 2222,1 should show:
0000000000001234 56780000
The use of the instruction lies in supplying indirect addressing, where the exact datum address may not be known at the time of writing the program, but may thus be computed later on and "set" for the proper instruction.
SADR -- 5: Set address as a positive value, essentially setting V3 to D1.D2, between 0.0000 and 9999.9999. For instance, 0,5,1234,4567,3 sets datum 3 to 1234.4567 or 0000000000001234 4567. The usefulness of the instruction lies in being able to set common constants and other housekeeping values in program space, rather than explicitly entering them as data (and thus, letting data with such setup "survive" reboots, as it will be set up properly next time the program is run, too).
SVAL -- 6: Set V3 to V2 (ignoring D1 and V1). Like common variable assignment in other languages.
IVAS -- 8: Indirect value assignment, where the value stored at D3 is interpreted itself as a datum address (say, "X"), and the value stored at X is set to V2. This is useful for manipulating arrays, where D3 may point to an array index, and that array index may change, but the corresponding array element under that array index will be set to V2.
PLUS -- 9: Addition, V3 = V1 + V2.
MINS -- 10: Subtraction, V3 = V1 - V2.
MULS -- 11: Multiplication, V3 = V1 * V2.
DIVS -- 12: Division, V3 = V1 * 100. Division by 0 results in 0.
In case an error is made (beyond what can corrected through immediate re-editing of a number), instructions merely need to be re-entered. Instructions need not be entered in any particular order, i.e. if you first enter the second, then the third and then the first instruction, they will still be executed in the order first, then second, then third instruction.
Using even just this "simple set" of instructions, complex computations are possible. An example program to add the numbers from 10 to 20 in steps of 0.5 would appear as:
(Optional: 5555 and 6666 to clear any previous program residuals.)
1,5,0,5000,1, set datum 1 to 0.5, the step
2,5,10,0,2, set datum 2 to the lower limit 10
3,5,20,0,3, set datum 3 to the higher limit 20
4,5,0,0,4, set datum 4 to zero for the initial result
5,5,1,0,5, set the difference to 1 (to be adjusted later)
6,1,5,219,2, terminate if the lower limit becomes greater than the higher limit
7,9,4,2,4, result is set to result plus lower limit
8,9,2,1,2, raise the lower limit by the step
9,10,3,2,5, find the difference between the higher and the lower limit
10,1,0,6,6 unconditionally jump to the loop check
To launch the program, do: 0,1,0,1,6
(You may need to press Enter after each screenfill.)
To examine the result, do: 2222, 4
The result should be revealed as 315, i.e. 0000000000000315 00000000.
Further instructions may help accomplish more complex tasks with less code, whereby "1", "2" and "3" refer to the respective data addresses:
1 = positive mantissa 2 = positive integer exponent 3 = result
POXY -- 13: X TO THE YTH
1 = number 2 = after decimal 3 = whole part
IFRA -- 15: INTEGER AND FRACTIONAL PART
1 = period 2 = +-percentage as decimal 3 = (1+percentage)^period
AMNT -- 17: AMOUNT WITH PERCENTAGE OVER TIME; according to the equation, amount = (1 + (percentage / 100)) ^ period
SWAP -- 20: SWAP TO NUMBERS (at D1 and D2; a trivial but annoying task for novices)
1 = rangebegin 2 = rangeend 3 = targetbegin
COPY -- 22: COPY A RANGE; in range computations, limits are always inclusive (and: giving the same number as range begin and end allows a computation on a single number)
1 = rangebegin 2 = rangeend 3 = result
SUMR -- 28: SUM UP A RANGE INTO ONE NUMBER
SUSQ -- 29: SUM THE SQUARES OF A RANGE INTO ONE NUMBER; useful for computing variance, geometric distances and thelike
1 = rangebegin 2 = rangeend 3 = targetbegin
CORS -- 26: COUPLED RANGE SORTING - this is a facility for sorting one range according to another. For instance, you have employee age and salary for employees of a certain type in two ranges of equal size, and you would like to sort the one range by age and see how the second range, that of the salaries, would change according to that age sorting. (Evidently, JUST sorting the age or JUST sorting the salaries is NOT what you want - you want to keep the COUPLING of each pair of elements in the respective ranges.)
IXTH -- 30: 1/X; turn each value X within a range into a value 1/X in the target range.
ABSR -- 31: ABSOLUTE VALUES OF A RANGE
SQRT -- 32: SQUARE ROOT of each element of a range
SQUA -- 33: SQUARE each element of a range
CBRT -- 34: CUBE ROOT of each element of a range
CUBE -- 35: CUBE (raise to the third power) each element of a range
SIND -- 40: SINUS ON DECIMAL for each element of a range, 0-90 degrees
COSD -- 41: COSINUS ON DECIMAL
TAND -- 42: TANGENS ON DECIMAL
1 = rangebegin 2 = rangeend 3 = mean, and the address following/above D3 contains stdev (based on "n", not "n-1")
MSTD -- 46: MEAN AND STANDARD DEVIATION on a range
1 = rangebegin 2 = rangeend 3 = ignored, result in range
SORT -- 25: SORT a range
TURN -- 27: TURN a range "upside down" (or "flip/reverse an array")
1 = rangebegin 2 = rangeend 3 = begin of second range, result in first range
PLUR -- 53: Add two ranges; the names are modeled on the "simple" instructions
MINR -- 54: Subtract a range from another
MULR -- 55: Multiply each element of a range by the corresponding element of a second range
DIVR -- 56: Divide each element of a range by the corresponding element of a second range
1 = rangebegin 2 = rangeend 3 = num, result in range
FRIS -- 23: FILL RANGE BY STEP; the inclusive range between datadr1 and 2 is filled by the step at 3; i.e. if the step is 2, then the filling is 2,4,6,8...
PLUN -- 57: Add to each element of a range a number
MINN -- 58: Subtract from each element of a range a number
MULN -- 59: Multiply each element of a range by a number
DIVN -- 60: Divide each element of a range by a number
Using z88dk, the Spectrum version of the system may be compiled with a command line akin to the following, tested on Windows 11 and Ubuntu Linux 22.04:
zcc +zx -clib=new -create-app 1V0TzIV600zx_20241211b.c -o ivo -create-app
There is presently no facility to save any files, including written programs, and these are to be therefore recreated upon each activation of 1V0.
Example programs:
Compute the factorial of a number < 17 under datum address 9 (e.g. 13), and see the result in datum address 10 (every "comma" is really an "enter"):
4444,9,0,0,0,13,0,0 write 13 into datum memory location 9
10,6,0,9,10 copy the received number into datum location 10
11,6,0,10,12 copy the received number into datum location 12
12,5,1,0,11 set a 1.0 into datum location 11 (needed in subtraction)
13,1,10,219,0 if we have reached 0, stop multiplying and exit
14,6,0,12,13 copy the intermediate result to datum address 13
15,10,10,11,10 turn n into n-1
16,11,10,12,12 multiply n-1 with result accumulator (datum address 12)
17,1,0,13,6 unconditionally loop
0,1,0,10,6 unconditionally jump (trigger) instruction 10
2222,10 inspect the result
Determine is a number prime, the number entered at datum address 10, the result being in datum address 11, either 1.0 - "not prime" - or 2.0 - "prime", e.g. 9:
4444,10,0,0,0,9,0,0 write the desired number 9 into datum memory location 10
1,5,1,0,1 set a 1.0 at datum memory location 1
2,5,2,0,2 set a 2.0 at datum memory location 2
3,10,10,2,3 subtract 2: are we dealing with a 2?
4,1,3,16,0 if the subtraction result is 0, we got 2, which is prime
5,1,3,18,2 if the subtraction result is less than 0, no prime
6,12,10,2,3 is the number divisible by 2
7,15,3,3,4 fractional part goes to mem. 3, integer part to mem. 4
8,1,3,18,0 fractional part = 0 means divisible by 2, i.e. not prime
9,10,4,1,5 subtract 1 from the divisor, to check for 0, i.e. "end"
10,1,5,16,0 if the divisor had been 1, then the number is prime
11,12,10,4,9 divide the number by the divisor, divisum goes to mem 9
12,15,9,8,0 discard the integer part and keep the divison rest in 8
13,1,8,18,0 if said division rest is zero, jump to "not prime"
14,6,0,5,4 otherwise get a new divisor, computed above in instr. 9
15,1,0,9,6 unconditional main loop to instruction 9
16,5,2,0,11 prime: move a 2.0 into datum position 11
17,1,0,219,6 unconditionally terminate
18,5,1,0,11 not prime: move a 1.0 into datum position 11
19,1,0,219,6 unconditionally terminate
2222,11 inspect the result
It is, moreover, on a desktop machine running a bash shell entirely possible to pipe input into 1V0 Tz IV and run programs "non-interactively". E.g., copy and save the following program between (and excluding) the dashed lines to approximate Pi via a Wallis series, e.g. under wallis.1v0:
- - - - - - -
1,5,1,0,10 set the accumulator to 0
2,5,0,0,9 set the dividend to 0
3,5,1,0,8 set the divisor to 1
4,5,0,0,7 set the first divisum to 0
5,5,0,0,6 set the second divisum to 0
6,5,10,0,5 set up the loop limit to
7,5,10,0,4 100 by multiplying
8,11,5,4,5 10 times 10 (one possibility)
9,5,2,0,2 set datum at memory address 2 to 2
10,5,1,0,1 set datum at memory address 1 to 1
11,10,5,1,5 decrease the loop limit (99 loops)
12,1,5,20,0 go to result print if above has become 0
13,9,9,2,9 increase the dividend by 2
14,12,9,8,7 dividend / divisor -> first divisum
15,9,8,2,8 increase divisor by 2
16,12,9,8,6 dividend / divisor -> second divisum
17,11,10,7,10 accumulator = itself times first divisum
18,11,10,6,10 accumulator = itself times second divisum
19,1,0,11,6 loop - unconditional jump to instr. 11
20,9,10,10,10 add pi/2 to pi/2 to gain pi
21,1,0,219,6 terminate, jumping to end of instructions
0,1,0,1,6 trigger - immediate jump to instruction 1
2222,10 print the result
8888 exit 1V0 Tz IV
- - - - - - -
This program can then be run as follows, automatically cutting off the comments from the first space character onwards (substitute "ivotz4" (the Linux executable) for whatever you have on your platform, e.g. the program "ivtz4mac" for a macOS system):
cut -f1 -d ' ' wallis.1v0 | tr ',' '\n' | ./ivotz4
The same can be done for the following program expressing an approximation of Pi using a Gregory-Leibnitz series (of course, also runnable interactively):
- - - - - - -
1,5,1,0,1 set the first datum in memory to 1
2,5,2,0,2 set the second datum in memory to 2
3,5,0,0,3 set the nextpart (of division) to 0
4,10,3,1,4 set the sign changer to -1
5,6,0,4,5 set a minusone (-1) value
6,5,10,0,6 set a loop limit to 100 by
7,5,10,0,7 multiplying 10 * 10 (as an
8,11,6,7,6 example)
9,6,0,5,7 set the loop counter to -1 (later: 0)
19,5,0,0,8 set the loop comparison to 0
11,6,0,5,9 set the divisor to -1
12,5,0,0,10 set the final result to 0
13,9,7,1,7 increase the loop counter
14,10,6,7,8 if it has reached the loop limit
15,1,8,22,0 go to the section showing results
16,11,4,5,4 prepare sign change of intermediate
17,9,9,2,9 increase the divisor by 2 for interm.
18,11,9,4,3 assign sign to divisor (into nextpart)
19,12,1,3,3 turn nextpart into (sign)1/nextpart
20,9,10,3,10 add (accumulate) nextpart into result
21,1,0,13,6 main loop
22,9,10,10,10 the accumulated result is pi/4, so
23,9,10,10,10 increase the result fourfold by adding
24,1,0,219,6 jump to instruction space end: finish
0,1,0,1,6 trigger computation by immediate jump
2222,10 print the result
8888 exit 1V0 Tz IV
- - - - - - -
The above two programs are roughly equivalent to the following C-language expressions:
#include<stdio.h>
/* Wallis series */
float dividend = 0.0;
float divisor = 1.0;
float divisumA = 0.0;
float divisumB = 0.0;
float adddiv = 2.0;
int i;
float accum = 1.0;
int main(void) {
for (i = 99; i>0; i--) {
dividend = dividend + adddiv;
divisumA = dividend / divisor;
divisor = divisor + adddiv;
divisumB = dividend / divisor;
accum = accum * divisumA;
accum = accum * divisumB;
}
accum = accum + accum;
printf("%f\n", accum);
return 0;
}
#include<stdio.h>
/* Gregory-Leibniz series */
float divisor = -1.0;
float adddiv = 2.0;
int i;
float signchanger = -1.0;
float minusone = -1.0;
float accum = 0.0;
float nextpart = 0.0;
float one = 1.0;
int main(void) {
for (i = 0; i<100; i++) {
signchanger = signchanger * minusone;
divisor = divisor + adddiv;
nextpart = divisor * signchanger;
nextpart = one / nextpart;
accum = accum + nextpart;
}
accum = accum + accum;
accum = accum + accum;
printf("%f\n", accum);
return 0;
}