taracpu 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. src/__init__.py +1 -0
  2. src/assembler/__init__.py +1 -0
  3. src/assembler/asm.py +241 -0
  4. src/assets/delete.jpeg +0 -0
  5. src/assets/logo/logo1.png +0 -0
  6. src/assets/logo/logo2.png +0 -0
  7. src/assets/logo/logo2_cropped.png +0 -0
  8. src/assets/logo/logo2_text.png +0 -0
  9. src/assets/rename.png +0 -0
  10. src/examples/__init__.py +1 -0
  11. src/examples/examples.py +17 -0
  12. src/examples/progs/binary_search.tara +71 -0
  13. src/examples/progs/display_bouncing_ball.tara +349 -0
  14. src/examples/progs/display_rectangle.tara +70 -0
  15. src/examples/progs/display_triangle.tara +80 -0
  16. src/examples/progs/drawline.tara +380 -0
  17. src/examples/progs/factorial_via_mul_n_6.tara +9 -0
  18. src/examples/progs/fibonacci_n_10.tara +14 -0
  19. src/examples/progs/gcd_a_48_b_18.tara +12 -0
  20. src/examples/progs/generate_prime.tara +62 -0
  21. src/examples/progs/image.tara +18 -0
  22. src/examples/progs/matrix_multiplication.tara +224 -0
  23. src/examples/progs/ping_pong.tara +206 -0
  24. src/examples/progs/prime_test_n_17.tara +19 -0
  25. src/examples/progs/sort_bubble.tara +49 -0
  26. src/examples/progs/sort_insertion.tara +52 -0
  27. src/examples/progs/testprog.tara +1 -0
  28. src/paths.py +30 -0
  29. src/run_sim.py +14 -0
  30. src/simulation/__init__.py +1 -0
  31. src/simulation/cpu.py +246 -0
  32. src/ui/__init__.py +1 -0
  33. src/ui/app.py +673 -0
  34. src/ui/assets.py +49 -0
  35. src/ui/isa_dialog.py +121 -0
  36. src/ui/panels.py +2988 -0
  37. src/ui/theme.py +322 -0
  38. taracpu/__init__.py +3 -0
  39. taracpu/__main__.py +3 -0
  40. taracpu-1.0.0.dist-info/METADATA +105 -0
  41. taracpu-1.0.0.dist-info/RECORD +44 -0
  42. taracpu-1.0.0.dist-info/WHEEL +5 -0
  43. taracpu-1.0.0.dist-info/entry_points.txt +2 -0
  44. taracpu-1.0.0.dist-info/top_level.txt +2 -0
