I recently put together an assembler for a CPU I designed. I'm looking for feedback on my program structure, formatting, or anything else. I'm self taught on all of this so I don't have opportunities for it to be reviewed. I'm also new to python programming so if anything else doesn't look right please set me on the right track.
Assembler.py
import sys
from tables import *
symbols = {}
# Memory map
symbols["IN0"] = 0xfff8
symbols["IN1"] = 0xfff9
symbols["OUT0"] = 0xfffa
symbols["OUT1"] = 0xfffb
def to_bin(n, bits):
n = bin(n & 2**bits-1)[2:]
return "{:0>{}}".format(n, bits)
def reg(s):
n = int(s[1:])
return to_bin(n, 3)
def value(s):
n = 0
if s[0].isdigit():
if s[:2] == "0b":
n = int(s[2:], 2)
elif s[:2] == "0x":
n = int(s[2:], 16)
else:
n = int(s)
else:
if s in symbols:
n = symbols[s] # get address
else:
print("Error: undefined symbol \"{}\"".format(s))
quit()
return n
with open("Programs/"+sys.argv[1], "r") as fileIn, \
open("Programs/"+sys.argv[1]+".asm", "w+") as fileOut:
print("### First Pass - Mapping Symbols to Addresses ###")
address = 0
for lineNum, line in enumerate(fileIn, start = 1):
tokens = line.split("#")[0].split()
if not tokens:
continue # skip empty lines
if tokens[0][-1] == ":": # found symbol
if tokens[0][:-1] in symbols:
print("Error: duplicate symbol \"{}\" on line {}".format(tokens[0], lineNum))
quit()
else:
symbols[tokens[0][:-1]] = address # add symbol to dictionary
del tokens[0]
if tokens:
address += 2 if tokens[0] == "movi" else 1
fileIn.seek(0)
print("...Done\n")
print("### Second Pass - Translating into machine code ###")
address = 0
for lineNum, line in enumerate(fileIn, start = 1):
tokens = line.split("#")[0].split(":")[-1].split() # remove comments and symbols
if not tokens:
continue # skip empty lines
asm = ""
if tokens[0] in RRR and len(tokens) == 4:
asm = "000" + reg(tokens[1]) + reg(tokens[2]) + reg(tokens[3]) + RRR[tokens[0]]
elif tokens[0] in RRI and len(tokens) == 4:
asm = RRI[tokens[0]] + reg(tokens[1]) + reg(tokens[2]) + to_bin(value(tokens[3]), 7)
elif tokens[0] in RI and len(tokens) == 3:
asm = RI[tokens[0]] + reg(tokens[1]) + to_bin(value(tokens[2]), 10)
elif tokens[0] in JMP and len(tokens) == 3:
asm = JMP[tokens[0]] + reg(tokens[1]) + to_bin(value(tokens[2]) - address, 10)
elif tokens[0] == "nop" and len(tokens) == 1:
asm = "0"*16
elif tokens[0] == "halt" and len(tokens) == 1:
asm = JMP["brfl"] + "0"*13
elif tokens[0] == "movi" and len(tokens) == 2:
asm = RI["lui"] + reg(tokens[1]) + to_bin(value(tokens[2]) >> 6 & 0x3ff, 10)
fileOut.write("{:04x}".format(int(asm, 2)) + "\n")
address += 1
asm = RRI["addi"] + reg(tokens[1]) + reg(tokens[1]) + to_bin(value(tokens[2]) & 0x3f, 7)
elif tokens[0] == ".fill" and len(tokens) == 2:
asm = to_bin(value(tokens[1]), 16)
else:
print("Error: invalid instruction on line {}".format(lineNum))
quit()
address += 1
fileOut.write("{:04x}".format(int(asm, 2)) + "\n")
print("...Done\n")
print("Assembling finished")
Tables.py
# Categorized opcodes based on number of arguments and type
# RRR, RRI, RI, JMP
RRR = {
"add" : "0000",
"sub" : "0001",
"nor" : "0010",
"and" : "0011",
"ior" : "0100",
"eor" : "0101",
"shl" : "0110",
"shr" : "0111",
"eql" : "1000",
"neq" : "1001",
"gtr" : "1010",
"lss" : "1011",
"mul" : "1100",
"mulu" : "1101",
"div" : "1110",
"mod" : "1111"
}
RRI = {
"addi" : "001",
"jalr" : "010",
"lwm" : "011",
"swm" : "100"
}
RI = {
"lui" : "101"
}
JMP = {
"brtr" : "110",
"brfl" : "111"
}
Example multiplication program
start: lwm r1 r0 numA
lwm r1 r1 0
lwm r2 r0 numB
lwm r2 r2 0
addi r3 r0 0 # r3 = 0
addi r7 r0 1 # r7 = 1
brfl r0 enter # enter loop
doAdd: add r3 r3 r1 # r3 += A
loop: shl r1 r1 r7 # r1 << 1
enter: and r6 r2 r7 # r6 = r2 & 1
shr r2 r2 r7 # r2 >> 1
brtr r6 doAdd # was B odd?
brtr r2 loop
lwm r1 r0 prod
swm r3 r1 0
brfl r0 start
numA: .fill IN0
numB: .fill IN1
prod: .fill OUT0