Skip to content

Commit 6d8f2c1

Browse files
Added new blog post and added Assembler
1 parent 6a28b73 commit 6d8f2c1

File tree

5 files changed

+347
-1
lines changed

5 files changed

+347
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
_site
2+
_code/program.hex
3+
_code/program.lst
4+
_code/program.lst.1

_code/assembler.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import re
2+
import warnings
3+
4+
# Instruction set with reads_ram and writes_ram for RAM access tracking
5+
instruction_set = {
6+
"nop": {"opcode": "0000", "operand": "none", "reads_ram": False, "writes_ram": False},
7+
"lda": {"opcode": "0001", "operand": "address", "reads_ram": True, "writes_ram": False},
8+
"add": {"opcode": "0010", "operand": "address", "reads_ram": True, "writes_ram": False},
9+
"sub": {"opcode": "0011", "operand": "address", "reads_ram": True, "writes_ram": False},
10+
"sta": {"opcode": "0100", "operand": "address", "reads_ram": False, "writes_ram": True},
11+
"ldi": {"opcode": "0101", "operand": "value", "reads_ram": False, "writes_ram": False},
12+
"jmp": {"opcode": "0110", "operand": "address", "reads_ram": False, "writes_ram": False},
13+
"jc": {"opcode": "0111", "operand": "address", "reads_ram": False, "writes_ram": False},
14+
"jz": {"opcode": "1000", "operand": "address", "reads_ram": False, "writes_ram": False},
15+
"out": {"opcode": "1110", "operand": "none", "reads_ram": False, "writes_ram": False},
16+
"hlt": {"opcode": "1111", "operand": "none", "reads_ram": False, "writes_ram": False}
17+
}
18+
19+
def assemble(code, max_instructions=16, max_address=15, strict_ram_check=False, opcode_bits=4, operand_bits=4):
20+
"""Assemble `code` to work on the SAP computer architecture.
21+
max_instructions: max number of instructions (2^PC_bits)
22+
max_address: max RAM addresses (2^MAR_bits)
23+
strict_ram_check: if True, raise error for uninitialized RAM access
24+
opcode_bits: number of bits for opcode
25+
operand_bits: number of bits for operand"""
26+
symbol_table = {}
27+
machine_code = []
28+
ram_init = {} # Store RAM initial values (address: value)
29+
initialized_ram = set() # Track initialized RAM addresses
30+
address = 0
31+
instruction_lines = [] # Store (binary, assembly_line) pairs
32+
33+
# Validate bit widths
34+
if opcode_bits + operand_bits != 8:
35+
raise ValueError(f"Opcode bits ({opcode_bits}) + operand bits ({operand_bits}) must equal 8")
36+
37+
# First pass: Build symbol table and check instruction count
38+
lines = code.splitlines()
39+
for line in lines:
40+
line = line.split(";")[0].strip() # Remove comments for parsing
41+
full_line = line.split(";")[0].rstrip() + (" ;" + ";".join(line.split(";")[1:]) if ";" in line else "")
42+
if not line:
43+
continue
44+
if line.startswith("#ram"): # Handle RAM initialization
45+
parts = line.split()
46+
if len(parts) != 3:
47+
raise ValueError(f"Invalid #ram directive: {line}")
48+
try:
49+
ram_addr = int(parts[1], 0) # Handle decimal or hex
50+
ram_value = int(parts[2], 0)
51+
if ram_addr > max_address or ram_value > max_address:
52+
raise ValueError(f"RAM address or value out of range: {line}")
53+
ram_init[ram_addr] = ram_value
54+
initialized_ram.add(ram_addr) # Mark as initialized
55+
except ValueError:
56+
raise ValueError(f"Invalid #ram address or value: {line}")
57+
continue
58+
if ":" in line: # Handle labels
59+
label = line.split(":")[0].strip()
60+
symbol_table[label] = format(address, f"0{max(4, operand_bits)}b") # Store address with sufficient bits
61+
line = line.split(":")[1].strip()
62+
full_line = line.split(";")[0].rstrip() + (" ;" + ";".join(line.split(";")[1:]) if ";" in line else "")
63+
if line:
64+
address += 1
65+
if address > max_instructions:
66+
raise ValueError(f"Program exceeds maximum of {max_instructions} instructions")
67+
68+
# Generate RAM initialization instructions
69+
for ram_addr, ram_value in ram_init.items():
70+
ldi_binary = f"0111{format(ram_value, f'0{operand_bits}b')}"
71+
sta_binary = f"0100{format(ram_addr, f'0{operand_bits}b')}"
72+
machine_code.append(ldi_binary)
73+
machine_code.append(sta_binary)
74+
instruction_lines.append((ldi_binary, f"ldi {ram_value} ; Generated for #ram {ram_addr} {ram_value}"))
75+
instruction_lines.append((sta_binary, f"sta [{ram_addr}] ; Generated for #ram {ram_addr} {ram_value}"))
76+
address += 2
77+
if address > max_instructions:
78+
raise ValueError(f"Program with RAM init exceeds {max_instructions} instructions")
79+
80+
# Second pass: Generate machine code and check RAM access
81+
# Second pass: Generate machine code and check RAM access
82+
address = len(machine_code) # Start after RAM init instructions
83+
for raw_line in lines:
84+
# Preserve original line with comments
85+
full_line = raw_line.rstrip() # Keep entire line, including comments
86+
line = raw_line.split(";")[0].strip() # Strip comments for parsing
87+
if not line or line.startswith("#ram"):
88+
continue
89+
if ":" in line:
90+
line = line.split(":")[1].strip()
91+
# Reconstruct full_line to include label and comment
92+
full_line = line.split(";")[0].rstrip() + (" ;" + ";".join(raw_line.split(";")[1:]) if ";" in raw_line else "")
93+
if line:
94+
# Parse instruction and operands
95+
match = re.match(r"(\w+)\s*(?:\[(\w+)\]|(\w+))?", line)
96+
if not match:
97+
raise ValueError(f"Invalid syntax: {line}")
98+
99+
instr, operand1, operand2 = match.groups()
100+
instr = instr.lower()
101+
102+
if instr not in instruction_set:
103+
raise ValueError(f"Unknown instruction: {instr}")
104+
105+
opcode = instruction_set[instr]["opcode"]
106+
operand_type = instruction_set[instr]["operand"]
107+
binary = opcode
108+
109+
if operand_type == "none":
110+
binary += "0" * operand_bits
111+
elif operand_type == "address":
112+
operand = operand1 or operand2
113+
if operand in symbol_table:
114+
binary += symbol_table[operand][-operand_bits:]
115+
else:
116+
try:
117+
addr = int(operand, 0)
118+
if addr > max_address:
119+
raise ValueError(f"Address out of range: {operand}")
120+
binary += format(addr, f"0{operand_bits}b")
121+
if instruction_set[instr]["reads_ram"] and addr not in initialized_ram:
122+
message = (f"Instruction '{full_line}' at address {address} accesses "
123+
f"uninitialized RAM[{addr}]. Consider adding `#ram {addr} 0` to initialize.")
124+
if strict_ram_check:
125+
raise ValueError(message)
126+
else:
127+
warnings.warn(message)
128+
if instruction_set[instr]["writes_ram"]:
129+
initialized_ram.add(addr)
130+
except ValueError:
131+
raise ValueError(f"Invalid address or unresolved label: {operand}")
132+
elif operand_type == "value":
133+
try:
134+
value = int(operand2, 0)
135+
if value > max_address:
136+
raise ValueError(f"Value out of range: {operand2}")
137+
binary += format(value, f"0{operand_bits}b")
138+
except ValueError:
139+
raise ValueError(f"Invalid value: {operand2}")
140+
141+
machine_code.append(binary)
142+
instruction_lines.append((binary, full_line))
143+
address += 1
144+
if address > max_instructions:
145+
raise ValueError(f"Program exceeds {max_instructions} instructions")
146+
147+
return machine_code, ram_init, instruction_lines, initialized_ram, symbol_table
148+
149+
# Format output for breadboard (e.g., toggle switches)
150+
def format_for_switches(binary):
151+
return ", ".join("on" if bit == "1" else "off" for bit in binary)
152+
153+
# Print listing with assembly code, address, machine code, and optional switches/comments
154+
def print_listing(instruction_lines, show_switches=False, show_comments=True, symbol_table=None):
155+
print("Program Listing:")
156+
for i, (binary, assembly_line) in enumerate(instruction_lines):
157+
if not isinstance(binary, str):
158+
raise TypeError(f"Non-string binary value at address {i}: {binary}")
159+
hex_val = hex(int(binary, 2))
160+
parts = assembly_line.split(";", 1)
161+
asm = parts[0].strip()
162+
comment = parts[1].strip() if len(parts) > 1 and show_comments else ""
163+
# Resolve address for address-based instructions
164+
match = re.match(r"(\w+)\s*(?:\[(\w+)\]|(\w+))?", asm)
165+
if match:
166+
instr, operand1, operand2 = match.groups()
167+
instr = instr.lower()
168+
operand = operand1 or operand2
169+
if instr in ["lda", "add", "sub", "sta", "jmp", "jc", "jnc", "ldx"] and operand:
170+
if operand in symbol_table:
171+
resolved_addr = int(symbol_table[operand], 2)
172+
asm += f" ({resolved_addr})"
173+
else:
174+
try:
175+
resolved_addr = int(operand, 0)
176+
asm += f" ({resolved_addr})"
177+
except ValueError:
178+
pass
179+
line = f"Addr {format(i, '04b')} ( {i:5d} ) : {binary} ({hex_val}) | {asm:<20}"
180+
if show_switches:
181+
switch_settings = format_for_switches(binary)
182+
line += f" Switches: {switch_settings}"
183+
if show_comments:
184+
line += f" {comment}"
185+
print(line)
186+
187+
def to_listing_file(instruction_lines, filename="program.lst", show_switches=False, show_comments=True, symbol_table=None):
188+
with open(filename, "w") as f:
189+
f.write("Program Listing:\n")
190+
for i, (binary, assembly_line) in enumerate(instruction_lines):
191+
if not isinstance(binary, str):
192+
raise TypeError(f"Non-string binary value at address {i}: {binary}")
193+
hex_val = hex(int(binary, 2))
194+
parts = assembly_line.split(";", 1)
195+
asm = parts[0].strip()
196+
comment = parts[1].strip() if len(parts) > 1 and show_comments else ""
197+
# Resolve address for address-based instructions
198+
match = re.match(r"(\w+)\s*(?:\[(\w+)\]|(\w+))?", asm)
199+
if match:
200+
instr, operand1, operand2 = match.groups()
201+
instr = instr.lower()
202+
operand = operand1 or operand2
203+
if instr in ["lda", "add", "sub", "sta", "jmp", "jc", "jnc", "ldx"] and operand:
204+
if operand in symbol_table:
205+
resolved_addr = int(symbol_table[operand], 2)
206+
asm += f" ({resolved_addr})"
207+
else:
208+
try:
209+
resolved_addr = int(operand, 0)
210+
asm += f" ({resolved_addr})"
211+
except ValueError:
212+
pass
213+
line = f"Addr {format(i, '04b')} ( {i:5d} ) : {binary} ({hex_val}) | {asm:<20}"
214+
if show_switches:
215+
switch_settings = format_for_switches(binary)
216+
line += f" Switches: {switch_settings}"
217+
if show_comments:
218+
line += f" {comment}"
219+
f.write(line + "\n")
220+
print(f"Listing file '{filename}' generated.")
221+
222+
# Generate hex file for EEPROM
223+
def to_hex_file(machine_code, filename="program.hex"):
224+
with open(filename, "w") as f:
225+
for binary in machine_code:
226+
if not isinstance(binary, str):
227+
raise TypeError(f"Non-string binary value in machine_code: {binary}")
228+
f.write(f"{int(binary, 2):02X}\n")
229+
230+
# Example usage with your sample code
231+
code = """
232+
START:
233+
ldi 1 ; Load 1 into accumulator
234+
sta [14] ; Initialize RAM[14] = 1 (F(n-2))
235+
sta [15] ; Initialize RAM[15] = 1 (F(n-1))
236+
out ; Output 1 (first Fibonacci number)
237+
LOOP:
238+
lda [14] ; Load F(n-2)
239+
add [15] ; Add F(n-1) to get F(n)
240+
jc END ; Stop if carry (overflow)
241+
out ; Output F(n)
242+
sta [15] ; Store F(n) in RAM[15] (new F(n-1))
243+
lda [15] ; Load old F(n-1)
244+
sta [14] ; Store old F(n-1) in RAM[14] (new F(n-2))
245+
jmp LOOP ; Repeat
246+
END:
247+
hlt ; Stop
248+
"""
249+
250+
code = """
251+
START:
252+
ldi 3
253+
sta [14] ; put 3 in ram 14
254+
ldi 4
255+
add [14] ;add ram [14] to 4
256+
out ; hopefully output 7
257+
sta [14]
258+
jmp 3
259+
hlt"""
260+
machine_code, ram_init, instruction_lines, initialized_ram, symbol_table = assemble(code, strict_ram_check=False)
261+
print("Assembly: \n"+code)
262+
print_listing(instruction_lines, show_switches=False, show_comments=True, symbol_table=symbol_table)
263+
to_listing_file(instruction_lines, show_switches=False, show_comments=True, symbol_table=symbol_table)
264+
265+
print("\nInitial RAM Contents:")
266+
if ram_init:
267+
for addr, value in sorted(ram_init.items()):
268+
print(f"RAM[{format(addr, '04b')} ( {addr} )] = {value} ({hex(value)})")
269+
else:
270+
print("No RAM initialization specified.")
271+
272+
to_hex_file(machine_code)
273+
print("\nHex file 'program.hex' generated for EEPROM.")
274+
275+
# Print total program bytes and RAM usage
276+
print(f"\nTotal Program Bytes: {len(machine_code)}")
277+
print(f"Total RAM Usage: {len(initialized_ram)} bytes")
278+
279+
# # Test with strict RAM check, no comments, and uninitialized RAM
280+
# print("\nTesting with strict RAM check and no comments:")
281+
# code_uninit = """
282+
# START:
283+
# ldi 1 ; Load 1 into accumulator
284+
# out
285+
# add [2] ; Add RAM[2] (uninitialized)
286+
# out
287+
# halt
288+
# """
289+
# try:
290+
# machine_code, ram_init, instruction_lines, initialized_ram, symbol_table = assemble(code_uninit, strict_ram_check=True)
291+
# print_listing(instruction_lines, show_switches=False, show_comments=False, symbol_table=symbol_table)
292+
# to_listing_file(instruction_lines, filename="program_uninit.lst", show_switches=False, show_comments=False, symbol_table=symbol_table)
293+
# except ValueError as e:
294+
# print(f"Error: {e}")
295+
296+
# # Test with new instructions
297+
# print("\nTesting with new instructions:")
298+
# code_new = """
299+
# START:
300+
# ldi 1 ; Load 1 into accumulator
301+
# sta [0] ; Store to RAM[0]
302+
# ldx [0] ; Load RAM[0] into X
303+
# inc ; Increment accumulator
304+
# mvi 5 ; Move 5 to register
305+
# out
306+
# jmp START ; Repeat
307+
# """
308+
# machine_code, ram_init, instruction_lines, initialized_ram, symbol_table = assemble(code_new, strict_ram_check=True)
309+
# print_listing(instruction_lines, show_switches=True, show_comments=True, symbol_table=symbol_table)
310+
# to_listing_file(instruction_lines, filename="program_new.lst", show_switches=True, show_comments=True, symbol_table=symbol_table)

