Yuki CPU microcode assembler

This is  a co-project of my Yuki CPU and Yuki SoC on FPGA as well as it is to my two-pass assembler for this CPU. The microassembler converts microinstructions like these

into bytecode like this

This tool actually made it much easier for me to design an instruction set for the Yuki CPU and it also means that the instruction set of the Yuki CPU can be customized at any time. If you are interested in the sourcecode of the microassembler, there is a code listing below.

"""
Title:       Yuki CPU microcode assembler
Author:      R.Lux
Last edited: 14.12.2017

microcode format:
bit 0    bit 14
ddddddssssssaaa
sssss = 6 bit source
ddddd = 6 bit destination
aaa   = 3 bit addressmode
addressmodes:
000 = PC
001 = X
010 = Y
011 = direct
100 = SP
101 = SSP
110 = USP

32 microcode steps : A0 - A4
8 bit instruction : A5 - A12
int the first microcode step, the cpu fetches a new instruction and advances the PC or handles an interrupt so the first microinstruction is always ignored
microcode format example:
<0xFF NOP               open microcode segment for instruction 0xFF called NOP
 .A,MEM                  do a move operation (addressmode=0)
 .B,MEM:1         do a move opeariotn (addressmode follows after dot)
 ,INCX:1                 do a special operation (addressmode follows after dot)
                         empty lines as well as comments(# ...) are ignored
 >            close microcode segment(not really mandatory)
"""


# Memory Array
memory = [0] * 8192
memory_index = 0


sources = { "Z"          : 0,
            "R0"         : 1,
        "R1"         : 2,
        "R2"         : 3,
        "R3"         : 4,
        "R4"         : 5,
        "R5"         : 6,
        "R6"         : 7,
        "R7"         : 8,
        "ALUA"       : 9,
        "ALUB"       : 10,
        "ADD"        : 11,
        "SUB"        : 12,
        "MULL"       : 13,
        "MULH"       : 14,
        "OR"         : 15,
        "AND"        : 16,
        "XOR"        : 17,
        "SHL"        : 18,
        "SHR"        : 19,
        "NOTA"       : 20,
        "NOTB"       : 21,
        "PCL"        : 22,
            "PCM"        : 23,
            "PCH"        : 24,
            "XL"         : 25,
            "XM"         : 26,
            "XH"         : 27,
            "YL"         : 28,
            "YM"         : 29,
            "YH"         : 30,
            "USPL"       : 31,
            "USPM"       : 32,
            "USPH"       : 33,
            "MEM"        : 34,
            "INTMEM"     : 35,
            "POP"        : 36}
           
destinations = {"R0"    : 1,
        "R1"    : 2,
        "R2"    : 3,
                "R3"    : 4,
        "R4"    : 5,
        "R5"    : 6,
        "R6"    : 7,
        "R7"    : 8,
        "ALUA"  : 9,
        "ALUB"  : 10,
        "PCL"   : 11,
        "PCM"   : 12,
        "PCH"   : 13,
        "XL"    : 14,
        "XM"    : 15,
        "XH"    : 16,
        "YL"    : 17,
        "YM"    : 18,
        "YH"    : 19,
        "DL"    : 20,
        "DM"    : 21,
        "DH"    : 22,
        "XCMPL" : 23,
        "XCMPM" : 24,
        "XCMPH" : 25,
        "YCMPL" : 26,
        "YCMPM" : 27,
                "YCMPH" : 28,
                "SSPL"  : 29,
                "SSPM"  : 30,
                "SSPH"  : 31,
                "USPL"  : 32,
                "USPM"  : 33,
                "USPH"  : 34,
                "PCBL"  : 35,
                "PCBM"  : 36,
                "PCBH"  : 37,
                "PCBF"  : 38,
                "PCBB"  : 39,
                "MEM"   : 48,
                "PUSH"  : 49,
                "IR"    : 63}
               
               
specials = {"INCD"      : (44, 0),
            "INCX"      : (37, 0),
        "INCY"      : (39, 0),
            "DECD"      : (45, 0),
        "DECX"      : (38, 0),
        "DECY"      : (40, 0),
        "INCPC"     : (43, 0),
        "INTEN"     : (0, 59),
        "INTDIS"    : (0, 58),
            "INTMOFF"   : (0, 61),
        "SUPOFF"    : (0, 60),
            "NOP"       : (0, 0),
            "END"       : (46, 0),
            "JMP"       : (0, 40),
            "JE"        : (0, 41),
            "JZ"        : (0, 42),
            "JN"        : (0, 43),
            "JC"        : (0, 44),
            "JB"        : (0, 45),
            "JXE"       : (0, 46),
            "JYE"       : (0, 47)}
           
infile = open("microcode.masm")
outfile = open("microcode_content.mif", "w") # Quartus Memory initialisation file as output for now
outfile.write("DEPTH = 8192;\n")
outfile.write("WIDTH = 15;\n")
outfile.write("ADDRESS_RADIX = UNS;\n")
outfile.write("DATA_RADIX = UNS;\n")
outfile.write("CONTENT\nBEGIN\n0 : ")


assembled_instructions = 0
for line in infile.readlines():
    line=line.replace("\n", "")
    if line == "":
        # Ignore empty lines
        continue

    if "#" in line:
        # Ignore comments
        continue
   
    if line[0] == "<":
        # A record starts here
        # Goto start address of record
        instruction = int(line[3:5], 16)
        start_address = instruction * 32 + 1
        memory_index = start_address
        print("Instruction " + str(line.split()[1]) + " (" + line[1:5] + ") gets constructed from 0x%0.3x... " % start_address, end="")
        continue

    if line[0] == ">":
        # The record ends here
        print("to 0x%0.3x" % (memory_index), end="")
        print("(size: " + str(memory_index - start_address) + " bytes)")
        if memory_index - start_address > 32:
            raise Exception("Microfunction to BIG!")
        assembled_instructions += 1


    if line[0] == ".":
        # A move happens here
        line = line.replace(".", "")

        # Default address mode is 0(PC)
        addr_int = 0
        if ":" in line:
            # We have got an address mode defintion
            # save it:
            addr_int = int(line.split(":")[1])
            # remove it
            line = line.split(":")[0]

        src = line.split(",")[0]
        dest = line.split(",")[1]
        src_int = sources[src]
        dest_int = destinations[dest]
           
        microinstruction = addr_int * 4096 + src_int * 64 + dest_int # Contruct arguments to control word
        memory[memory_index] = microinstruction               # Save microinstruction
        memory_index += 1
        continue

    if line[0] == ",":
        # We are in special operation mode now!
        line = line.replace(",", "")

        # Default address mode is 0(PC)
        addr_int = 0
        if ":" in line:
            # We have a special address mode!
            addr_int = addr_int = int(line.split(":")[1])
            # remove it
            line = line.split(":")[0]

        src_int, dest_int = specials[line]
        microinstruction = addr_int * 8192 + src_int * 64 + dest_int # Construct arguments to control word
        memory[memory_index] = microinstruction # Save microinstruction
        memory_index += 1
        continue
       



# Now flush memory into output file

for memorycell in memory:
    outfile.write(str(memorycell) + " ")
outfile.write(";\nEND;")

print("Assembled " + str(assembled_instructions) + " instructions")

# Close the files and leave
outfile.close()
infile.close()