Close

Assembler Revision 2

A project log for muCPU: an 8-bit MCU

An 8-bit load-store CPU with 2 pipeline stages, designed in Logisim and implemented in VHDL + assembler written in Python

reed-fosterReed Foster 04/17/2016 at 04:530 Comments

In this revision of the code editor, I added line numbers and syntax highlighting (sort of). The line numbers consist of text on a canvas that is refreshed every time a key is pressed, a left click occurs, or a click and drag occurs. Syntax highlighting utilizes the Text.search() function supported by Tkinter. I also decided to use two files for this project to keep the main file (slightly) less cluttered. Code (with very few comments) below:

Main file (assemblerv2.py):

from Tkinter import *
from tkFileDialog import *
from tkFont import *
import os.path
import sys
import re

from assemblerlib import *

basefilename = "Untitled"
filename = ""
fileexists = False

canvaswidthdefault = 20
canvaswidth = canvaswidthdefault
currentlength = 1

mnemonicstring = "sll|add|sub|nand|nor|bez|bnez|bgez|blez|bgz|blz|lw|sw"
registerstring = "r0|r1|r2|r3|r4|r5|r6|r7"

def openFile():
    global filename
    global basefilename
    openfilename = askopenfilename()
    if openfilename is not None:
        filename = openfilename
        basefilename = os.path.basename(filename)
        asmfile = open(filename, "r")
        asmfile.seek(0)
        asmdata = asmfile.read()
        textArea.delete("1.0", "end - 1c")
        textArea.insert("1.0", asmdata)
        asmfile.close()
        filemenu.entryconfig(filemenu.index("Save"), state = NORMAL)
        frame.title("muCPU Assembler [" + basefilename + "]")
        frame.focus()
        initonOpen()
    
def saveFile():
    global filename
    asmdata = textArea.get("1.0", "end - 1c")
    asmfile = open(filename, "w")
    asmfile.seek(0)
    asmfile.truncate()
    asmfile.write(asmdata)
    asmfile.close()

def saveFileAs():
    global filename
    global fileexists
    global basefilename
    saveasfilename = asksaveasfilename()
    if saveasfilename is not None:
        filename = saveasfilename
        basefilename = os.path.basename(filename)
        fileexists = True
        asmdata = textArea.get("1.0", "end - 1c")
        asmfile = open(filename, "w")
        asmfile.seek(0)
        asmfile.truncate()
        asmfile.write(asmdata)
        asmfile.close()
        filemenu.entryconfig(filemenu.index("Save"), state = NORMAL)
        frame.title("muCPU Assembler [" + basefilename + "]")
        frame.focus()
    
        
def exitApp():
    frame.destroy()
    sys.exit()
    
def compileASM():
    global filename
    cpu_out = ""
    asm_in = textArea.get("1.0", END)
    asmlines = re.split("\n", asm_in)
    for i in range (len(asmlines)):
        if (asmlines[i] != ""):
            cpu_out += str(i) + " => x\"" + decode(asmlines[i]) + "\",\n"
    name, ext = os.path.splitext(filename)
    hexfilename = name + ".hex"
    hexfile = open(hexfilename, "w")
    hexfile.seek(0)
    hexfile.truncate()
    hexfile.write(cpu_out)
    hexfile.close()

def updateLinesEvent(event):
    drawLinenums()

def updateHighlightEvent(event):
    highlightSyntax((re.split("\.", textArea.index(INSERT))[0] + ".0"), INSERT)

def initonOpen():
    highlightSyntax("1.0", END)
    drawLinenums()


def drawLinenums():
    global canvaswidth
    global canvaswidthdefault
    linenumbers.delete("all")
    i = textArea.index("@0,0")
    while True:
        dline = textArea.dlineinfo(i)
        if dline is None: break
        y = dline[1]
        linenum = str(i).split(".")[0]
        linenumbers.create_text(canvaswidth, y, anchor=NE,text=linenum)
        i = textArea.index("%s+1line" % i)
        linenumbers.config(width = canvaswidth)
        