src/__init__.py ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1 @@
1
+
src/assembler/asm.py ADDED
@@ -0,0 +1,241 @@
1
+ """
2
+ TARA Assembler
3
+ Converts TARA assembly text into 16-bit machine words.
4
+ Supports labels, comments (;), and all instruction formats.
5
+ """
6
+
7
+ import re
8
+ from src.simulation.cpu import OP, FMT, sext
9
+
10
+ class AssemblerError(Exception):
11
+ def __init__(self, msg, line=None):
12
+ self.msg = msg
13
+ self.line = line
14
+ super().__init__(msg)
15
+
16
+ def __str__(self):
17
+ return f"Line {self.line}: {self.msg}" if self.line else self.msg
18
+
19
+
20
+ def parse_reg(tok):
21
+ """Parse Rn or rn → integer 0-7."""
22
+ tok = tok.strip().upper()
23
+ if tok.startswith('R') and tok[1:].isdigit():
24
+ n = int(tok[1:])
25
+ if 0 <= n <= 7:
26
+ return n
27
+ raise AssemblerError(f"Invalid register '{tok}'")
28
+
29
+
30
+ def parse_int(tok, label_map=None, pc=None):
31
+ """Parse a literal integer or label reference."""
32
+ tok = tok.strip()
33
+ if tok.startswith('0x') or tok.startswith('0X'):
34
+ return int(tok, 16)
35
+ if tok.startswith('0b') or tok.startswith('0B'):
36
+ return int(tok, 2)
37
+ if tok.lstrip('-').isdigit():
38
+ return int(tok)
39
+ if label_map is not None and tok in label_map:
40
+ return label_map[tok]
41
+ raise AssemblerError(f"Cannot parse value '{tok}'")
42
+
43
+
44
+ def parse_mem(tok):
45
+ """Parse off(Rbase) → (offset_str, reg_str)."""
46
+ m = re.match(r'^(-?\d+|0x[0-9a-fA-F]+)\(([Rr]\d)\)$', tok.strip())
47
+ if not m:
48
+ raise AssemblerError(f"Invalid memory operand '{tok}'")
49
+ return m.group(1), m.group(2)
50
+
51
+
52
+ OPERAND_COUNTS = {
53
+ 'NOP': 0, 'HLT': 0, 'RET': 0,
54
+ 'ADD': 3, 'SUB': 3, 'MUL': 3, 'AND': 3, 'OR': 3, 'XOR': 3, 'SLT': 3,
55
+ 'MOV': 2, 'NOT': 2,
56
+ 'LIL': 2, 'LIH': 2, 'ADDI': 2, 'SHL': 2, 'SHR': 2,
57
+ 'LDW': 2, 'LDB': 2, 'STW': 2, 'STB': 2,
58
+ 'BZ': 2, 'BN': 2,
59
+ 'JMP': 1, 'CALL': 1,
60
+ 'PUSH': 1, 'POP': 1,
61
+ }
62
+
63
+
64
+ def _parse_operands(ops_str, mnem):
65
+ operands = [o.strip() for o in ops_str.split(',') if o.strip()]
66
+ expected = OPERAND_COUNTS[mnem]
67
+ if len(operands) != expected:
68
+ suffix = "" if expected == 1 else "s"
69
+ raise AssemblerError(
70
+ f"{mnem} expects {expected} operand{suffix}, got {len(operands)}"
71
+ )
72
+ return operands
73
+
74
+
75
+ def _label_rel(target_s, iaddr, label_map):
76
+ """Resolve a branch/jump target to a signed word-offset relative to iaddr+2."""
77
+ if target_s in label_map:
78
+ return (label_map[target_s] - (iaddr + 2)) // 2
79
+ return parse_int(target_s, label_map)
80
+
81
+
82
+ def _encode_f0(op):
83
+ return (op << 11)
84
+
85
+ def _encode_f1(op, rd, rA, rB):
86
+ return (op << 11) | (rd << 8) | (rA << 5) | (rB << 2)
87
+
88
+ def _encode_f2(op, rd, rs):
89
+ return (op << 11) | (rd << 8) | (rs << 5)
90
+
91
+ def _encode_f3(op, rd, imm8):
92
+ imm8 = imm8 & 0xFF
93
+ return (op << 11) | (rd << 8) | imm8
94
+
95
+ def _encode_f4(op, rdata, rbase, off5):
96
+ off5 = off5 & 0x1F
97
+ return (op << 11) | (rdata << 8) | (rbase << 5) | off5
98
+
99
+ def _encode_f5(op, rtest, rel8):
100
+ rel8 = rel8 & 0xFF
101
+ return (op << 11) | (rtest << 8) | rel8
102
+
103
+ def _encode_f6(op, rel11):
104
+ rel11 = rel11 & 0x7FF
105
+ return (op << 11) | rel11
106
+
107
+ def _encode_f7(op, rstk):
108
+ return (op << 11) | (rstk << 8)
109
+
110
+
111
+ def assemble(source):
112
+ """
113
+ Assemble source text → (words, listing, errors)
114
+ words : list of (byte_addr, 16-bit int)
115
+ listing: list of dict per assembled instruction
116
+ errors : list of AssemblerError
117
+ """
118
+ lines = source.splitlines()
119
+ errors = []
120
+ label_map = {} # label → byte address
121
+ parsed = [] # list of (lineno, addr, mnemonic, operands_str, original)
122
+
123
+ # ── Pass 1: strip comments, collect labels, count addresses ───────────────
124
+ addr = 0
125
+ for lineno, raw in enumerate(lines, 1):
126
+ line = raw.split(';')[0].strip()
127
+ if not line:
128
+ continue
129
+
130
+ # Extract label(s)
131
+ while ':' in line:
132
+ colon = line.index(':')
133
+ label = line[:colon].strip()
134
+ if label:
135
+ label_map[label] = addr
136
+ line = line[colon+1:].strip()
137
+
138
+ if not line:
139
+ continue
140
+
141
+ parts = line.split(None, 1)
142
+ mnem = parts[0].upper()
143
+ ops = parts[1].strip() if len(parts) > 1 else ''
144
+
145
+ if mnem not in OP:
146
+ errors.append(AssemblerError(f"Unknown mnemonic '{mnem}'", lineno))
147
+ continue
148
+
149
+ parsed.append((lineno, addr, mnem, ops, raw.rstrip()))
150
+ addr += 2
151
+
152
+ # ── Pass 2: encode instructions ───────────────────────────────────────────
153
+ words = []
154
+ listing = []
155
+
156
+ for (lineno, iaddr, mnem, ops_str, original) in parsed:
157
+ opcode = OP[mnem]
158
+ word = 0
159
+ err = None
160
+
161
+ try:
162
+ operands = _parse_operands(ops_str, mnem)
163
+ if mnem in ('NOP', 'HLT', 'RET'):
164
+ word = _encode_f0(opcode)
165
+
166
+ elif mnem in ('ADD', 'SUB', 'MUL', 'AND', 'OR', 'XOR', 'SLT'):
167
+ # rd, rA, rB
168
+ rd = parse_reg(operands[0])
169
+ rA = parse_reg(operands[1])
170
+ rB = parse_reg(operands[2])
171
+ word = _encode_f1(opcode, rd, rA, rB)
172
+
173
+ elif mnem in ('MOV', 'NOT'):
174
+ # rd, rs
175
+ rd = parse_reg(operands[0])
176
+ rs = parse_reg(operands[1])
177
+ word = _encode_f2(opcode, rd, rs)
178
+
179
+ elif mnem in ('LIL', 'LIH', 'ADDI'):
180
+ rd = parse_reg(operands[0])
181
+ imm = parse_int(operands[1], label_map)
182
+ word = _encode_f3(opcode, rd, imm)
183
+
184
+ elif mnem in ('SHL', 'SHR'):
185
+ # rd, shamt
186
+ rd = parse_reg(operands[0])
187
+ shamt = parse_int(operands[1], label_map)
188
+ word = _encode_f3(opcode, rd, shamt)
189
+
190
+ elif mnem in ('LDW', 'LDB'):
191
+ # rd, off(rbase)
192
+ rd = parse_reg(operands[0])
193
+ off_s, base_s = parse_mem(operands[1])
194
+ off = parse_int(off_s, label_map)
195
+ rbase = parse_reg(base_s)
196
+ word = _encode_f4(opcode, rd, rbase, off)
197
+
198
+ elif mnem in ('STW', 'STB'):
199
+ # rs, off(rbase)
200
+ rs = parse_reg(operands[0])
201
+ off_s, base_s = parse_mem(operands[1])
202
+ off = parse_int(off_s, label_map)
203
+ rbase = parse_reg(base_s)
204
+ word = _encode_f4(opcode, rs, rbase, off)
205
+
206
+ elif mnem in ('BZ', 'BN'):
207
+ rtest = parse_reg(operands[0])
208
+ rel = _label_rel(operands[1], iaddr, label_map)
209
+ word = _encode_f5(opcode, rtest, rel)
210
+
211
+ elif mnem in ('JMP', 'CALL'):
212
+ rel = _label_rel(operands[0], iaddr, label_map)
213
+ word = _encode_f6(opcode, rel)
214
+
215
+ elif mnem in ('PUSH', 'POP'):
216
+ rs = parse_reg(operands[0])
217
+ word = _encode_f7(opcode, rs)
218
+
219
+ else:
220
+ raise AssemblerError(f"Unhandled mnemonic '{mnem}'")
221
+
222
+ except (AssemblerError, ValueError, IndexError) as e:
223
+ if not isinstance(e, AssemblerError):
224
+ e = AssemblerError(str(e))
225
+ e.line = lineno
226
+ errors.append(e)
227
+ err = e
228
+ word = 0xDEAD
229
+
230
+ words.append((iaddr, word))
231
+ listing.append({
232
+ 'lineno': lineno,
233
+ 'addr': iaddr,
234
+ 'word': word,
235
+ 'mnem': mnem,
236
+ 'ops': ops_str,
237
+ 'original': original,
238
+ 'error': bool(err),
239
+ })
240
+
241
+ return words, listing, errors, label_map
src/assets/delete.jpeg ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
src/assets/rename.png ADDED
Binary file
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,17 @@
1
+ from pathlib import Path
2
+
3
+ from src.paths import PROGS_DIR
4
+
5
+
6
+ def _display_name(path):
7
+ return path.stem.replace('_', ' ').title()
8
+
9
+
10
+ def load_examples():
11
+ examples = {}
12
+ progs = Path(PROGS_DIR)
13
+ if not progs.exists():
14
+ return examples
15
+ for path in sorted(progs.glob('*.tara')):
16
+ examples[_display_name(path)] = path.read_text(encoding='utf-8', errors='replace')
17
+ return examples
@@ -0,0 +1,71 @@
1
+ ; Binary search for a sorted byte array.
2
+ ;
3
+ ; Interface at label "search":
4
+ ; R0 = starting byte address of sorted array
5
+ ; R1 = number of elements
6
+ ; R2 = search key
7
+ ;
8
+ ; Return value:
9
+ ; R3 = index of matching element, or 0xFFFF if not found
10
+ ;
11
+ ; The demo setup below searches for 7 in [1,3,5,7,9,11] at 0x0400.
12
+ ; After HLT, R3 contains 3.
13
+
14
+ ; ---- Demo setup -----------------------------------------------------
15
+ LIL R0, 0
16
+ LIH R0, 4 ; R0 = 0x0400
17
+ LIL R1, 6 ; N = 6 byte elements
18
+ LIL R2, 7 ; key = 7
19
+
20
+ LIL R3, 1
21
+ STB R3, 0(R0)
22
+ LIL R3, 3
23
+ STB R3, 1(R0)
24
+ LIL R3, 5
25
+ STB R3, 2(R0)
26
+ LIL R3, 7
27
+ STB R3, 3(R0)
28
+ LIL R3, 9
29
+ STB R3, 4(R0)
30
+ LIL R3, 11
31
+ STB R3, 5(R0)
32
+
33
+ ; ---- Binary search --------------------------------------------------
34
+ ; R3 = result index
35
+ ; R4 = low index
36
+ ; R5 = high index
37
+ ; R6 = mid index
38
+ ; R7 = scratch / loaded array value
39
+
40
+ search: LIL R3, 255
41
+ LIH R3, 255 ; default result = 0xFFFF (not found)
42
+ LIL R4, 0 ; low = 0
43
+ MOV R5, R1
44
+ ADDI R5, -1 ; high = N - 1
45
+
46
+ loop: SUB R7, R5, R4
47
+ BN R7, done ; high < low
48
+
49
+ MOV R6, R4
50
+ ADD R6, R6, R5
51
+ SHR R6, 1 ; mid = (low + high) / 2
52
+
53
+ ADD R7, R0, R6
54
+ LDB R7, 0(R7) ; R7 = A[mid]
55
+ SUB R7, R7, R2 ; compare A[mid] - key
56
+
57
+ BZ R7, found
58
+ BN R7, go_right ; A[mid] < key
59
+
60
+ MOV R5, R6 ; A[mid] > key, high = mid - 1
61
+ ADDI R5, -1
62
+ JMP loop
63
+
64
+ go_right:
65
+ MOV R4, R6 ; low = mid + 1
66
+ ADDI R4, 1
67
+ JMP loop
68
+
69
+ found: MOV R3, R6
70
+
71
+ done: HLT
@@ -0,0 +1,349 @@
1
+ ; Bouncing glyph demo for the TARA memory-mapped display.
2
+ ;
3
+ ; Display map:
4
+ ; 0x0600-0x07FF framebuffer
5
+ ; 64x64 pixels, 1 bit per pixel, 8 horizontal pixels per byte
6
+ ; framebuffer row stride = 8 bytes
7
+ ;
8
+ ; Glyph map:
9
+ ; 0x0400 byte glyph width in pixels
10
+ ; 0x0401 byte glyph height in pixels
11
+ ; 0x0402... packed 1-bit row-major glyph bytes
12
+ ;
13
+ ; Glyph rows are stored top-to-bottom. Each row is padded to a whole
14
+ ; number of bytes: row_bytes = ceil(width / 8). This matches the image
15
+ ; import tool's "Glyph asset" format.
16
+ ;
17
+ ; Animation state:
18
+ ; 0x0580 byte x byte-column, so x pixel = x * 8
19
+ ; 0x0581 byte y bottom row
20
+ ; 0x0582 byte dx, 0=left, 1=right
21
+ ; 0x0583 byte dy, 0=down, 1=up
22
+ ;
23
+ ; The blitter is byte-aligned horizontally. Import a ball glyph at
24
+ ; 0x0400, or let this program create a default 16x16 ball there.
25
+
26
+ ; ---- Ensure a glyph exists at 0x0400 -------------------------------
27
+ LIL R6, 0
28
+ LIH R6, 4 ; R6 = 0x0400 glyph base
29
+ LDB R0, 0(R6) ; width
30
+ BZ R0, init_glyph
31
+ JMP init_state
32
+
33
+ ; Default 16x16 ball glyph. Header = width,height, then 16 rows x 2 bytes.
34
+ init_glyph:
35
+ LIL R6, 0
36
+ LIH R6, 4
37
+ LIL R0, 16
38
+ STB R0, 0(R6) ; width
39
+ STB R0, 1(R6) ; height
40
+ ADDI R6, 2
41
+
42
+ LIL R0, 3
43
+ STB R0, 0(R6)
44
+ ADDI R6, 1
45
+ LIL R0, 192
46
+ STB R0, 0(R6)
47
+ ADDI R6, 1
48
+
49
+ LIL R0, 15
50
+ STB R0, 0(R6)
51
+ ADDI R6, 1
52
+ LIL R0, 240
53
+ STB R0, 0(R6)
54
+ ADDI R6, 1
55
+
56
+ LIL R0, 31
57
+ STB R0, 0(R6)
58
+ ADDI R6, 1
59
+ LIL R0, 248
60
+ STB R0, 0(R6)
61
+ ADDI R6, 1
62
+
63
+ LIL R0, 63
64
+ STB R0, 0(R6)
65
+ ADDI R6, 1
66
+ LIL R0, 252
67
+ STB R0, 0(R6)
68
+ ADDI R6, 1
69
+
70
+ LIL R0, 127
71
+ STB R0, 0(R6)
72
+ ADDI R6, 1
73
+ LIL R0, 254
74
+ STB R0, 0(R6)
75
+ ADDI R6, 1
76
+
77
+ LIL R0, 127
78
+ STB R0, 0(R6)
79
+ ADDI R6, 1
80
+ LIL R0, 254
81
+ STB R0, 0(R6)
82
+ ADDI R6, 1
83
+
84
+ LIL R0, 255
85
+ STB R0, 0(R6)
86
+ ADDI R6, 1
87
+ STB R0, 0(R6)
88
+ ADDI R6, 1
89
+
90
+ LIL R0, 255
91
+ STB R0, 0(R6)
92
+ ADDI R6, 1
93
+ STB R0, 0(R6)
94
+ ADDI R6, 1
95
+
96
+ LIL R0, 255
97
+ STB R0, 0(R6)
98
+ ADDI R6, 1
99
+ STB R0, 0(R6)
100
+ ADDI R6, 1
101
+
102
+ LIL R0, 255
103
+ STB R0, 0(R6)
104
+ ADDI R6, 1
105
+ STB R0, 0(R6)
106
+ ADDI R6, 1
107
+
108
+ LIL R0, 127
109
+ STB R0, 0(R6)
110
+ ADDI R6, 1
111
+ LIL R0, 254
112
+ STB R0, 0(R6)
113
+ ADDI R6, 1
114
+
115
+ LIL R0, 127
116
+ STB R0, 0(R6)
117
+ ADDI R6, 1
118
+ LIL R0, 254
119
+ STB R0, 0(R6)
120
+ ADDI R6, 1
121
+
122
+ LIL R0, 63
123
+ STB R0, 0(R6)
124
+ ADDI R6, 1
125
+ LIL R0, 252
126
+ STB R0, 0(R6)
127
+ ADDI R6, 1
128
+
129
+ LIL R0, 31
130
+ STB R0, 0(R6)
131
+ ADDI R6, 1
132
+ LIL R0, 248
133
+ STB R0, 0(R6)
134
+ ADDI R6, 1
135
+
136
+ LIL R0, 15
137
+ STB R0, 0(R6)
138
+ ADDI R6, 1
139
+ LIL R0, 240
140
+ STB R0, 0(R6)
141
+ ADDI R6, 1
142
+
143
+ LIL R0, 3
144
+ STB R0, 0(R6)
145
+ ADDI R6, 1
146
+ LIL R0, 192
147
+ STB R0, 0(R6)
148
+
149
+ ; ---- Initial state --------------------------------------------------
150
+ init_state:
151
+ LIL R7, 128
152
+ LIH R7, 5 ; R7 = 0x0580 state base
153
+
154
+ LIL R0, 0
155
+ STB R0, 0(R7) ; x byte-column = 0
156
+ STB R0, 1(R7) ; y bottom row = 0
157
+
158
+ LIL R0, 1
159
+ STB R0, 2(R7) ; dx = right
160
+ STB R0, 3(R7) ; dy = up
161
+
162
+ ; ---- Animation loop -------------------------------------------------
163
+ main_loop:
164
+ LIL R7, 128
165
+ LIH R7, 5 ; state base
166
+
167
+ LDB R1, 0(R7) ; R1 = x byte-column
168
+ LDB R2, 1(R7) ; R2 = y bottom row
169
+
170
+ ; ---- Erase old glyph only ------------------------------------------
171
+ ; This is much less flickery than clearing the whole framebuffer.
172
+ erase_setup:
173
+ LIL R7, 0
174
+ LIH R7, 4 ; glyph base
175
+ LDB R4, 0(R7) ; width
176
+ ADDI R4, 7
177
+ SHR R4, 3 ; row_bytes
178
+ LDB R5, 1(R7) ; height
179
+
180
+ MOV R6, R2
181
+ ADD R6, R6, R5
182
+ ADDI R6, -1 ; top display row = y + height - 1
183
+ SHL R6, 3 ; row * 8 bytes
184
+ LIL R0, 0
185
+ LIH R0, 6 ; framebuffer base
186
+ ADD R6, R6, R0
187
+ ADD R6, R6, R1
188
+ LIL R0, 0 ; erase byte
189
+
190
+ erase_row:
191
+ BZ R5, reload_state
192
+ MOV R3, R4 ; bytes remaining in this glyph row
193
+
194
+ erase_byte:
195
+ BZ R3, erase_row_done
196
+ STB R0, 0(R6)
197
+ ADDI R6, 1
198
+ ADDI R3, -1
199
+ JMP erase_byte
200
+
201
+ erase_row_done:
202
+ SUB R6, R6, R4 ; back to row start
203
+ ADDI R6, -8 ; next lower display row
204
+ ADDI R5, -1
205
+ JMP erase_row
206
+
207
+ reload_state:
208
+ LIL R7, 128
209
+ LIH R7, 5
210
+ LDB R1, 0(R7) ; R1 = x byte-column
211
+ LDB R2, 1(R7) ; R2 = y bottom row
212
+ LDB R3, 2(R7) ; R3 = dx
213
+ LDB R4, 3(R7) ; R4 = dy
214
+
215
+ ; ---- Move x and bounce at left/right walls -------------------------
216
+ move_x:
217
+ BZ R3, move_left
218
+ ADDI R1, 1
219
+ JMP check_x
220
+
221
+ move_left:
222
+ ADDI R1, -1
223
+
224
+ check_x:
225
+ BN R1, hit_left
226
+
227
+ LIL R6, 0
228
+ LIH R6, 4 ; glyph base
229
+ LDB R5, 0(R6) ; glyph width
230
+ ADDI R5, 7
231
+ SHR R5, 3 ; row_bytes = ceil(width / 8)
232
+ LIL R6, 8
233
+ SUB R5, R6, R5 ; max x byte-column = 8 - row_bytes
234
+ SUB R5, R5, R1 ; max_x - x
235
+ BN R5, hit_right
236
+ JMP x_done
237
+
238
+ hit_left:
239
+ LIL R1, 0
240
+ LIL R3, 1 ; dx = right
241
+ JMP x_done
242
+
243
+ hit_right:
244
+ LIL R6, 0
245
+ LIH R6, 4
246
+ LDB R1, 0(R6)
247
+ ADDI R1, 7
248
+ SHR R1, 3 ; row_bytes
249
+ LIL R5, 8
250
+ SUB R1, R5, R1 ; x = max_x
251
+ LIL R3, 0 ; dx = left
252
+
253
+ x_done:
254
+
255
+ ; ---- Move y and bounce at bottom/top walls -------------------------
256
+ BZ R4, move_down
257
+ ADDI R2, 1
258
+ JMP check_y
259
+
260
+ move_down:
261
+ ADDI R2, -1
262
+
263
+ check_y:
264
+ BN R2, hit_bottom
265
+
266
+ LIL R6, 0
267
+ LIH R6, 4
268
+ LDB R5, 1(R6) ; glyph height
269
+ LIL R6, 64
270
+ SUB R5, R6, R5 ; max y = 64 - height
271
+ SUB R5, R5, R2 ; max_y - y
272
+ BN R5, hit_top
273
+ JMP y_done
274
+
275
+ hit_bottom:
276
+ LIL R2, 0
277
+ LIL R4, 1 ; dy = up
278
+ JMP y_done
279
+
280
+ hit_top:
281
+ LIL R6, 0
282
+ LIH R6, 4
283
+ LDB R2, 1(R6)
284
+ LIL R5, 64
285
+ SUB R2, R5, R2 ; y = max_y
286
+ LIL R4, 0 ; dy = down
287
+
288
+ y_done:
289
+ LIL R7, 128
290
+ LIH R7, 5
291
+ STB R1, 0(R7)
292
+ STB R2, 1(R7)
293
+ STB R3, 2(R7)
294
+ STB R4, 3(R7)
295
+
296
+ ; ---- Draw glyph into framebuffer -----------------------------------
297
+ ; Registers during draw:
298
+ ; R1 = x byte-column
299
+ ; R2 = y bottom row
300
+ ; R4 = row_bytes
301
+ ; R5 = rows remaining
302
+ ; R6 = framebuffer row pointer
303
+ ; R7 = glyph source pointer
304
+ draw_setup:
305
+ LIL R7, 0
306
+ LIH R7, 4 ; glyph base
307
+ LDB R4, 0(R7) ; width
308
+ ADDI R4, 7
309
+ SHR R4, 3 ; row_bytes
310
+ LDB R5, 1(R7) ; height
311
+ ADDI R7, 2 ; source points at first glyph row, top first
312
+
313
+ MOV R6, R2
314
+ ADD R6, R6, R5
315
+ ADDI R6, -1 ; top display row = y + height - 1
316
+ SHL R6, 3 ; row * 8 bytes
317
+ LIL R0, 0
318
+ LIH R0, 6 ; framebuffer base
319
+ ADD R6, R6, R0
320
+ ADD R6, R6, R1
321
+
322
+ draw_row:
323
+ BZ R5, delay_setup
324
+ MOV R3, R4 ; bytes remaining in this glyph row
325
+
326
+ draw_byte:
327
+ BZ R3, row_done
328
+ LDB R0, 0(R7)
329
+ NOT R0, R0
330
+ STB R0, 0(R6)
331
+ ADDI R7, 1
332
+ ADDI R6, 1
333
+ ADDI R3, -1
334
+ JMP draw_byte
335
+
336
+ row_done:
337
+ SUB R6, R6, R4 ; back to row start
338
+ ADDI R6, -8 ; next lower display row
339
+ ADDI R5, -1
340
+ JMP draw_row
341
+
342
+ ; ---- Delay so motion is visible ------------------------------------
343
+ delay_setup:
344
+ LIL R6, 80
345
+
346
+ delay_loop:
347
+ ADDI R6, -1
348
+ BZ R6, main_loop
349
+ JMP delay_loop