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()
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()