def highlightSyntax(start, end):
    global mnemonicstring
    global registerstring
    mnemoniclen = StringVar()
    registerlen = StringVar()
    numberlen = StringVar()
    pos = start
    while True:
        pos = textArea.search(mnemonicstring, pos, end, regexp = True, count = mnemoniclen)
        #print pos
        if not pos: break
        textArea.tag_add("mnemonic", pos, pos + " + " + str(mnemoniclen.get()) + "c")
        posarry = re.split("\.", pos)
        posarry[1] = str(int(posarry[1]) + 1)
        pos = posarry[0] + "." + posarry[1] + ("0" * (len(posarry[1]) - 2))
    pos = start
    while True:
        pos = textArea.search("-?\\d", pos, end, regexp = True, count = numberlen)
        #print pos
        if not pos: break
        textArea.tag_add("number", pos, pos + " + " + str(numberlen.get()) + "c")
        posarry = re.split("\.", pos)
        posarry[1] = str(int(posarry[1]) + 1)
        pos = posarry[0] + "." + posarry[1] + ("0" * (len(posarry[1]) - 2))
    pos = start
    while True:
        pos = textArea.search(registerstring, pos, end, regexp = True, count = registerlen)
        #print pos
        if not pos: break
        textArea.tag_add("register", pos, pos + " + " + str(registerlen.get()) + "c")
        posarry = re.split("\.", pos)
        posarry[1] = str(int(posarry[1]) + 1)
        pos = posarry[0] + "." + posarry[1] + ("0" * (len(posarry[1]) - 2))
    
        
Tk().withdraw()
frame = Toplevel(bg="#D8D8D8")

frame.bind("<Key>", updateLinesEvent)
frame.bind("<Button-1>", updateLinesEvent)
frame.bind("<B1-Motion>", updateLinesEvent)
frame.bind("<ButtonRelease-1>", updateLinesEvent)
frame.bind("<Key>", updateHighlightEvent)

scrollbar = Scrollbar(frame)
scrollbar.pack(side = RIGHT, fill = Y)
frame.title("muCPU Assembler [" + basefilename + "]")
textArea = Text(frame, height = 30, width = 100, padx = 3, pady = 3, yscrollcommand = scrollbar.set)
textArea.pack(side=RIGHT)
scrollbar.config(command=textArea.yview)

mnemonicfont = Font(frame, family = "Courier", size = 10, weight = "bold")
textArea.tag_config("mnemonic", foreground = "blue", font = mnemonicfont)
numberfont = Font(frame, family = "Courier", size = 10)
textArea.tag_config("number", foreground = "#585858", font = numberfont)
registerfont = Font(frame, family = "Courier", size = 10)#, slant = "italic")
textArea.tag_config("register", foreground = "red", font = registerfont)

linenumbers = Canvas(frame, width = canvaswidthdefault, height = 487, bg = "#D8D8D8", highlightbackground = "#D8D8D8")
linenumbers.pack()

menubar = Menu(frame)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", command=openFile)
filemenu.add_command(label="Save", command=saveFile, state = DISABLED)
filemenu.add_command(label="Save as...", command=saveFileAs)
filemenu.add_command(label="Exit", command=exitApp)
menubar.add_cascade(label="File", menu=filemenu)
runmenu = Menu(menubar, tearoff=0)
runmenu.add_command(label="Compile", command=compileASM)
menubar.add_cascade(label="Run", menu=runmenu)
frame.config(menu=menubar)

initonOpen()

frame.resizable(0,0)
frame.mainloop()

#Current code
"""
lw r4, 176(r0)
lw r3, 177(r0)
sub r2, r4, r1
bez r2, 8
sw r1, 252(r0)
bez r0, -8
add r1, r1, r3
sll r0, r0, r0
bez r0, -2
"""

Library file (assemblerlib.py):

import re