_posts/2025-04-23-blog-setup.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ Now, I have a whole lot on my mind, but I think the rest is probably better expl
1616

1717
Some links for me in the future.
1818
[Markdown cheat sheet](https://www.markdownguide.org/cheat-sheet/)
19+
1920
[Blog setup/examples](https://chadbaldwin.net/2021/03/14/how-to-build-a-sql-blog.html)
21+
2022
[more blog examples](https://emmatheeng.github.io/projects/blog_setup.html)

_posts/2025-04-24-some-basic-design-goals.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ This will be the very first iteration of design goals for my SAP-2/3/whatever co
3838
- [Bill Buzbee](https://www.youtube.com/@Homebrew_CPU/videos)
3939
- [Ben Eater](https://www.youtube.com/@BenEater)
4040
- [Fadil Isamotu's blog](https://fadil-1.github.io/blog/8-bit_breadboard_CPU/overview/)
41-
- [Fadil Isamotu's github](https://github.com/Fadil-1/8-BIT-BREADBOARD-CPU)
41+
- [Fadil Isamotu's github](https://github.com/Fadil-1/8-BIT-BREADBOARD-CPU)
42+
- [visrealm's vrcpu github](https://github.com/visrealm/vrcpu?tab=readme-ov-file)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## Assembler for the Future
2+
3+
Just finished using Grok for my assembler for the computer. It took a decent bit of time, but far less than it would have to do it myself. And like, yes, [others](https://hlorenzi.github.io/customasm/web/) do exist, but this is mine, and I can do with it what I want. Maybe I'll make it a web app sometime. It's in python though, so who knows. However, it is a good step towards my final goal, which would almost be a compiler to translate something legible like Basic or, HOPEFULLY, C/C++. Or, I might try to make my computer able to run gcc or tcc. But, I think that's a _very_ far step, and I don't even know if it's possible without going to something much more advanced.
4+
5+
#### 4-24 Update.
6+
7+
I started writing this blog yesterday, because I felt like it would be moreso a tomorrow problem. But it is also a today problem, and therefore a tomorrow problem. I got the assembler working, after realizing I goofed. The stupid thing was trying to gaslight me a couple times. I wasn't able to get the fibonacci algorithm working right,
8+
```asm
9+
START:
10+
ldi 3
11+
sta [14] ; put 3 in ram 14
12+
ldi 4
13+
add [14] ;add ram [14] to 4
14+
out ; hopefully output 7
15+
jmp 3
16+
hlt
17+
```
18+
I was able to get something simple like this working before going to bed, proving that I indeed can store ram, load another value, and add numbers up.
19+
20+
21+
#### 4-25 (Finally today)
22+
23+
I don't have time before work today, but I should hopefully have time tomorrow once I get home. Can't wait to finalize the SAP-1. I know many people have kept improving it to the SAP-2 or SAP-3, but that one will be put up on a wall or something. It's my first real work of art in this space.
24+
25+
Finally got back from work. I don't think I'll have a ton of time today due to needing to pack up, but who knows. I'm reading a bit of [The Elements of Computing Systems](https://thecodingchicken.com/chips/computer%20design/The%20Elements%20of%20Computing%20Systems.pdf). I'll share more books as time goes, and probably screenshot/upload individual chapters to help make more sense for various modules/why I went this way or that way for various design decisions.
26+
This book, [Digital Computer Electronics](https://thecodingchicken.com/chips/computer%20design/digital-computer-electronics-3rd-edition.pdf), by Albert Paul Malvino and Jerald A. Brown, is probably the BEST source of information. Chapters 10-12 are probably the most relevant, covering SAP1-SAP3 computer designs. Chapters 11 and 12 are a bit more brief, but at that point I presume they expect you to know more and figure it out. However, I won't assume that here, and I will follow up with more details.
27+
28+
I highly recommend printing out Digital Computer Electronics, and maybe some other books as well, found [here](https://thecodingchicken.com/chips/computer%20design/). I'll continue uploading future books that I found on the subject there. I haven't read all of them, but they seemed relevant in the quest for knowledge and future builds.
29+
30+
I'm going to get back to testing out my SAP-1, as it was acting weirdly. But the manual inputting of data has really shown me how annoying it is to program stuff, even when you only have 10 lines of code.

0 commit comments

Comments
 (0)