000: 1 |
001: 1001 |
002: 4 |
003: 15 |
004: 42 |
[1] is the contents of memory location 1, and [1] = 1001 in our example above.Instruction Format
[10] + 3 -> [11] means take the contents of location 10, add 3 to that value, and store it as the new contents of location [11]
[1] + [[2]] -> [1] means increment the contents of location 1 by the contents of the location whose address comes from the contents of location 2. So in the above example [1] is 1001, [2] is 4, so [[2]] is 42. So the new value of location 1, i.e. [1], is 1043.
So the instruction at address 1 above is: SUB, 4, 15, 42.
Opcode Op1 Op2 Op3 Action SUB (1001) A B C [A] - [B] -> [C] BGE (1002) A B T if [A] >= [B] branch to T
while runningNote the layers of fetching actual values to operate on!
if [PC] == 0: halt
SUB, A, B, C i.e. [A] - [B] -> [C]
if [ [PC] ] == SUB:
[ [ [PC]+1 ] ] - [ [ [PC]+2 ] ] -> [ [ [PC]+3 ] ]
[PC] + 4 -> [PC]
BGE, A, B, T i.e. if [A] >= [B] branch to T
if [PC] == BGE:
if [ [ [PC]+1 ] ] >= [ [ [PC]+2 ] ]
then [ [PC]+3 ] -> [PC]
else [PC]+4 -> [PC]
otherwise: halt
# |
# Start loading into memory at location 12 |
# |
@ = 12; |
# |
# Some simple useful constants |
# |
Zero: 0; |
NegOne: -1; |
|
# Arithmetic on symbols to initialize constants |
_far_delta: here - far; |
|
# Instead of naming constants, we can adopt the convention of |
# _x for the constant x, and _nx for the constant -x |
_n1: -1; |
_0: 0; |
_1: 1; |
_2: 2; |
|
# |
# Some variables |
# |
A: 10; |
B: 0; |
X: 42; |
Y: -42; |
Z: 0; |
|
# |
# Copy from location A to location B, i.e. [A] -> [B] |
# |
|
SUB, A, Zero, B; |
|
# |
# Increment location T by 1, i.e. [T] + 1 -> [T] |
# |
|
SUB, T, NegOne, T; |
|
# |
# Branch to location Dest: Dest -> [PC] |
# |
|
BGE, Zero, Zero, Dest; |
# ... |
Dest: |
|
# |
# Test if location X and location Y have the same values. I.e. |
# if [X] == [Y] then do something, otherwise do something else. |
# |
|
BGE, X, Y, ge; |
BGE, Zero, Zero, ne; |
ge: BGE, Y, X, eq; |
BGE, Zero, Zero, ne; |
eq: |
# do stuff if equal, branch when done |
BGE, Zero, Zero, done; |
ne: |
# do stuff if not equal, branch when done |
BGE, Zero, Zero, done; |
# ... |
done: |
|
# |
# Compute the min of location X and Y and store in location Z, |
# i.e. min( [X], [Y] ) -> [Z] |
# |
|
BGE, Y, X, xmin; |
ymin: SUB, Y, _0, Z; |
BGE, _0, _0, next; |
xmin: SUB, X, _0, Z; |
next: |
# ... |
|
# |
# Using the @ symbol, which has the value of the address of the |
# beginning of the instruction, you can eliminate having to |
# invent unique names for intermediate symbols. Best when |
# using a macro-processor like M4. |
# |
|
BGE, Y, X, @+16; |
SUB, Y, _0, Z; |
BGE, _0, _0, @+8; |
SUB, X, _0, Z; |
|
# |
# Relative branch 2 instructions after "here" by modifying PC. |
# During instruction execution PC is not changed until instruction |
# complete, which is why [_far_delta] is -12 and not -8. |
# |
|
SUB, _0, _0, OUT; |
here: SUB, PC, _far_delta, PC; |
|
near: SUB, _1, _0, OUT; |
SUB, PC, PC, PC; |
|
far: SUB, _2, _0, OUT; |
SUB, PC, PC, PC; |
|
# |
# Indexed load from element L[i] of an array: [L+i] -> [V] |
# |
|
SUB, _0, i, T; |
SUB, L_addr, T, load+1; |
load: SUB, 0, _0, V; |
# ... |
# index |
i: 0; |
# temp |
T: 0; |
# value to work with |
V: 0; |
# pointer to array L |
L_addr: L; |
# number of elements in L |
L_len: L_end - L; |
# start of array L |
L: |
@ = @ + 10; # 10 un-initialized locations for L |
# 1 past end of array L, so L_end - L is length of array |
L_end: ; |
|
|
Code listing: |
# |
# Start loading into memory at location 12 |
# |
000: @ = 12 |
# |
# Some simple useful constants |
# |
012: 0 Zero: 0 |
013: -1 NegOne: -1 |
# Arithmetic on symbols to initialize constants |
014: -12 _far_delta: here - far |
# Instead of naming constants, we can adopt the convention of |
# _x for the constant x, and _nx for the constant -x |
015: -1 _n1: -1 |
016: 0 _0: 0 |
017: 1 _1: 1 |
018: 2 _2: 2 |
# |
# Some variables |
# |
019: 10 A: 10 |
020: 0 B: 0 |
021: 42 X: 42 |
022: -42 Y: -42 |
023: 0 Z: 0 |
# |
# Copy from location A to location B, i.e. [A] -> [B] |
# |
024: 1001 19 12 20 SUB, A, Zero, B |
# |
# Increment location T by 1, i.e. [T] + 1 -> [T] |
# |
028: 1001 129 13 129 SUB, T, NegOne, T |
# |
# Branch to location Dest: Dest -> [PC] |
# |
032: 1002 12 12 36 BGE, Zero, Zero, Dest |
# ... |
036: Dest: |
# |
# Test if location X and location Y have the same values. I.e. |
# if [X] == [Y] then do something, otherwise do something else. |
# |
036: 1002 21 22 44 BGE, X, Y, ge |
040: 1002 12 12 56 BGE, Zero, Zero, ne |
044: 1002 22 21 52 ge: BGE, Y, X, eq |
048: 1002 12 12 56 BGE, Zero, Zero, ne |
052: eq: |
# do stuff if equal, branch when done |
052: 1002 12 12 60 BGE, Zero, Zero, done |
056: ne: |
# do stuff if not equal, branch when done |
056: 1002 12 12 60 BGE, Zero, Zero, done |
# ... |
060: done: |
# |
# Compute the min of location X and Y and store in location Z, |
# i.e. min( [X], [Y] ) -> [Z] |
# |
060: 1002 22 21 72 BGE, Y, X, xmin |
064: 1001 22 16 23 ymin: SUB, Y, _0, Z |
068: 1002 16 16 76 BGE, _0, _0, next |
072: 1001 21 16 23 xmin: SUB, X, _0, Z |
076: next: |
# ... |
# |
# Using the @ symbol, which has the value of the address of the |
# beginning of the instruction, you can eliminate having to |
# invent unique names for intermediate symbols. Best when |
# using a macro-processor like M4. |
# |
076: 1002 22 21 92 BGE, Y, X, @+16 |
080: 1001 22 16 23 SUB, Y, _0, Z |
084: 1002 16 16 92 BGE, _0, _0, @+8 |
088: 1001 21 16 23 SUB, X, _0, Z |
# |
# Relative branch 2 instructions after "here" by modifying PC. |
# During instruction execution PC is not changed until instruction |
# complete, which is why [_far_delta] is -12 and not -8. |
# |
092: 1001 16 16 -2 SUB, _0, _0, OUT |
096: 1001 0 14 0 here: SUB, PC, _far_delta, PC |
100: 1001 17 16 -2 near: SUB, _1, _0, OUT |
104: 1001 0 0 0 SUB, PC, PC, PC |
108: 1001 18 16 -2 far: SUB, _2, _0, OUT |
112: 1001 0 0 0 SUB, PC, PC, PC |
# |
# Indexed load from element L[i] of an array: [L+i] -> [V] |
# |
116: 1001 16 128 129 SUB, _0, i, T |
120: 1001 131 129 125 SUB, L_addr, T, load+1 |
124: 1001 0 16 130 load: SUB, 0, _0, V |
# ... |
# index |
128: 0 i: 0 |
129: |
# temp |
129: 0 T: 0 |
130: |
# value to work with |
130: 0 V: 0 |
131: |
# pointer to array L |
131: 133 L_addr: L |
132: |
# number of elements in L |
132: 10 L_len: L_end - L |
133: |
# start of array L |
133: L: |
133: @ = @ + 10 |
# 10 un-initialized locations for L |
# 1 past end of array L, so L_end - L is length of array |
143: L_end: |
143: |
|
|
Symbol Table |
143: @ |
19: A |
20: B |
1002: BGE |
-3: CIN |
-4: COUT |
36: Dest |
-1: IN |
133: L |
131: L_addr |
143: L_end |
132: L_len |
13: NegOne |
-2: OUT |
0: PC |
1001: SUB |
129: T |
130: V |
21: X |
22: Y |
23: Z |
12: Zero |
16: _0 |
17: _1 |
18: _2 |
14: _far_delta |
15: _n1 |
60: done |
52: eq |
108: far |
44: ge |
96: here |
128: i |
124: load |
56: ne |
100: near |
76: next |
72: xmin |
64: ymin |
|
|
|
|
Code listing: |
# Program to: |
# read integer and store in A, |
# read integer and store in B, |
# put the minimum of [A] and [B] into C, i.e. |
# min{ [A], [B] } -> [C] |
# Idiom to initialize PC with START addr |
000: 1 _:START |
# Read in two integers |
001: START: |
001: 1001 -1 33 35 SUB, IN, _0, A |
005: 1001 -1 33 36 SUB, IN, _0, B |
009: 1002 36 35 21 BGE, B, A, MinIsA |
# B is smaller |
013: 1001 36 33 37 SUB, B, _0, C |
017: 1002 33 33 25 BGE, _0, _0, Continue |
# A is smaller |
021: 1001 35 33 37 MinIsA: SUB, A, _0, C |
025: Continue: |
# Print the min |
025: 1001 37 33 -2 SUB, C, _0, OUT |
# Halt by setting PC to 0 |
029: 1001 33 33 0 SUB, _0, _0, PC |
# Some constants |
033: 0 _0: 0 |
034: 1 _1: 1 |
# Variables |
035: 0 A: 0 |
036: 0 B: 0 |
037: 0 C: 0 |
|
|
Symbol Table |
37: @ |
35: A |
36: B |
1002: BGE |
37: C |
-3: CIN |
-4: COUT |
25: Continue |
-1: IN |
21: MinIsA |
-2: OUT |
0: PC |
1: START |
1001: SUB |
0: _ |
33: _0 |
34: _1 |
|
|
|
|
Code listing: |
# Program to |
# read in n |
# sum the first n terms of 1+2+3+ ... and |
# put the result in sum |
# output sum |
# Idiom to initialize PC with START addr, so the constants |
# at the top are skipped. |
000: 7 _:START |
# Constants |
001: 0 _0: 0 |
002: 1 _1: 1 |
003: -1 _m1: -1 |
# Variables |
004: 0 n: 0 |
005: 0 sum: 0 |
# Working variables |
006: 0 i: 0 |
007: START: |
# Read n |
007: 1001 -1 1 4 SUB, IN, _0, n |
# initialize sum |
011: 1001 1 1 5 SUB, _0, _0, sum |
# loop i = 1 .. n |
015: 1001 4 3 4 SUB, n, _m1, n |
019: 1001 2 1 6 SUB, _1, _0, i |
023: loop_1_begin: |
023: 1002 6 4 47 BGE, i, n, loop_1_end |
# Sum i negatively |
027: 1001 5 6 5 SUB, sum, i, sum |
# Debug - print i and sum |
031: 1001 6 1 -2 SUB, i, _0, OUT |
035: 1001 5 1 -2 SUB, sum, _0, OUT |
# Increment i |
039: 1001 6 3 6 SUB, i, _m1, i |
# Back to top of loop |
043: 1002 1 1 23 BGE, _0, _0, loop_1_begin |
047: loop_1_end: |
# Flip sign of sum, and outpu |
047: 1001 1 5 5 SUB, _0, sum, sum |
051: 1001 5 1 -2 SUB, sum, _0, OUT |
# Halt by setting PC to 0 |
055: 1001 0 0 0 SUB, PC, PC, PC |
|
|
Symbol Table |
55: @ |
1002: BGE |
-3: CIN |
-4: COUT |
-1: IN |
-2: OUT |
0: PC |
7: START |
1001: SUB |
0: _ |
1: _0 |
2: _1 |
3: _m1 |
6: i |
23: loop_1_begin |
47: loop_1_end |
4: n |
5: sum |
|
|
|
|
Code listing: |
# Program to convert a non-negative integer x into its binary |
# represention |
# Reads an integer n and extracts its binary representation |
# Pre: integer n must be non-negative and < 2^nbits |
# Post: bit i of n will be placed into bits[i] |
# idiom to initialize PC with START addr |
000: 1 _:START |
001: START: |
# bits posn 0 1 ... (nbits-1) |
# bits value 2^0 2^1 ... 2^(nbits-1) |
# Maximum number of bits |
001: nbits = 8 |
# Begin by computing a table of the power of 2 associated with each |
# bit position. |
# Pointer p_pow2 to bit 0, incremented on each bit |
001: 1001 139 127 136 SUB, pow2_start_addr, _0, p_pow2 |
# Current power of 2 |
005: 1001 128 127 131 SUB, _1, _0, two_to_i |
# loop i = 0 .. n-1 times, using i as the index |
009: 1001 127 127 135 SUB, _0, _0, i |
013: 1002 135 134 45 loop_1_begin: BGE, i, n, loop_1_end |
# indirect store: [two_to_i] -> [ [p_pow2] ] |
# build instruction to store pow2 into that address |
017: 1001 136 127 24 SUB, p_pow2, _0, @+7 |
021: 1001 131 127 0 SUB, two_to_i, _0, 0 |
# double the power of 2 |
025: 1001 127 131 138 SUB, _0, two_to_i, tmp |
029: 1001 131 138 131 SUB, two_to_i, tmp, two_to_i |
# increment i and address of next pow2 table entry |
033: 1001 135 126 135 SUB, i, _n1, i |
037: 1001 136 126 136 SUB, p_pow2, _n1, p_pow2 |
# back to top of loop |
041: 1002 127 127 13 BGE, _0, _0, loop_1_begin |
045: loop_1_end: |
# Now read in a value and determine its bin rep |
045: 1001 -1 127 130 SUB, IN, _0, value |
049: 1001 130 127 132 SUB, value, _0, remain |
# end address is 1 beyond actual table, and we want to start |
# at last entries and work down |
053: 1001 140 128 136 SUB, pow2_end_addr, _1, p_pow2 |
057: 1001 142 128 137 SUB, bits_end_addr, _1, p_bits |
# Work in decreasing powers of 2 |
# loop i = 0 .. n-1 times, using i as the index |
061: 1001 127 127 135 SUB, _0, _0, i |
065: 1002 135 134 121 loop_2_begin: BGE, i, n, loop_2_end |
# indirect load: [ [p_pow2] ] -> [ two_to_i ] |
# build instruction to load 2^(n-1-i) into two_to_i |
069: 1001 136 127 74 SUB, p_pow2, _0, @+5 |
073: 1001 0 127 131 SUB, 0, _0, two_to_i |
# remain < pow => remain does not contain that power so bit is 0 |
# otw bit is 1 |
077: 1001 127 127 133 SUB, _0, _0, bit |
081: 1002 132 131 89 BGE, remain, two_to_i, bigger |
085: 1002 127 127 97 BGE, _0, _0, smaller |
089: bigger: |
089: 1001 128 127 133 SUB, _1, _0, bit |
# remove that power from remainder |
093: 1001 132 131 132 SUB, remain, two_to_i, remain |
097: smaller: |
# debug |
# SUB, i, _0, OUT; |
# SUB, a, _0, OUT; |
# SUB, b, _0, OUT; |
# SUB, two_to_i, _0, OUT; |
# SUB, bit, _0, OUT; |
# SUB, remain, _0, OUT; |
# SUB, _n1, _0, OUT; |
# build instruction to store the bit into the bits table. |
097: 1001 137 127 104 SUB, p_bits, _0, @+7 |
101: 1001 133 127 0 SUB, bit, _0, 0 |
# increment i and decrement address of current entry in power |
# table and bit value |
105: 1001 135 126 135 SUB, i, _n1, i |
109: 1001 136 128 136 SUB, p_pow2, _1, p_pow2 |
113: 1001 137 128 137 SUB, p_bits, _1, p_bits |
# back to top of loop |
117: 1002 127 127 65 BGE, _0, _0, loop_2_begin |
121: loop_2_end: |
# Halt by setting PC to 0 |
121: 1001 127 127 0 SUB, _0, _0, PC |
# Some constants. Note that if you put them in this order |
# then you can index off _0, for example, this stores 1 - (-1) |
# into tmp. |
# SUB, _0+1, _0-1, tmp; |
125: -2 _n2: -2 |
126: -1 _n1: -1 |
127: 0 _0: 0 |
128: 1 _1: 1 |
129: 2 _2: 2 |
# Input value to be converted |
130: 0 value: 0 |
# Current power 2^n |
131: 1 two_to_i: 1 |
# Remaining bits of value to be processed, should be 0 when |
# finished. |
132: 0 remain: 0 |
# Current bit value |
133: 0 bit: 0 |
# Variables |
# Number of bits expected |
134: 8 n: nbits |
135: |
# Index |
135: 0 i: 0 |
# Pointer to pow2 entry |
136: 0 p_pow2: 0 |
# Pointer to bits entry |
137: 0 p_bits: 0 |
# Volatile tmp value |
138: 0 tmp: 0 |
# the idiom to reserve space for a table of size k is |
# |
# table_start_addr: table - addr of element 0 |
# table_end_addr: table_end - 1 past element k |
# |
# table: ; |
# @ = @ + size; |
# table_end: ; |
# power table is [pow2_start_addr ... pow2_end_addr] inclusive |
139: 143 pow2_start_addr: pow2 |
140: 151 pow2_end_addr: pow2_end |
# bits table is [bits_start_addr ... bits_end_addr - 1] |
141: 151 bits_start_addr: bits |
142: 159 bits_end_addr: bits_end |
# powers of 2 table starts here, pow2[i] = 2^i 0 <= i < nbits |
143: pow2: |
143: @=@+nbits |
151: pow2_end: |
# bits of binary representation start here, bits[i] = value of bit i, |
# in little endian ordering. |
# bit[i] is value of bit representing 2^i |
151: bits: |
151: @=@+nbits |
159: bits_end: |
|
|
Symbol Table |
159: @ |
1002: BGE |
-3: CIN |
-4: COUT |
-1: IN |
-2: OUT |
0: PC |
1: START |
1001: SUB |
0: _ |
127: _0 |
128: _1 |
129: _2 |
126: _n1 |
125: _n2 |
89: bigger |
133: bit |
151: bits |
159: bits_end |
142: bits_end_addr |
141: bits_start_addr |
135: i |
13: loop_1_begin |
45: loop_1_end |
65: loop_2_begin |
121: loop_2_end |
134: n |
8: nbits |
137: p_bits |
136: p_pow2 |
143: pow2 |
151: pow2_end |
140: pow2_end_addr |
139: pow2_start_addr |
132: remain |
97: smaller |
138: tmp |
131: two_to_i |
130: value |
|
|
|
|
Code listing: |
# Division Algorithm |
# Given a >= 0, b > 0 compute |
# a = b * q + r, 0 <= r < b |
# q = a div b |
# r = a rem b |
# Based on the binary representation algorithm which in essence |
# computes a div 1. The bin rep algorithm reduces the remainder by the |
# largest possible power of 2 at each step. Instead of reducing by |
# 2^i, reducing by b * 2^1 extracts out 2^i multiples of b. The resulting |
# sequence of bits is the representation of q, and the remaining value |
# that is smaller than 2^0 * b is r. |
# idiom to initialize PC with START addr |
000: 1 _:START |
001: START: |
# bits posn 0 1 ... (nbits-1) |
# bits value 2^0 2^1 ... 2^(nbits-1) |
# Maximum number of bits - note this is an asm symbol not a mem cell |
001: nbits = 8 |
# Read in a and b |
001: 1001 -1 227 230 SUB, IN, _0, a |
005: 1001 -1 227 231 SUB, IN, _0, b |
# Make sure a is >= 0, b is > 0 |
009: 1002 230 227 17 BGE, a, _0, aok |
013: 1001 227 230 230 SUB, _0, a, a |
017: aok: |
017: 1002 231 228 29 BGE, b, _1, bok |
021: 1001 227 231 231 SUB, _0, b, b |
025: 1002 227 231 221 BGE, _0, b, error |
029: bok: |
# Begin by computing a table of the multiples of b scaled by powers of |
# 2 associated. |
# Pointer p_b2n to bit 0, incremented on each bit |
029: 1001 242 227 239 SUB, b2n_start_addr, _0, p_b2n |
# Current value of b * 2^1 |
033: 1001 231 227 234 SUB, b, _0, two_to_i |
# loop i = 0 .. n-1 times, using i as the index |
037: 1001 227 227 238 SUB, _0, _0, i |
041: 1002 238 237 73 loop_1_begin: BGE, i, n, loop_1_end |
# indirect store: [two_to_i] -> [ [p_b2n] ] |
# build instruction to store b2n into that address |
045: 1001 239 227 52 SUB, p_b2n, _0, @+7 |
049: 1001 234 227 0 SUB, two_to_i, _0, 0 |
# double the power of 2 |
053: 1001 227 234 241 SUB, _0, two_to_i, tmp |
057: 1001 234 241 234 SUB, two_to_i, tmp, two_to_i |
# increment i and address of next b2n table entry |
061: 1001 238 226 238 SUB, i, _n1, i |
065: 1001 239 226 239 SUB, p_b2n, _n1, p_b2n |
# back to top of loop |
069: 1002 227 227 41 BGE, _0, _0, loop_1_begin |
073: loop_1_end: |
# Initialize the remainder |
073: 1001 230 227 235 SUB, a, _0, remain |
# end address is 1 beyond actual table, and we want to start |
# at last entries and work down |
077: 1001 243 228 239 SUB, b2n_end_addr, _1, p_b2n |
081: 1001 245 228 240 SUB, bitsq_end_addr, _1, p_bitsq |
# Work in decreasing powers of 2 |
# loop i = 0 .. n-1 times, using i as the index |
085: 1001 227 227 238 SUB, _0, _0, i |
089: 1002 238 237 145 loop_2_begin: BGE, i, n, loop_2_end |
# indirect load: [ [p_b2n] ] -> [ two_to_i ] |
# build instruction to load 2^(n-1-i) into two_to_i |
093: 1001 239 227 98 SUB, p_b2n, _0, @+5 |
097: 1001 0 227 234 SUB, 0, _0, two_to_i |
# remain < pow => remain does not contain that power so bit is 0 |
# otw bit is 1 |
101: 1001 227 227 236 SUB, _0, _0, bit |
105: 1002 235 234 113 BGE, remain, two_to_i, bigger |
109: 1002 227 227 121 BGE, _0, _0, smaller |
113: bigger: |
113: 1001 228 227 236 SUB, _1, _0, bit |
# remove that power from remainder |
117: 1001 235 234 235 SUB, remain, two_to_i, remain |
121: smaller: |
# build instruction to store the bit into the bitsq table. |
121: 1001 240 227 128 SUB, p_bitsq, _0, @+7 |
125: 1001 236 227 0 SUB, bit, _0, 0 |
# increment i and decrement address of current entry in power |
# table and bit value |
129: 1001 238 226 238 SUB, i, _n1, i |
133: 1001 239 228 239 SUB, p_b2n, _1, p_b2n |
137: 1001 240 228 240 SUB, p_bitsq, _1, p_bitsq |
# back to top of loop |
141: 1002 227 227 89 BGE, _0, _0, loop_2_begin |
145: loop_2_end: |
145: 1001 235 227 233 SUB, remain, _0, r |
# Now reconstruct q from its bits |
149: 1001 245 228 240 SUB, bitsq_end_addr, _1, p_bitsq |
153: 1001 227 227 238 SUB, _0, _0, i |
157: 1001 227 227 232 SUB, _0, _0, q |
161: 1002 238 237 197 loop_3_begin: BGE, i, n, loop_3_end |
# we want to compute q <- 2 * q + bits[n-i] |
# tmp <- 0 - bits[n-i] - q - q |
# q <- 0 - tmp |
# compute -2q, i.e.shift left 1 bit |
165: 1001 227 232 241 SUB, _0, q, tmp |
169: 1001 241 232 241 SUB, tmp, q, tmp |
# build instruction to subtract bit[n-i] from tmp |
173: 1001 240 227 179 SUB, p_bitsq, _0, @+6 |
177: 1001 241 0 241 SUB, tmp, 0, tmp |
# and then q <- -tmp |
181: 1001 227 241 232 SUB, _0, tmp, q |
# increment i and decrement address of current entry in power |
# table and bit value |
185: 1001 238 226 238 SUB, i, _n1, i |
189: 1001 240 228 240 SUB, p_bitsq, _1, p_bitsq |
# back to top of loop |
193: 1002 227 227 161 BGE, _0, _0, loop_3_begin |
197: loop_3_end: |
197: 1001 230 227 -2 SUB, a, _0, OUT |
201: 1001 231 227 -2 SUB, b, _0, OUT |
205: 1001 232 227 -2 SUB, q, _0, OUT |
209: 1001 233 227 -2 SUB, r, _0, OUT |
# Keep asking for input until get error |
213: 1002 227 227 1 BGE, _0, _0, START |
# Halt by setting PC to 0 |
217: 1001 227 227 0 SUB, _0, _0, PC |
# Error |
221: 1001 227 227 0 error: SUB, _0, _0, PC |
# Some constants. Note that if you put them in this order |
# then you can index off _0, for example, this stores 1 - (-1) |
# into tmp. |
# SUB, _0+1, _0-1, tmp; |
225: -2 _n2: -2 |
226: -1 _n1: -1 |
227: 0 _0: 0 |
228: 1 _1: 1 |
229: 2 _2: 2 |
# Inputs a and b |
230: 0 a: 0 |
231: 0 b: 0 |
# Results q and r |
232: 0 q: 0 |
233: 0 r: 0 |
# Current power 2^n |
234: 1 two_to_i: 1 |
# Remainder bits to be reduced, should be < b when finished. |
235: 0 remain: 0 |
# Current bit value |
236: 0 bit: 0 |
# Variables |
# Number of bits expected |
237: 8 n: nbits |
# Index |
238: 0 i: 0 |
# Pointer to b2n entry |
239: 0 p_b2n: 0 |
# Pointer to bitsq entry |
240: 0 p_bitsq: 0 |
# Volatile tmp value |
241: 0 tmp: 0 |
# the idiom to reserve space for a table of size k is |
# |
# table_start_addr: table - addr of element 0 |
# table_end_addr: table_end - 1 past element k |
# |
# table: ; |
# @ = @ + size; |
# table_end: ; |
# power table is [b2n_start_addr ... b2n_end_addr] inclusive |
242: 246 b2n_start_addr: b2n |
243: 254 b2n_end_addr: b2n_end |
# bitsq table is [bitsq_start_addr ... bitsq_end_addr - 1] |
244: 254 bitsq_start_addr: bitsq |
245: 262 bitsq_end_addr: bitsq_end |
# b * 2^i table starts here, b2n[i] = b * 2^i 0 <= i < nbits |
246: b2n: |
246: @=@+nbits |
254: b2n_end: |
# bits of binary rep of q start here, bitsq[i] = value of bit i, |
# in little endian ordering. |
# bitq[i] is value of bit representing b * 2^i |
254: bitsq: |
254: @=@+nbits |
262: bitsq_end: |
|
|
Symbol Table |
262: @ |
1002: BGE |
-3: CIN |
-4: COUT |
-1: IN |
-2: OUT |
0: PC |
1: START |
1001: SUB |
0: _ |
227: _0 |
228: _1 |
229: _2 |
226: _n1 |
225: _n2 |
230: a |
17: aok |
231: b |
246: b2n |
254: b2n_end |
243: b2n_end_addr |
242: b2n_start_addr |
113: bigger |
236: bit |
254: bitsq |
262: bitsq_end |
245: bitsq_end_addr |
244: bitsq_start_addr |
29: bok |
221: error |
238: i |
41: loop_1_begin |
73: loop_1_end |
89: loop_2_begin |
145: loop_2_end |
161: loop_3_begin |
197: loop_3_end |
237: n |
8: nbits |
239: p_b2n |
240: p_bitsq |
232: q |
233: r |
235: remain |
121: smaller |
241: tmp |
234: two_to_i |
|
|
|
|
Code listing: |
# Program to show stack-based calling conventions |
# Read in n |
# Compute the following recursive function fn(n) |
# fn(0) = 1; |
# fn(n) = 2 * fn(n-1); |
# that computes fn(n) = 2 ** n |
# loc 0 is always PC by hardware |
# loc 1 is stack pointer sp by convention |
# loc 2 is address of bottom of stack, |
# grows upward to increasing addrs |
# Idiom to initialize PC with START addr |
000: 16 _:START |
001: 180 sp: stack |
002: 180 addr_stack: stack |
# Constants, convention is _ddddd or _nddddd. |
# e.g. _12 for 12, _n2 for -2 |
003: 0 _0: 0 |
004: 1 _1: 1 |
005: 2 _2: 2 |
006: 3 _3: 3 |
007: -1 _n1: -1 |
008: -2 _n2: -2 |
009: -3 _n3: -3 |
010: -8 _n8: -8 |
# We compute m = fn(n) = 2 ** n |
011: 0 n: 0 |
012: |
012: 0 m: 0 |
# count the number of calls |
013: 0 n_calls: 0 |
# Temp variables, be careful not to use across a fn call |
# just like a register |
014: 0 tmp1: 0 |
015: 0 result: 0 |
# Always reset the stack pointer when you start |
016: 1001 2 3 1 START: SUB, addr_stack, _0, sp |
# A stack frame looks like this: |
# [[sp]-2] - function param |
# [[sp]-1] - return result |
# [[sp]] - return address |
# Read n |
020: 1001 -1 3 11 SUB, IN, _0, n |
# Call fn(n), |
# Allocate space on the stack for param n, result m, return addr |
024: 1001 1 9 1 SUB, sp, _n3, sp |
# Put n into the param position at [sp]-2 |
# i.e. [ n ] -> [ [sp]-2 ] |
028: 1001 1 5 35 SUB, sp, _2, @+7 |
032: 1001 11 3 0 SUB, n, _0, 0 |
# Put 0 into the result position at [sp]-1 to 0 |
# i.e. 0 -> [ [sp]-1 ] |
036: 1001 1 4 43 SUB, sp, _1, @+7 |
040: 1001 3 3 0 SUB, _0, _0, 0 |
# Put return address (next instruction after the call) into |
# the return position at [sp]. |
044: 1001 1 3 51 SUB, sp, _0, @+7 |
048: 1001 0 10 0 SUB, PC, _n8, 0 |
# Call the function |
052: 1002 3 3 80 BGE, _0, _0, fn |
# Return here |
# Fetch result |
056: 1001 1 4 61 SUB, sp, _1, @+5 |
060: 1001 0 3 12 SUB, 0, _0, m |
# Pop stack frame: param, result, and return address |
064: 1001 1 6 1 SUB, sp, _3, sp |
# Print the result |
068: 1001 12 3 -2 SUB, m, _0, OUT |
# Print the the number of recursive calls |
072: 1001 13 3 -2 SUB, n_calls, _0, OUT |
# Halt by setting PC to 0 |
076: 1001 0 0 0 SUB, PC, PC, PC |
# Body of function fn |
# fn(0) = 1; |
# fn(n) = 2 * fn(n-1); |
080: fn: |
# Count calls |
080: 1001 13 7 13 SUB, n_calls, _n1, n_calls |
# Fetch the param n into tmp1, the global copy of n, not |
# safe over a function call |
084: 1001 1 5 89 SUB, sp, _2, @+5 |
088: 1001 0 3 14 SUB, 0, _0, tmp1 |
# Don't recurse if n <= 0; |
092: 1002 3 14 156 BGE, _0, tmp1, base_case |
# Recursive call, put new param on stack |
# Allocate space on the stack for param n, result m, return addr |
096: 1001 1 9 1 SUB, sp, _n3, sp |
# Put n-1 into the param position. |
100: 1001 1 5 107 SUB, sp, _2, @+7 |
104: 1001 14 4 0 SUB, tmp1, _1, 0 |
# Clear result location |
108: 1001 1 4 115 SUB, sp, _1, @+7 |
112: 1001 3 3 0 SUB, _0, _0, 0 |
# Return address |
116: 1001 1 3 123 SUB, sp, _0, @+7 |
120: 1001 0 10 0 SUB, PC, _n8, 0 |
# Call fn. |
124: 1002 3 3 80 BGE, _0, _0, fn |
# return here |
# Fetch result |
128: 1001 1 4 133 SUB, sp, _1, @+5 |
132: 1001 0 3 14 SUB, 0, _0, tmp1 |
# Pop stack frame |
136: 1001 1 6 1 SUB, sp, _3, sp |
# Multiply result by 2 |
# result is unsafe over function calls |
140: 1001 3 14 15 SUB, _0, tmp1, result |
144: 1001 15 14 15 SUB, result, tmp1, result |
148: 1001 3 15 15 SUB, _0, result, result |
152: 1002 3 3 160 BGE, _0, _0, done |
156: base_case: |
# Result is 1 |
156: 1001 4 3 15 SUB, _1, _0, result |
# now return to the caller |
160: done: |
# Return, stack to be cleaned up by caller who is only |
# one that knows how many things were put on the stack. |
# Put returned result into stack, just below return address |
160: 1001 1 4 167 SUB, sp, _1, @+7 |
164: 1001 15 3 0 SUB, result, _0, 0 |
# Get the return address from top of the stack |
# and install it into the branch |
168: 1001 1 3 173 SUB, sp, _0, @+5 |
172: 1001 0 3 179 SUB, 0, _0, @+7 |
176: 1002 3 3 0 BGE, _0, _0, 0 |
# Stack starts here |
180: 0 stack: 0 |
# Advance a bit so that the trace routine shows the stack cells |
# with labels |
181: @ = @ + 32 |
213: 0 stack_end: 0 |
|
|
Symbol Table |
213: @ |
1002: BGE |
-3: CIN |
-4: COUT |
-1: IN |
-2: OUT |
0: PC |
16: START |
1001: SUB |
0: _ |
3: _0 |
4: _1 |
5: _2 |
6: _3 |
7: _n1 |
8: _n2 |
9: _n3 |
10: _n8 |
2: addr_stack |
156: base_case |
160: done |
80: fn |
12: m |
11: n |
13: n_calls |
15: result |
1: sp |
180: stack |
213: stack_end |
14: tmp1 |
|
|
|
|
Code listing: |
# Program to show more complex stack-based calling conventions |
# Read in n |
# Compute fib(n) very inefficiently by |
# fib(0) = 0; |
# fib(1) = 1; |
# fib(n) = fib(n-2) + fib(n-1) for n >=2 |
# |
# n: 0 1 2 3 4 5 6 7 |
# Fib: 0 1 1 2 3 5 8 13 |
# loc 0 is always PC by hardware |
# loc 1 is stack pointer sp by convention |
# loc 2 is address of bottom of stack, |
# grows upward to increasing addrs |
# Idiom to initialize PC with START addr |
000: 17 _:START |
001: 285 sp: stack |
002: 285 addr_stack: stack |
# Constants, convention is _ddddd or _nddddd. |
# e.g. _12 for 12, _n2 for -2 |
003: 0 _0: 0 |
004: 1 _1: 1 |
005: 2 _2: 2 |
006: 3 _3: 3 |
007: -1 _n1: -1 |
008: -2 _n2: -2 |
009: -3 _n3: -3 |
010: -8 _n8: -8 |
# We compute m = fib(n) |
011: 0 n: 0 |
012: |
012: 0 m: 0 |
# count the number of calls |
013: 0 n_calls: 0 |
# Temp variables, be careful not to use across a function call |
# just like a register |
014: 0 tmp_r: 0 |
015: 0 tmp_n: 0 |
016: 0 result: 0 |
# Always reset the stack pointer when you start |
017: 1001 2 3 1 START: SUB, addr_stack, _0, sp |
# A stack frame on call looks like this: |
# [[sp]-2] - function param n |
# [[sp]-1] - return result fib(n) |
# [[sp]] - return address |
# During execution after entry and allocating local variables |
# frame, the stack looks like this:: |
# [[sp]-4] - function param n |
# [[sp]-3] - return result |
# [[sp]-2] - return address |
# [[sp]-1] - value of n on initial entry |
# [[sp]] - value of fib(n-2) |
# Read n |
021: 1001 -1 3 11 SUB, IN, _0, n |
# Call fib(n), |
# Allocate space on the stack for param n, result m, return addr |
025: 1001 1 9 1 SUB, sp, _n3, sp |
# Put n into the param position at [sp]-2 |
# i.e. [ n ] -> [ [sp]-2 ] |
029: 1001 1 5 36 SUB, sp, _2, @+7 |
033: 1001 11 3 0 SUB, n, _0, 0 |
# Put 0 into the result position at [sp]-1 to 0 |
# i.e. 0 -> [ [sp]-1 ] |
037: 1001 1 4 44 SUB, sp, _1, @+7 |
041: 1001 3 3 0 SUB, _0, _0, 0 |
# Put return address (next instruction after the call) into |
# the return position at [sp]. |
045: 1001 1 3 52 SUB, sp, _0, @+7 |
049: 1001 0 10 0 SUB, PC, _n8, 0 |
# Call the function |
053: 1002 3 3 85 BGE, _0, _0, fib |
# Return here |
# Fetch result |
057: 1001 1 4 62 SUB, sp, _1, @+5 |
061: 1001 0 3 12 SUB, 0, _0, m |
# Pop stack frame: param, result, and return address |
065: 1001 1 6 1 SUB, sp, _3, sp |
# Print n |
069: 1001 11 3 -2 SUB, n, _0, OUT |
# Print the result |
073: 1001 12 3 -2 SUB, m, _0, OUT |
# Print the the number of recursive calls, negated |
077: 1001 3 13 -2 SUB, _0, n_calls, OUT |
# Halt by setting PC to 0 |
081: 1001 0 0 0 SUB, PC, PC, PC |
# |
# Body of function fib |
# |
085: fib: |
# Count calls |
085: 1001 13 7 13 SUB, n_calls, _n1, n_calls |
# Fetch the param into tmp_n, a global temp not safe over |
# function calls |
089: 1001 1 5 94 SUB, sp, _2, @+5 |
093: 1001 0 3 15 SUB, 0, _0, tmp_n |
# Only recurse if n > 2 |
097: 1002 3 15 257 BGE, _0, tmp_n, basecase_0 |
101: 1002 4 15 249 BGE, _1, tmp_n, basecase_1 |
# We need to store n and fib(n-2) across function calls. So we |
# need to allocate two local variables on the stack to hold |
# n |
# fib(n-2) |
105: 1001 1 8 1 SUB, sp, _n2, sp |
109: 1001 1 3 116 SUB, sp, _0, @+7 |
113: 1001 3 3 0 SUB, _0, _0, 0 |
117: 1001 1 4 124 SUB, sp, _1, @+7 |
121: 1001 15 3 0 SUB, tmp_n, _0, 0 |
# Recursive call, put new param on stack |
# Allocate space on the stack for param n, result m, return addr |
125: 1001 1 9 1 SUB, sp, _n3, sp |
# Put n-2 into the param position. |
129: 1001 1 5 136 SUB, sp, _2, @+7 |
133: 1001 15 5 0 SUB, tmp_n, _2, 0 |
# Clear result location |
137: 1001 1 4 144 SUB, sp, _1, @+7 |
141: 1001 3 3 0 SUB, _0, _0, 0 |
# Return address |
145: 1001 1 3 152 SUB, sp, _0, @+7 |
149: 1001 0 10 0 SUB, PC, _n8, 0 |
# Call fib. |
153: 1002 3 3 85 BGE, _0, _0, fib |
# return here |
# Fetch result fib(n-1) |
157: 1001 1 4 162 SUB, sp, _1, @+5 |
161: 1001 0 3 14 SUB, 0, _0, tmp_r |
# Pop stack frame |
165: 1001 1 6 1 SUB, sp, _3, sp |
# Save fib(n-2) in local stack variable |
169: 1001 1 3 176 SUB, sp, _0, @+7 |
173: 1001 14 3 0 SUB, tmp_r, _0, 0 |
# Get n-1 |
177: 1001 1 4 182 SUB, sp, _1, @+5 |
181: 1001 0 4 15 SUB, 0, _1, tmp_n |
# Call fib(n-1) |
# Allocate space on the stack for param n, result m, return addr |
185: 1001 1 9 1 SUB, sp, _n3, sp |
# Put n-1 into the param position. |
189: 1001 1 5 196 SUB, sp, _2, @+7 |
193: 1001 15 3 0 SUB, tmp_n, _0, 0 |
# Clear result location |
197: 1001 1 4 204 SUB, sp, _1, @+7 |
201: 1001 3 3 0 SUB, _0, _0, 0 |
# Return address |
205: 1001 1 3 212 SUB, sp, _0, @+7 |
209: 1001 0 10 0 SUB, PC, _n8, 0 |
# Call fib. |
213: 1002 3 3 85 BGE, _0, _0, fib |
# return here |
# Fetch result of fib(n-1) |
217: 1001 1 4 222 SUB, sp, _1, @+5 |
221: 1001 0 3 16 SUB, 0, _0, result |
# Pop stack frame |
225: 1001 1 6 1 SUB, sp, _3, sp |
# Fetch saved fib(n-2), negated |
229: 1001 1 3 235 SUB, sp, _0, @+6 |
233: 1001 3 0 14 SUB, _0, 0, tmp_r |
# fib(n-1) + fib(n-2) |
237: 1001 16 14 16 SUB, result, tmp_r, result |
# Pop local variables |
241: 1001 1 5 1 SUB, sp, _2, sp |
245: 1002 3 3 265 BGE, _0, _0, done |
249: basecase_1: |
# n=1 Result is 1 |
249: 1001 4 3 16 SUB, _1, _0, result |
253: 1002 3 3 265 BGE, _0, _0, done |
257: basecase_0: |
# n=0 Result is 0 |
257: 1001 3 3 16 SUB, _0, _0, result |
261: 1002 3 3 265 BGE, _0, _0, done |
# now return to the caller |
265: done: |
# Return, stack to be cleaned up by caller who is only |
# one that knows how many things were put on the stack. |
# Put returned result into stack, just below return address |
265: 1001 1 4 272 SUB, sp, _1, @+7 |
269: 1001 16 3 0 SUB, result, _0, 0 |
# Get the return address from top of the stack |
# and install it into the branch |
273: 1001 1 3 278 SUB, sp, _0, @+5 |
277: 1001 0 3 284 SUB, 0, _0, @+7 |
281: 1002 3 3 0 BGE, _0, _0, 0 |
# Stack starts here |
285: 0 stack: 0 |
# Advance a bit so that the trace routine shows the stack cells |
# with labels |
286: @ = @ + 32 |
318: 0 stack_end: 0 |
|
|
Symbol Table |
318: @ |
1002: BGE |
-3: CIN |
-4: COUT |
-1: IN |
-2: OUT |
0: PC |
17: START |
1001: SUB |
0: _ |
3: _0 |
4: _1 |
5: _2 |
6: _3 |
7: _n1 |
8: _n2 |
9: _n3 |
10: _n8 |
2: addr_stack |
257: basecase_0 |
249: basecase_1 |
265: done |
85: fib |
12: m |
11: n |
13: n_calls |
16: result |
1: sp |
285: stack |
318: stack_end |
15: tmp_n |
14: tmp_r |
|
|
Opcode Op1 Op2 Op3 Op4 Action SAB (1003) A B C T [A] - [B] -> [C]
if [C] >= 0 branch to T
But that means that we only need the SAB instruction. We could build a new machine, the Z-machine that has only one instruction. So it does not require an opcode - thus the Z for Zero opcode machine. So a program is just a string of addresses, every sequence of 4 address being a legitimate instruction! The SUB and BGE instructions in the Z-machine are implemented as:
SUB, A, B, C => SAB, A, B, C, @+5 BGE, A, B, T => SAB, A, B, TMP, T
This makes for an interesting code generation and optimization exercise.
SUB, A, B, C => A, B, C, @+4 BGE, A, B, T => A, B, TMP, T