def asmtoint(asm):
    asm_split = re.split(" |, |\(|\)", asm)
    args = []
    for i in range (len(asm_split)):
        if (asm_split[i] != ""):
            args.append(asm_split[i])
    #print args
    opcode = 0
    func = 0
    rd = 0
    rs = 0
    rt = 0
    imm = 0
    if (args[0] == "sll"):
        if (len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 0
        func = 0
        rd = int(args[1][1:])
        rs = int(args[2][1:])
        rt = int(args[3][1:])
    elif (args[0] == "add"):
        if (len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 0
        func = 1
        rd = int(args[1][1:])
        rs = int(args[2][1:])
        rt = int(args[3][1:])
    elif (args[0] == "sub"):
        if (len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 0
        func = 2
        rd = int(args[1][1:])
        rs = int(args[2][1:])
        rt = int(args[3][1:])
    elif (args[0] == "nand"):
        if (len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 0
        func = 3
        rd = int(args[1][1:])
        rs = int(args[2][1:])
        rt = int(args[3][1:])
    elif (args[0] == "nor"):
        if (len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 0
        func = 4
        rd = int(args[1][1:])
        rs = int(args[2][1:])
        rt = int(args[3][1:])
    elif (args[0] == "bez"):
        if (len(args) != 3):
            return 0,0,0,0,0,0
        opcode = 1
        rt = 0
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "bnez"):
        if (len(args) != 3):
            return 0,0,0,0,0,0
        opcode = 1
        rt = 1
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "bgez"):
        if (len(args) != 3):
            return 0,0,0,0,0,0
        opcode = 1
        rt = 2
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "blez"):
        if (len(args) != 3):
            return 0,0,0,0,0,0
        opcode = 1
        rt = 3
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "bgz"):
        if (len(args) != 3):
            return 0,0,0,0,0,0
        opcode = 1
        rt = 4
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "blz"):
        if (len(args) != 3):
            return 0
        opcode = 1
        rt = 5
        rs = int(args[1][1:])
        imm = int(args[2])
    elif (args[0] == "lw"):
        if (args[-1] == ''):
            args = args[0:-1]
        if (len(args) != 3 and len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 2
        rt = int(args[1][1:])
        if (len(args) == 3):
            imm = 0
            rs = int(args[2][1:])
        else:
            imm = int(args[2])
            rs = int(args[3][1:])
    elif (args[0] == "sw"):
        if (args[-1] == ''):
            args = args[0:-1]
        if (len(args) != 3 and len(args) != 4):
            return 0,0,0,0,0,0
        opcode = 3
        rt = int(args[1][1:])
        if (len(args) == 3):
            imm = 0
            rs = int(args[2][1:])
        else:
            imm = int(args[2])
            rs = int(args[3][1:])
    else:
        return 0,0,0,0,0,0
    return opcode, rs, rt, rd, func, imm

def inttohex(opcode, rs, rt, rd, func, imm):
    if (opcode == 0):
        opstr = format(opcode, '02b')
        rsstr = format(rs, '03b')
        rtstr = format(rt, '03b')
        rdstr = format(rd, '03b')
        fnstr = format(func, '05b')
        #print opstr, rsstr, rtstr, rdstr, fnstr
        instruction = opstr + rsstr + rtstr + rdstr + fnstr
    else :
        opstr = format(opcode, '02b')
        rtstr = format(rt, '03b')
        rsstr = format(rs, '03b')
        if (imm < 0):
            imm2s = ((-imm) ^ 255) + 1
            immstr = format(imm2s, '08b')
        else :
            immstr = format(imm, '08b')
        #print opstr, rtstr, rsstr, immstr
        instruction = opstr + rsstr + rtstr + immstr
    return format(int(instruction, 2), '04x')

def decode(asm):
    opcode, rs, rt, rd, func, imm = asmtoint(asm)
    instruction = inttohex(opcode, rs, rt, rd, func, imm)
    return instruction

Discussions