init
This commit is contained in:
commit
d7091e7f36
12 changed files with 757 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.13
|
||||||
0
README.md
Normal file
0
README.md
Normal file
257
assembler.py
Normal file
257
assembler.py
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
binary_instructions = ["ADD", "ADC", "SUB", "XOR", "MUL", "AND", "SHR"]
|
||||||
|
unary_instructions = ["DRF", "SND"]
|
||||||
|
address_instructions = ["JMP", "CAL"]
|
||||||
|
conditional_instructions = ["JIZ", "JNZ", "GET", "PUT"]
|
||||||
|
mono_instructions = ["RET"]
|
||||||
|
immediate_instructions = ["IMM"]
|
||||||
|
|
||||||
|
instruction_map = {
|
||||||
|
"ADD": 0b0000,
|
||||||
|
"ADC": 0b0001,
|
||||||
|
"SUB": 0b0010,
|
||||||
|
"XOR": 0b0011,
|
||||||
|
"GET": 0b0100,
|
||||||
|
"PUT": 0b0101,
|
||||||
|
"JMP": 0b0110,
|
||||||
|
"JIZ": 0b0111,
|
||||||
|
"JNZ": 0b1000,
|
||||||
|
"CAL": 0b1001,
|
||||||
|
"RET": 0b1010,
|
||||||
|
"SND": 0b1011,
|
||||||
|
"DRF": 0b1100,
|
||||||
|
"AND": 0b1101,
|
||||||
|
"IMM": 0b1110,
|
||||||
|
"SHR": 0b1111,
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
inp = "test.ns" #sys.argv[1]
|
||||||
|
#out = sys.argv[2]
|
||||||
|
|
||||||
|
with open(inp, 'r') as infile:
|
||||||
|
data = infile.read()
|
||||||
|
|
||||||
|
rom = bytearray(2**9)
|
||||||
|
pc = 0
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
labels = {}
|
||||||
|
put_labels = {}
|
||||||
|
|
||||||
|
line = 1
|
||||||
|
column = 1
|
||||||
|
|
||||||
|
def gobble(det: lambda c: bool, n: int=-1) -> str:
|
||||||
|
nonlocal line
|
||||||
|
nonlocal column
|
||||||
|
nonlocal i
|
||||||
|
start = i
|
||||||
|
while i < len(data) and (i - start) != n and det(data[i]):
|
||||||
|
column += 1
|
||||||
|
if data[i] == "\n":
|
||||||
|
line += 1
|
||||||
|
column = 1
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
return data[start:i]
|
||||||
|
|
||||||
|
|
||||||
|
parse_name = lambda: gobble(lambda c: c.isalnum() or c == "_")
|
||||||
|
parse_hex = lambda n=-1: gobble(lambda c: (c.isdigit() or c.lower() in "abcdef"), n)
|
||||||
|
parse_decimal = lambda n=-1: gobble(lambda c: c.isdigit(), n or -1)
|
||||||
|
skip_whitespace = lambda: gobble(lambda c: c.isspace())
|
||||||
|
|
||||||
|
def error(msg: str):
|
||||||
|
raise ValueError(f"Error at {line}:{column}: {msg}")
|
||||||
|
|
||||||
|
def accept(s: str) -> bool:
|
||||||
|
nonlocal i
|
||||||
|
if data[i:i+len(s)] == s:
|
||||||
|
i += len(s)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def expect(s: str, msg: str = ""):
|
||||||
|
nonlocal i
|
||||||
|
if data[i:i+len(s)] != s:
|
||||||
|
error(f"Expected '{s}' (found {data[i:i+len(s)]})" + (": " + msg if msg else ""))
|
||||||
|
i += len(s)
|
||||||
|
|
||||||
|
def parse_register() -> int:
|
||||||
|
skip_whitespace()
|
||||||
|
expect("r", "registers must start with r")
|
||||||
|
|
||||||
|
token = parse_decimal(1)
|
||||||
|
if not token.isdigit() or int(token) < 0 or int(token) > 7:
|
||||||
|
error(f"Register out of range: {token}")
|
||||||
|
return int(token)
|
||||||
|
|
||||||
|
def parse_label() -> tuple[str, int]:
|
||||||
|
skip_whitespace()
|
||||||
|
expect("#", "label references must start with #")
|
||||||
|
label = parse_name()
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
if accept("."):
|
||||||
|
offset_str = parse_decimal()
|
||||||
|
offset = int(offset_str)
|
||||||
|
|
||||||
|
return label, offset
|
||||||
|
|
||||||
|
skip_whitespace()
|
||||||
|
while i < len(data):
|
||||||
|
if pc >= len(rom):
|
||||||
|
error("Program too large to fit in ROM")
|
||||||
|
|
||||||
|
c = data[i]
|
||||||
|
if c == "@":
|
||||||
|
i += 1
|
||||||
|
label = parse_name()
|
||||||
|
labels[label] = pc
|
||||||
|
elif c == "x":
|
||||||
|
i += 1
|
||||||
|
token = parse_hex()
|
||||||
|
value = int(token, 16)
|
||||||
|
if value < 0 or value > 255:
|
||||||
|
error(f"Value out of range: {token}")
|
||||||
|
|
||||||
|
rom[pc] = value
|
||||||
|
pc += 1
|
||||||
|
elif c == "+":
|
||||||
|
i += 1
|
||||||
|
token = parse_decimal()
|
||||||
|
value = int(token, 10)
|
||||||
|
if pc + value >= len(rom):
|
||||||
|
error(f"Offset value outside rom: {token}")
|
||||||
|
|
||||||
|
pc += value
|
||||||
|
elif c == ":":
|
||||||
|
i += 1
|
||||||
|
token = parse_decimal()
|
||||||
|
value = int(token, 10)
|
||||||
|
if value < 0 or value >= len(rom):
|
||||||
|
error(f"Address out of range: {token}")
|
||||||
|
|
||||||
|
pc = value
|
||||||
|
elif c == '"': # string literal
|
||||||
|
i += 1
|
||||||
|
while i < len(data) and data[i] != '"':
|
||||||
|
rom[pc] = ord(data[i]) & 0xFF
|
||||||
|
pc += 1
|
||||||
|
i += 1
|
||||||
|
if i >= len(data) or data[i] != '"':
|
||||||
|
error("Unterminated string literal")
|
||||||
|
i += 1
|
||||||
|
elif c == ";": # comment
|
||||||
|
i += 1
|
||||||
|
gobble(lambda c: c != "\n")
|
||||||
|
else: # instruction
|
||||||
|
token = parse_name().upper()
|
||||||
|
if token in binary_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
skip_whitespace()
|
||||||
|
|
||||||
|
r0 = parse_register()
|
||||||
|
r1 = parse_register()
|
||||||
|
r2 = parse_register()
|
||||||
|
|
||||||
|
rom[pc] |= (int(r0) & 0b111) << 1
|
||||||
|
rom[pc] |= (int(r1) >> 2) & 0b001
|
||||||
|
pc += 1
|
||||||
|
|
||||||
|
rom[pc] = 0
|
||||||
|
rom[pc] |= (int(r1) & 0b011) << 6
|
||||||
|
rom[pc] |= (int(r2) & 0b111) << 3
|
||||||
|
pc += 1
|
||||||
|
|
||||||
|
elif token in address_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
|
||||||
|
label, offset = parse_label()
|
||||||
|
|
||||||
|
put_labels[pc] = { "label": label, "offset": offset, "type": "address" }
|
||||||
|
pc += 2
|
||||||
|
elif token in conditional_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
skip_whitespace()
|
||||||
|
|
||||||
|
label, offset = parse_label()
|
||||||
|
put_labels[pc] = { "label": label, "offset": offset, "type": "address" }
|
||||||
|
pc += 1
|
||||||
|
|
||||||
|
r0 = parse_register()
|
||||||
|
|
||||||
|
rom[pc] = int(r0)
|
||||||
|
|
||||||
|
pc += 1
|
||||||
|
elif token in mono_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
pc += 1
|
||||||
|
elif token in immediate_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
|
||||||
|
r0 = parse_register()
|
||||||
|
|
||||||
|
rom[pc] |= (int(r0) & 0b111) << 1
|
||||||
|
pc += 1
|
||||||
|
|
||||||
|
skip_whitespace()
|
||||||
|
|
||||||
|
if data[i] == "#":
|
||||||
|
label, offset = parse_label()
|
||||||
|
put_labels[pc] = { "label": label, "offset": offset, "type": "immediate" }
|
||||||
|
imm_value = 0
|
||||||
|
else:
|
||||||
|
expect("x", "hexadecimal values must start with x")
|
||||||
|
imm_token = parse_hex()
|
||||||
|
imm_value = int(imm_token, 16)
|
||||||
|
if imm_value < 0 or imm_value > 255:
|
||||||
|
error(f"Immediate value out of range: {imm_token}")
|
||||||
|
|
||||||
|
rom[pc] = imm_value
|
||||||
|
pc += 1
|
||||||
|
elif token in unary_instructions:
|
||||||
|
rom[pc] = instruction_map[token] << 4
|
||||||
|
|
||||||
|
r0 = parse_register()
|
||||||
|
r1 = parse_register()
|
||||||
|
|
||||||
|
rom[pc] |= (int(r0) & 0b111) << 1
|
||||||
|
rom[pc] |= (int(r1) & 0b001) >> 2
|
||||||
|
pc += 1
|
||||||
|
|
||||||
|
rom[pc] = (int(r1) & 0b011) << 6
|
||||||
|
pc += 1
|
||||||
|
else:
|
||||||
|
error(f"Unknown instruction at position {i}: {token}")
|
||||||
|
|
||||||
|
skip_whitespace()
|
||||||
|
|
||||||
|
# put labels
|
||||||
|
for addr, label in put_labels.items():
|
||||||
|
if label["label"] not in labels:
|
||||||
|
error(f"Undefined label: {label['label']}")
|
||||||
|
|
||||||
|
target = labels[label["label"]] + label["offset"]
|
||||||
|
if target < 0 or target >= len(rom):
|
||||||
|
error(f"Label address out of range: {label['label']}")
|
||||||
|
|
||||||
|
if label["type"] == "address":
|
||||||
|
rom[addr] |= (target >> 5) & 0b1111
|
||||||
|
rom[addr + 1] |= (target & 0b11111) << 3
|
||||||
|
elif label["type"] == "immediate":
|
||||||
|
if target < 0 or target > 255:
|
||||||
|
error(f"Immediate label address out of range: {label['label']}")
|
||||||
|
rom[addr] = target & 0xFF
|
||||||
|
else:
|
||||||
|
error(f"Unknown label type for label: {label['label']}")
|
||||||
|
|
||||||
|
with open("out.rom", 'wb') as outfile:
|
||||||
|
outfile.write(rom)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
137
emulator.py
Normal file
137
emulator.py
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import sys
|
||||||
|
from assembler import instruction_map as instruction_to_number_map, binary_instructions, address_instructions, conditional_instructions, unary_instructions, mono_instructions, immediate_instructions
|
||||||
|
|
||||||
|
number_to_instruction_map = {v: k for k, v in instruction_to_number_map.items()}
|
||||||
|
|
||||||
|
class Emulator:
|
||||||
|
def __init__(self, ram: bytearray):
|
||||||
|
self.ram = ram
|
||||||
|
self.pc = 0
|
||||||
|
self.registers = bytearray(8)
|
||||||
|
self.carry = 0
|
||||||
|
self.call_stack = []
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
opcode = self.ram[self.pc] >> 4
|
||||||
|
|
||||||
|
op = number_to_instruction_map[opcode]
|
||||||
|
|
||||||
|
if op in unary_instructions or op in binary_instructions:
|
||||||
|
reg_a = (self.ram[self.pc] >> 1) & 0b111
|
||||||
|
reg_b = ((self.ram[self.pc] & 0b1) << 2) | ((self.ram[self.pc + 1] >> 6) & 0b11)
|
||||||
|
if op in unary_instructions:
|
||||||
|
match op:
|
||||||
|
case "DRF":
|
||||||
|
addr = self.registers[reg_a]
|
||||||
|
v = self.ram[addr]
|
||||||
|
self.registers[reg_b] = v
|
||||||
|
case "SND":
|
||||||
|
self.ram[self.registers[reg_a]] = self.registers[reg_b]
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown unary opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
elif op in binary_instructions:
|
||||||
|
reg_c = (self.ram[self.pc + 1] >> 3) & 0b111
|
||||||
|
if op in binary_instructions:
|
||||||
|
match op:
|
||||||
|
case "ADD" | "ADC":
|
||||||
|
v = self.registers[reg_a] + self.registers[reg_b] + (self.carry if op == "ADC" else 0)
|
||||||
|
if v > 0xFF:
|
||||||
|
self.carry = 1
|
||||||
|
else:
|
||||||
|
self.carry = 0
|
||||||
|
|
||||||
|
self.registers[reg_c] = v & 0xFF
|
||||||
|
case "SUB":
|
||||||
|
self.registers[reg_c] = (self.registers[reg_a] - self.registers[reg_b] + 0x100) & 0xFF
|
||||||
|
case "XOR":
|
||||||
|
self.registers[reg_c] = self.registers[reg_a] ^ self.registers[reg_b]
|
||||||
|
case "SHR":
|
||||||
|
self.registers[reg_c] = self.registers[reg_a] >> self.registers[reg_b]
|
||||||
|
case "AND":
|
||||||
|
self.registers[reg_c] = self.registers[reg_a] & self.registers[reg_b]
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown binary opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
elif op in address_instructions:
|
||||||
|
addr = ((self.ram[self.pc] & 0b1111) << 5) | (self.ram[self.pc + 1] >> 3)
|
||||||
|
|
||||||
|
match op:
|
||||||
|
case "JMP":
|
||||||
|
self.pc = addr
|
||||||
|
case "CAL":
|
||||||
|
self.call_stack.append(self.pc + 2)
|
||||||
|
self.pc = addr
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown conditional opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
return
|
||||||
|
elif op in conditional_instructions:
|
||||||
|
reg = self.ram[self.pc+1] & 0b111
|
||||||
|
addr = ((self.ram[self.pc] & 0b1111) << 5) | (self.ram[self.pc + 1] >> 3)
|
||||||
|
|
||||||
|
match op:
|
||||||
|
case "JIZ":
|
||||||
|
if self.registers[reg] == 0:
|
||||||
|
self.pc = addr
|
||||||
|
return
|
||||||
|
case "JNZ":
|
||||||
|
if self.registers[reg] != 0:
|
||||||
|
self.pc = addr
|
||||||
|
return
|
||||||
|
case "PUT":
|
||||||
|
self.ram[addr] = self.registers[reg]
|
||||||
|
case "GET":
|
||||||
|
self.registers[reg] = self.ram[addr]
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown conditional opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
elif op in mono_instructions:
|
||||||
|
match op:
|
||||||
|
case "RET":
|
||||||
|
if not self.call_stack:
|
||||||
|
raise ValueError(f"Call stack underflow at PC={self.pc:03X}")
|
||||||
|
self.pc = self.call_stack.pop()
|
||||||
|
return
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown mono opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
elif op in immediate_instructions:
|
||||||
|
reg = (self.ram[self.pc] >> 1) & 0b111
|
||||||
|
imm = self.ram[self.pc + 1]
|
||||||
|
|
||||||
|
match op:
|
||||||
|
case "IMM":
|
||||||
|
self.registers[reg] = imm
|
||||||
|
case _:
|
||||||
|
raise ValueError(f"Unknown immediate opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown opcode at PC={self.pc:03X}: {opcode:04b}")
|
||||||
|
|
||||||
|
self.pc += 2
|
||||||
|
|
||||||
|
def state(self) -> str:
|
||||||
|
regs = ' '.join(f'r{i}={self.registers[i]:02X}' for i in range(8))
|
||||||
|
return f'PC={self.pc:03X} RAM[PC]={self.ram[self.pc]:08b} RAM[PC+1]={self.ram[self.pc+1]:08b} C={self.carry} \t' + regs
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ram = bytearray(2**9)
|
||||||
|
|
||||||
|
with open("out.rom", 'rb') as infile:
|
||||||
|
rom_data = infile.read()
|
||||||
|
ram[0:len(rom_data)] = rom_data
|
||||||
|
|
||||||
|
emu = Emulator(ram)
|
||||||
|
prev_pc = -1
|
||||||
|
while emu.pc != prev_pc:
|
||||||
|
if emu.pc >= len(ram):
|
||||||
|
print("PC out of bounds!")
|
||||||
|
break
|
||||||
|
|
||||||
|
# handle output
|
||||||
|
if emu.ram[511] != 0:
|
||||||
|
print(f"{chr(emu.ram[-1])}", end='')
|
||||||
|
emu.ram[-1] = 0
|
||||||
|
|
||||||
|
prev_pc = emu.pc
|
||||||
|
#print(emu.state())
|
||||||
|
emu.step()
|
||||||
|
#print("End:\t " + emu.state())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
98
mul.ns
Normal file
98
mul.ns
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
jmp #start
|
||||||
|
|
||||||
|
; ROM/RAM data would go here before start
|
||||||
|
|
||||||
|
@number x0
|
||||||
|
|
||||||
|
@string "Hello world!" x0A x00 ; Newline and null terminated
|
||||||
|
|
||||||
|
|
||||||
|
@start
|
||||||
|
imm r1 xF
|
||||||
|
imm r2 x7
|
||||||
|
|
||||||
|
cal #full_ladder_multiply
|
||||||
|
|
||||||
|
imm r0 #string ; put the address of #string into r0
|
||||||
|
cal #print ; print it!
|
||||||
|
|
||||||
|
jmp #loop ; limbo/endless loop
|
||||||
|
|
||||||
|
|
||||||
|
@multiply ; Multiply r1 by r2, putting the result in r0
|
||||||
|
imm r7 x1 ; We use r7 to decrement r2
|
||||||
|
|
||||||
|
@multiply_round ; Increment and decrement
|
||||||
|
add r0 r1 r0 ; Add r1 to counter
|
||||||
|
sub r2 r7 r2 ; Decrement r2
|
||||||
|
jnz #multiply_round r2 ; If r2 is not zero, again!
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
@full_ladder_multiply ; Multiply r1 by r2, putting the result in r0 (r1 ends up being r1*r2 + r1)
|
||||||
|
imm r6 x1 ; let r6 be 1. it is used as a mask to select the first bit
|
||||||
|
imm r7 x80 ; let r7 be 0b1000_0000. it is used as a bit mask for the current bit
|
||||||
|
|
||||||
|
; Move r2 into r5
|
||||||
|
imm r5 x0
|
||||||
|
add r2 r5 r5
|
||||||
|
|
||||||
|
@full_ladder_multiply_find_high_bit ; go through each bit from low to high. if we encounter a 1, we have found the highest bit.
|
||||||
|
and r7 r2 r3 ; use r7 as a mask for r2, getting the current bit
|
||||||
|
shr r7 r6 r7 ; shift r7 right by one (r6 = 1)
|
||||||
|
jiz #full_ladder_multiply_find_high_bit r3 ; r7 ends up being 2^(l-2), where the bit at l-1 is 1 and is the highest bit
|
||||||
|
|
||||||
|
imm r0 x0 ; Let r0 be 0 (to reset it).
|
||||||
|
add r0 r1 r0 ; r0 starts with the value of r1
|
||||||
|
imm r1 x0
|
||||||
|
|
||||||
|
add r0 r0 r2 ; r1 starts with the value of two r1
|
||||||
|
imm r3 x0
|
||||||
|
adc r3 r3 r3 ; put the carry into r3
|
||||||
|
|
||||||
|
@full_ladder_multiply_round
|
||||||
|
and r5 r7 r3 ; let r3 be r5 & r7. We check if the bit that r7 is masking is high on r2 later
|
||||||
|
shr r7 r6 r7 ; Shift r7 right by 1 (r6 = 1)
|
||||||
|
|
||||||
|
jnz #full_ladder_multiply_round_1 r3 ; if r3 is 1, we jump. otherwise
|
||||||
|
@full_ladder_multiply_round_0
|
||||||
|
add r0 r2 r2 ; Let r2 be r0 + r2
|
||||||
|
adc r1 r3 r3 ; carry
|
||||||
|
|
||||||
|
add r0 r0 r0 ; Double r0
|
||||||
|
adc r1 r1 r1 ; Double r1 and add carry
|
||||||
|
jmp #ladder_multiply_round_end ; Skip the second alternative
|
||||||
|
@full_ladder_multiply_round_1
|
||||||
|
add r0 r2 r0 ; Let r0 be r0 + r2
|
||||||
|
adc r1 r3 r1 ; carry
|
||||||
|
|
||||||
|
add r2 r2 r2 ; Double r0
|
||||||
|
adc r3 r3 r3 ; Double r1 and add carry
|
||||||
|
@full_ladder_multiply_round_end
|
||||||
|
jnz #full_ladder_multiply_round r7 ; if the mask still hasn't been shifted out (r7 != 0), do another round
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@print ; Print a string to output, given r0 as the address of a null-terminated string
|
||||||
|
imm r2 x1
|
||||||
|
|
||||||
|
@print_loop
|
||||||
|
drf r0 r1 ; get the value that r0 points to, and put it in r1
|
||||||
|
jiz #print_loop_end r1 ; if the value of r1 is 0, we have reached the end and should exit
|
||||||
|
|
||||||
|
put #output r1 ; output the character
|
||||||
|
|
||||||
|
add r0 r2 r0 ; increment the pointer (r2 = 1)
|
||||||
|
jmp #print_loop ; again!
|
||||||
|
@print_loop_end
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
@loop ; Infinite loop
|
||||||
|
jmp #loop
|
||||||
|
|
||||||
|
|
||||||
|
:511 @output ; I/O address for serial out
|
||||||
88
multiply.ns
Normal file
88
multiply.ns
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
jmp #start
|
||||||
|
|
||||||
|
; ROM/RAM data would go here before start
|
||||||
|
|
||||||
|
@number x0
|
||||||
|
|
||||||
|
@string "Hello world!" x0A x00 ; Newline and null terminated
|
||||||
|
|
||||||
|
|
||||||
|
@start
|
||||||
|
imm r1 xF
|
||||||
|
imm r2 x7
|
||||||
|
|
||||||
|
cal #ladder_multiply
|
||||||
|
put #number r0 ; keep the number
|
||||||
|
|
||||||
|
imm r1 #string
|
||||||
|
cal #number_to_string
|
||||||
|
|
||||||
|
imm r0 #string ; put the address of #string into r0
|
||||||
|
cal #print ; print it!
|
||||||
|
|
||||||
|
jmp #loop ; limbo/endless loop
|
||||||
|
|
||||||
|
|
||||||
|
@multiply ; Multiply r1 by r2, putting the result in r0
|
||||||
|
imm r7 x1 ; We use r7 to decrement r2
|
||||||
|
|
||||||
|
@multiply_round ; Increment and decrement
|
||||||
|
add r0 r1 r0 ; Add r1 to counter
|
||||||
|
sub r2 r7 r2 ; Decrement r2
|
||||||
|
jnz #multiply_round r2 ; If r2 is not zero, again!
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
@ladder_multiply ; Multiply r1 by r2, putting the result in r0 (r1 ends up being r1*r2 + r1)
|
||||||
|
imm r0 x0 ; Let r0 be 0 (to reset it).
|
||||||
|
imm r6 x1 ; let r6 be 1. it is used as a mask to select the first bit
|
||||||
|
imm r7 x80 ; let r7 be 0b1000_0000. it is used as a bit mask for the current bit
|
||||||
|
|
||||||
|
@ladder_multiply_find_high_bit ; go through each bit from low to high. if we encounter a 1, we have found the highest bit.
|
||||||
|
and r7 r2 r3
|
||||||
|
shr r7 r6 r7
|
||||||
|
jiz #ladder_multiply_find_high_bit r3 ; r7 ends up being 2^(l-2), where the bit at l-1 is 1 and is the highest bit
|
||||||
|
|
||||||
|
add r0 r1 r0 ; r0 starts with the value of r1
|
||||||
|
add r1 r1 r1 ; r1 starts with the value of two r1
|
||||||
|
|
||||||
|
@ladder_multiply_round
|
||||||
|
and r2 r7 r3 ; let r3 be r2 & r7. We check if the bit that r7 is masking is high on r2 later
|
||||||
|
shr r7 r6 r7 ; Shift r7 right by 1 (r6 = 1)
|
||||||
|
|
||||||
|
jnz #ladder_multiply_round_1 r3 ; if r3 is 1, we jump. otherwise
|
||||||
|
@ladder_multiply_round_0
|
||||||
|
add r0 r1 r1 ; Let r1 be r0 + r1
|
||||||
|
add r0 r0 r0 ; Double r0
|
||||||
|
jmp #ladder_multiply_round_end ; Skip the second alternative
|
||||||
|
@ladder_multiply_round_1
|
||||||
|
add r0 r1 r0 ; Let r0 be r0 + r1
|
||||||
|
add r1 r1 r1 ; Double r1
|
||||||
|
@ladder_multiply_round_end
|
||||||
|
jnz #ladder_multiply_round r7 ; if the mask still hasn't been shifted out (r7 != 0), do another round
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@print ; Print a string to output, given r0 as the address of a null-terminated string
|
||||||
|
imm r2 x1
|
||||||
|
|
||||||
|
@print_loop
|
||||||
|
drf r0 r1 ; get the value that r0 points to, and put it in r1
|
||||||
|
jiz #print_loop_end r1 ; if the value of r1 is 0, we have reached the end and should exit
|
||||||
|
|
||||||
|
put #output r1 ; output the character
|
||||||
|
|
||||||
|
add r0 r2 r0 ; increment the pointer (r2 = 1)
|
||||||
|
jmp #print_loop ; again!
|
||||||
|
@print_loop_end
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
@loop ; Infinite loop
|
||||||
|
jmp #loop
|
||||||
|
|
||||||
|
|
||||||
|
:511 @output ; I/O address for serial out
|
||||||
53
number.ns
Normal file
53
number.ns
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
|
||||||
|
|
||||||
|
; this was more complicated than i thought it would be
|
||||||
|
@number_to_string_len x0
|
||||||
|
@number_to_string_addr x0
|
||||||
|
@number_to_string ; convert a number in r0 into a null-terminated decimal ascii string stored at the address r1 of r2 digits
|
||||||
|
put #number_to_string_len r2
|
||||||
|
put #number_to_string_addr r1
|
||||||
|
|
||||||
|
; Converting a binary number to a decimal string requires using the double-dabble algorithm
|
||||||
|
@number_to_string_loop
|
||||||
|
; Shift everything to the left
|
||||||
|
add r0 r0 r0 ; Equivalent to doubling, which has the same effect as shifting to the left
|
||||||
|
|
||||||
|
imm r3 x0 ; let r3 be the offset from the start of the string of the current character
|
||||||
|
imm r4 x0 ; r4 is used to store the overflowed bit from the previous shift
|
||||||
|
|
||||||
|
; for each digit
|
||||||
|
@number_to_string_loop_digits
|
||||||
|
drf r7 r5 ; get the current character and put it into r5
|
||||||
|
|
||||||
|
add r5 r5 r5 ; shift to left
|
||||||
|
add r5 r4 r5 ; add the carry
|
||||||
|
|
||||||
|
imm r6 x1
|
||||||
|
add r3 r6 r3 ; increment r3 (r6 = 1)
|
||||||
|
|
||||||
|
; determine if overflowed
|
||||||
|
imm r6 x10
|
||||||
|
and r5 r6 r4
|
||||||
|
jiz #number_to_string_loop_digits_overflow r4
|
||||||
|
imm r4 x1 ; if it overflowed, i.e. r4 (the overflow register) is NOT zero, set it to a 1.
|
||||||
|
@number_to_string_loop_digits_overflow
|
||||||
|
|
||||||
|
snd r5 r7 ; put the current character back/update it
|
||||||
|
|
||||||
|
xor r3 r2 r5 ; if r3 == r2, then r5 would be 0
|
||||||
|
jnz #number_to_string_loop_digits r5 ; therefore, loop while r3 != r2
|
||||||
|
|
||||||
|
; add 3 to each digit greater than five
|
||||||
|
imm
|
||||||
|
@number_to_string_loop_add
|
||||||
|
drf r7 r5 ; get the current character and put it into r5
|
||||||
|
|
||||||
|
; while it is greater than
|
||||||
|
|
||||||
|
xor r3 r2 r5 ; if r3 == r2, then r5 would be 0
|
||||||
|
jnz #number_to_string_loop_add r5 ; therefore, loop while r3 != r2
|
||||||
|
|
||||||
|
|
||||||
|
jnz #number_to_string_loop r0 ; While there are still bits in r0, again!
|
||||||
|
|
||||||
|
ret
|
||||||
BIN
out.rom
Normal file
BIN
out.rom
Normal file
Binary file not shown.
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[project]
|
||||||
|
name = "nc2-assembler"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = []
|
||||||
98
test.ns
Normal file
98
test.ns
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
jmp #start
|
||||||
|
|
||||||
|
; ROM/RAM data would go here before start
|
||||||
|
@number x00 x00 x00 ; The last byte is to terminate it
|
||||||
|
@string "Hello world!" x0A x00 ; Newline and null terminated
|
||||||
|
|
||||||
|
|
||||||
|
@start
|
||||||
|
imm r0 xFF
|
||||||
|
imm r1 xFF
|
||||||
|
|
||||||
|
cal #full_ladder_multiply
|
||||||
|
put #number.1 r0 ; low byte
|
||||||
|
put #number.0 r1 ; high byte
|
||||||
|
|
||||||
|
imm r0 #string ; put the address of #string into r0
|
||||||
|
cal #print ; print it!
|
||||||
|
|
||||||
|
;imm r0 #number
|
||||||
|
;cal #print
|
||||||
|
|
||||||
|
jmp #loop ; limbo/endless loop
|
||||||
|
|
||||||
|
|
||||||
|
@multiply ; Multiply r1 by r2, putting the result in r0. NB! very slow for big values of r2
|
||||||
|
imm r7 x1 ; We use r7 to decrement r2
|
||||||
|
|
||||||
|
@multiply_round ; Increment and decrement
|
||||||
|
add r0 r1 r0 ; Add r1 to counter
|
||||||
|
sub r2 r7 r2 ; Decrement r2
|
||||||
|
jnz #multiply_round r2 ; If r2 is not zero, again!
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
@full_ladder_multiply ; Multiply r0 by r1, putting the result in r0 (r1 ends up being r1*r2 + r1)
|
||||||
|
imm r6 x1 ; let r6 be 1. it is used as a mask to select the first bit
|
||||||
|
imm r7 x80 ; let r7 be 0b1000_0000. it is used as a bit mask for the current bit
|
||||||
|
|
||||||
|
; Move r1 into r5
|
||||||
|
imm r5 x0
|
||||||
|
add r1 r5 r5
|
||||||
|
|
||||||
|
@full_ladder_multiply_find_high_bit ; go through each bit from low to high. if we encounter a 1, we have found the highest bit.
|
||||||
|
and r7 r1 r3
|
||||||
|
shr r7 r6 r7
|
||||||
|
jiz #full_ladder_multiply_find_high_bit r3 ; r7 ends up being 2^(l-2), where the bit at l-1 is 1 and is the highest bit
|
||||||
|
|
||||||
|
imm r1 x0
|
||||||
|
|
||||||
|
add r0 r0 r2 ; r2 starts with the value of two r0
|
||||||
|
imm r3 x0
|
||||||
|
adc r3 r3 r3 ; put the carry into r3
|
||||||
|
|
||||||
|
@full_ladder_multiply_round
|
||||||
|
and r5 r7 r4 ; let r3 be r2 & r7. We check if the bit that r7 is masking is high on r2 later
|
||||||
|
shr r7 r6 r7 ; Shift r7 right by 1 (r6 = 1)
|
||||||
|
|
||||||
|
jnz #full_ladder_multiply_round_1 r4 ; if r4 is 1, we jump. otherwise
|
||||||
|
@full_ladder_multiply_round_0
|
||||||
|
add r0 r2 r2 ; Let r2 be r0 + r2
|
||||||
|
adc r1 r3 r3 ; carry
|
||||||
|
|
||||||
|
add r0 r0 r0 ; Double r0
|
||||||
|
adc r1 r1 r1 ; Double r1 and add carry
|
||||||
|
jmp #full_ladder_multiply_round_end ; Skip the second alternative
|
||||||
|
@full_ladder_multiply_round_1
|
||||||
|
add r0 r2 r0 ; Let r0 be r0 + r2
|
||||||
|
adc r1 r3 r1 ; carry
|
||||||
|
|
||||||
|
add r2 r2 r2 ; Double r0
|
||||||
|
adc r3 r3 r3 ; Double r1 and add carry
|
||||||
|
@full_ladder_multiply_round_end
|
||||||
|
jnz #full_ladder_multiply_round r7 ; if the mask still hasn't been shifted out (r7 != 0), do another round
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
|
||||||
|
@print ; Print a string to output, given r0 as the address of a null-terminated string
|
||||||
|
imm r2 x1
|
||||||
|
|
||||||
|
@print_loop
|
||||||
|
drf r0 r1 ; get the value that r0 points to, and put it in r1
|
||||||
|
jiz #print_loop_end r1 ; if the value of r1 is 0, we have reached the end and should exit
|
||||||
|
|
||||||
|
put #output r1 ; output the character
|
||||||
|
|
||||||
|
add r0 r2 r0 ; increment the pointer (r2 = 1)
|
||||||
|
jmp #print_loop ; again!
|
||||||
|
@print_loop_end
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
@loop ; Infinite loop
|
||||||
|
jmp #loop
|
||||||
|
|
||||||
|
|
||||||
|
:511 @output ; I/O address for serial out
|
||||||
8
uv.lock
generated
Normal file
8
uv.lock
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nc2-assembler"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue