Python Forum
I wrote an emulator!
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
I wrote an emulator!
#1
I have been inspired by YouTubers like Ben Eater and James Sharman and decided to start dabbling in electronics with my hobby eventually culminating in the design and implementation of a CPU for which I can write an OS and various other programs in assembly. To try to nail down the architecture and have something to play with while I plan things out and get more skilled in electronics, I've written an emulator in Python. (I know it's not a very well suited language for this purpose, but I'm crazy.)

I am now at the point of designing the program loading algorithm, comprehensively testing all the instructions, (It's a microcode level emulator and with the crude code gen I used to implement the microcode, it's kind of Turing sandbox-ish.) The source below is the emulator proper. The constants file is also included and it's a dependency. I like to namespace my constants in class variables. In most cases, it makes the code
more readable.
###
### emulator.py
###

from emulator_constants import *


# Usually a great convenience, Pythons expanding integer type
# is a hinderance when writing an emulator.
# 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF is a valid Python integer
# even though it would overflow any common integer size in other languages
# because Python just keeps adding bytes. Using Python for purposes for
# which it was not intended FTW!!!
class FixedWidthInt:

    def __init__ (self, bitwidth, init = 0):
        self.bitwidth = bitwidth
        self.bitmask = (2 ** self.bitwidth) - 1
        self.value = init & self.bitmask

    def set (self, newValue):
        self.value = self.bitmask & newValue

    def get (self):
        return self.value

class CPU:

    def __init__ (self):

        # Set up the register stack and all the CPU internal data.
        self.ra = FixedWidthInt(16)
        self.rb = FixedWidthInt(16)
        self.rc = FixedWidthInt(16)
        self.ro = FixedWidthInt(16)
        self.sp = FixedWidthInt(16, 0xFFFF)
        self.ao = FixedWidthInt(16, 0xFFFF)
        self.PC = FixedWidthInt(16)
        self.tmp = FixedWidthInt(16)
        self.statusWord = FixedWidthInt(8)

        self.RAM = [FixedWidthInt(8) for i in range(0, 2**16)]
        
        self.PC = FixedWidthInt(16)
        self.tmp = FixedWidthInt(16)

        # The CPU buses.
        self.addrBus = FixedWidthInt(16)
        self.dataBus = FixedWidthInt(16)
        self.RHSBus = FixedWidthInt(16)
        self.LHSBus = FixedWidthInt(16)

        # Set up of all the memory components of the control logic
        # including the control ROM.
        self.ins = FixedWidthInt(8)
        self.flagIn = FixedWidthInt(1)
        self.UProgCtr = FixedWidthInt(4, 0xF)
        self.ctrlWord = FixedWidthInt(32)
        self.loadMicroprogram()

    def loadMicroprogram (self):
        ROMFile = "./leverage_architecture_ROM.txt"
        with open(ROMFile, "r") as f:
            self.controlROM = eval(f.read())

    def readRAM (self):
        significantByte = self.RAM[self.addrBus.get()].get()
        insignificantByte = self.RAM[(self.addrBus.get() + 1) & 0xFFFF].get()
        return (significantByte * 2**8) | insignificantByte

    def writeRAM (self):
        significantByte = (self.dataBus.get() // 2 ** 8) & 0xFF
        insignificantByte = self.dataBus.get() & 0xFF
        self.RAM[self.addrBus.get()].set(significantByte)
        self.RAM[(self.addrBus.get() + 1) & 0xFFFF].set(insignificantByte)

    def getAcc(self):
        accSelection = self.statusWord.get() & 0xF
        if accSelection == const.instrSetRegIDs.ra: return self.ra.get()
        elif accSelection == const.instrSetRegIDs.rb: return self.rb.get()
        elif accSelection == const.instrSetRegIDs.rc: return self.rc.get()
        elif accSelection == const.instrSetRegIDs.ro: return self.ro.get()
        elif accSelection == const.instrSetRegIDs.sp: return self.sp.get()
        elif accSelection == const.instrSetRegIDs.ao: return self.ao.get()
        elif accSelection == const.instrSetRegIDs.IO:
            print("Kyle, your fucking microcode gen is broken again! The IO")
            print("device should never be selected as the accumulator!")
            input()
            exit()
        elif accSelection == const.instrSetRegIDs.PC: return self.PC.get()
        else:
            print("Kyle, your damn selectAcc function is broken! It shouldn't")
            print("be storing numbers larger than 7 in the lower 4 bits of the")
            print("status word!")
            input()
            exit()

    def setAcc(self):
        accSelection = self.statusWord.get() & 0xF
        if accSelection == const.instrSetRegIDs.ra: self.ra.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.rb: self.rb.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.rc: self.rc.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.ro: self.ro.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.sp: self.sp.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.ao: self.ao.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.IO:
            print("Kyle, your fucking microcode gen is broken again! The IO")
            print("device should never be selected as the accumulator!")
            input()
            exit()
        elif accSelection == const.instrSetRegIDs.PC: self.PC.set(self.dataBus.get())
        else:
            print("Kyle, your damn selectAcc function is broken! It shouldn't")
            print("be storing numbers larger than 7 in the lower 4 bits of the")
            print("status word!")
            input()
            exit()

    def selectAcc (self):
        self.statusWord.set((self.statusWord.get() & 0xF0) | (self.ins.get() & 0x7))

    def computeFlags (self, ALUOutput):
        self.statusWord.set(self.statusWord.get() & 0xF) # Get rid of the old flag values.

        # Compute the sign flag.
        if ALUOutput & 2**15 != 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.sign)

        # Compute the carry flag.
        if ALUOutput & 0x1_0000 != 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.carry)

        # Compute the overflow flag.
        if (ALUOutput & 2**15 != 0 and self.LHSBus.get() & 2**15 == 0 and self.RHSBus.get() & 2**15 == 0) or (ALUOutput & 2**15 == 0 and self.LHSBus.get() & 2**15 != 0 and self.RHSBus.get() & 2**15 != 0):
            self.statusWord.set(self.statusWord.get() | const.flagPos.of)

        # Compute the zero flag.
        if ALUOutput & 0xFFFF == 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.zero)

    def getInput (self):
        userInput = input("\n::>")
        if len(userInput) == 0: return 0
        if len(userInput) == 1: return ord(userInput) * 2**8
        significantByte = ord(userInput[-2])
        insignificantByte = ord(userInput[-1])
        return (significantByte * 2**8) | insignificantByte

    def output (self):
        firstChr = chr((self.dataBus.get() // 2**8) & 0xFF)
        secondChr = chr(self.dataBus.get() & 0xFF)
        print(firstChr + secondChr, end = "")

    def readSEXTByte (self):
        byte = self.RAM[self.addrBus.get()].get()
        if byte & 2**7 != 0:
            return (0xFF * 2**8) | byte
        else:
            return byte

    def writeSingleByte (self):
        byte = self.dataBus.get() & 0xFF
        self.RAM[self.addrBus.get()].set(byte)

    def clockLoop (self):

        while True:

            # Falling Edge

            # first compute the address of the next
            # control word to be read from the control ROM.
            flagBit = 0
            ctrlWord = self.ctrlWord.get()
            currentFlagSelection = (ctrlWord // const.fieldPositionsAndMasks.flagSelPos) & const.fieldPositionsAndMasks.flagSelMask
            # Get the flag bit that goes into the addressing input.
            currentStatWord = self.statusWord.get()
            if (ctrlWord // const.fieldPositionsAndMasks.negFlagsPos) & const.fieldPositionsAndMasks.negFlagsMask == 1:
                currentStatWord = ~currentStatWord

            if currentFlagSelection == const.flagSelectField.sign:
                flagBit = (currentStatWord // const.flagPos.sign) & 1
            elif currentFlagSelection == const.flagSelectField.carry:
                flagBit = (currentStatWord // const.flagPos.carry) & 1
            elif currentFlagSelection == const.flagSelectField.overflow:
                flagBit = (currentStatWord // const.flagPos.of) & 1
            elif currentFlagSelection == const.flagSelectField.zero:
                flagBit = (currentStatWord // const.flagPos.zero) & 1
            elif currentFlagSelection == const.flagSelectField.not_signOrZero:
                if (currentStatWord // const.flagPos.sign) & 1 == 0 and (currentStatWord // const.flagPos.zero) & 1 == 0:
                    flagBit = 1
                else:
                    flagBit = 0
            elif currentFlagSelection == const.flagSelectField.noFlg:
                flagBit = 0
            elif currentFlagSelection == const.flagSelectField.incPC:
                flagBit = 0
            elif currentFlagSelection == const.noFlgWr:
                flagBit = 0
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            # Reset the microinstruction counter if the UProgCtr reset bit is set.
            # Otherwise, increment.
            if (ctrlWord // const.fieldPositionsAndMasks.microCounterResetPos) & const.fieldPositionsAndMasks.microCounterResetMask == 1:
                self.UProgCtr.set(0)
            else:
                self.UProgCtr.set(self.UProgCtr.get() + 1)

            # Compute the address input to the control ROM to get the next control word.
            ctrlRomAddr = 0 | (self.UProgCtr.get() * const.ctrlROMFieldPositions.UProgCtr)
            ctrlRomAddr |= flagBit * const.ctrlROMFieldPositions.flagBit
            ctrlRomAddr |= self.ins.get() * const.ctrlROMFieldPositions.ins
            self.ctrlWord.set(self.controlROM[ctrlRomAddr]); ctrlWord = self.controlROM[ctrlRomAddr]

            # First check whether any of the bus control fields have halt codes in them
            # then simulate the propagation of data onto busses. 
            addrCtrlField = (ctrlWord // const.fieldPositionsAndMasks.addrBusCtrlPos) & const.fieldPositionsAndMasks.addrBusCtrlMask
            dataEnCtrlField = (ctrlWord // const.fieldPositionsAndMasks.dataBusCtrlPos) & const.fieldPositionsAndMasks.dataBusCtrlMask
            dataWrCtrlField = (ctrlWord // const.fieldPositionsAndMasks.dataBusWriteCtrlPos) & const.fieldPositionsAndMasks.dataBusWriteCtrlMask
            LHSCtrlField = (ctrlWord // const.fieldPositionsAndMasks.LHSBusCtrlPos) & const.fieldPositionsAndMasks.LHSBusCtrlMask
            RHSCtrlField = (ctrlWord // const.fieldPositionsAndMasks.RHSBusCtrlPos) & const.fieldPositionsAndMasks.RHSBusCtrlMask
            ALUFuncSelField = (ctrlWord // const.fieldPositionsAndMasks.ALUFuncSelPos) & const.fieldPositionsAndMasks.ALUFuncSelMask
            constSelField = (ctrlWord // const.fieldPositionsAndMasks.immSelPos) & const.fieldPositionsAndMasks.immSelMask
            flagSelField = (ctrlWord // const.fieldPositionsAndMasks.flagSelPos) & const.fieldPositionsAndMasks.flagSelMask

            if dataEnCtrlField == const.dataEnableField.INVALID_STATE_HALT:
                print("The CPU has reached an invalid state.")
                input()
                exit()
            elif dataEnCtrlField == const.dataEnableField.INVALID_INSTR_HALT:
                print(f"The instruction {hex(self.ins.get())} is invalid.")
                input()
                exit()

            if dataWrCtrlField == const.dataWriteField.HALT:
                print("The CPU has halted. The program produced no errors.")
                input()
                exit()

            # Compute the value of the selected constant.
            selectedConst = const.constTable[constSelField]
            
            # Compute the value of the address bus in cases where it does not come from the ALU.
            if addrCtrlField == const.addrEnableField.raEnable: self.addrBus.set(self.ra.get())
            elif addrCtrlField == const.addrEnableField.rbEnable: self.addrBus.set(self.rb.get())
            elif addrCtrlField == const.addrEnableField.rcEnable: self.addrBus.set(self.rc.get())
            elif addrCtrlField == const.addrEnableField.roEnable: self.addrBus.set(self.ro.get())
            elif addrCtrlField == const.addrEnableField.spEnable: self.addrBus.set(self.sp.get())
            elif addrCtrlField == const.addrEnableField.aoEnable: self.addrBus.set(self.ao.get())
            elif addrCtrlField == const.addrEnableField.PCEnable: self.addrBus.set(self.PC.get())
            elif addrCtrlField == const.addrEnableField.tmpEnable: self.addrBus.set(self.tmp.get())
            elif addrCtrlField == const.addrEnableField.incPC: pass
            elif addrCtrlField == const.addrEnableField.NO_ENABLE:
                pass
            elif addrCtrlField == const.addrEnableField.ALUEnable:
                pass
            else:
                print("Kyle, fix your damn microcode generator!!!")
                input()
                exit()
            
            # Since the ALU output can be enabled onto the address bus, the ALU output must
            # be computed first. This means RHS and LHS are computed before addr. The
            # address bus can influence data, meaning data must be computed last.
            if LHSCtrlField == const.LHSEnableField.NO_ENABLE: pass
            elif LHSCtrlField == const.LHSEnableField.raEnable: self.LHSBus.set(self.ra.get())
            elif LHSCtrlField == const.LHSEnableField.rbEnable: self.LHSBus.set(self.rb.get())
            elif LHSCtrlField == const.LHSEnableField.rcEnable: self.LHSBus.set(self.rc.get())
            elif LHSCtrlField == const.LHSEnableField.roEnable: self.LHSBus.set(self.ro.get())
            elif LHSCtrlField == const.LHSEnableField.spEnable: self.LHSBus.set(self.sp.get())
            elif LHSCtrlField == const.LHSEnableField.aoEnable: self.LHSBus.set(self.ao.get())
            elif LHSCtrlField == const.LHSEnableField.RAMEnable: self.LHSBus.set(self.readRAM())
            elif LHSCtrlField == const.LHSEnableField.accEnable: self.LHSBus.set(self.getAcc())
            elif LHSCtrlField == const.LHSEnableField.selectAcc: self.selectAcc()
            elif LHSCtrlField == const.LHSEnableField.PCEnable: self.LHSBus.set(self.PC.get())
            elif LHSCtrlField == const.LHSEnableField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            if RHSCtrlField == const.RHSEnableField.NO_ENABLE: pass
            elif RHSCtrlField == const.RHSEnableField.raEnable: self.RHSBus.set(self.ra.get())
            elif RHSCtrlField == const.RHSEnableField.rbEnable: self.RHSBus.set(self.rb.get())
            elif RHSCtrlField == const.RHSEnableField.rcEnable: self.RHSBus.set(self.rc.get())
            elif RHSCtrlField == const.RHSEnableField.roEnable: self.RHSBus.set(self.ro.get())
            elif RHSCtrlField == const.RHSEnableField.spEnable: self.RHSBus.set(self.sp.get())
            elif RHSCtrlField == const.RHSEnableField.aoEnable: self.RHSBus.set(self.ao.get())
            elif RHSCtrlField == const.RHSEnableField.RAMEnable: self.RHSBus.set(self.readRam())
            elif RHSCtrlField == const.RHSEnableField.tmpEnable: self.RHSBus.set(self.getAcc())
            elif RHSCtrlField == const.RHSEnableField.constEnable: self.RHSBus.set(selectedConst)
            elif RHSCtrlField == const.RHSEnableField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            # Compute the ALU output
            ALUOutput = 0
            if ALUFuncSelField == const.ALUFuncField.f_add: ALUOutput = self.LHSBus.get() + self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_sub: ALUOutput = self.LHSBus.get() - self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_and: ALUOutput = self.LHSBus.get() & self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_or: ALUOutput = self.LHSBus.get() | self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_xor: ALUOutput = (~self.LHSBus.get() & self.RHSBus.get()) | (self.LHSBus.get() & ~self.RHSBus.get())
            elif ALUFuncSelField == const.ALUFuncField.f_nor: ALUOutput = ~(self.LHSBus.get() | self.RHSBus.get())
            elif ALUFuncSelField == const.ALUFuncField.f_lsh: ALUOutput = self.LHSBus.get() * 2
            elif ALUFuncSelField == const.ALUFuncField.f_rsh: ALUOutput = self.LHSBus.get() // 2
            elif ALUFuncSelField == const.ALUFuncField.f_not: ALUOutput = ~self.LHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_addC: ALUOutput = self.LHSBus.get() + self.RHSBus.get() + ((self.statusWord.get() // const.flagPos.carry) & 1)
            elif ALUFuncSelField == const.ALUFuncField.f_subB: ALUOutput = self.LHSBus.get() - self.RHSBus.get() - ((self.statusWord.get() // const.flagPos.carry) & 1)
            elif ALUFuncSelField == const.ALUFuncField.f_NOP:
                pass
            elif ALUFuncSelField == const.ALUFuncField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()
            
            # Compute the value of the address bus in case of the value coming from the ALU.
            if addrCtrlField == const.addrEnableField.ALUEnable: self.addrBus.set(ALUOutput)

            # Compute the value of the data bus.
            if dataEnCtrlField == const.dataEnableField.NO_ENABLE: pass
            elif dataEnCtrlField == const.dataEnableField.raEnable: self.dataBus.set(self.ra.get())
            elif dataEnCtrlField == const.dataEnableField.rbEnable: self.dataBus.set(self.rb.get())
            elif dataEnCtrlField == const.dataEnableField.rcEnable: self.dataBus.set(self.rc.get())
            elif dataEnCtrlField == const.dataEnableField.roEnable: self.dataBus.set(self.ro.get())
            elif dataEnCtrlField == const.dataEnableField.spEnable: self.dataBus.set(self.sp.get())
            elif dataEnCtrlField == const.dataEnableField.aoEnable: self.dataBus.set(self.ao.get())
            elif dataEnCtrlField == const.dataEnableField.ALUEnable: self.dataBus.set(ALUOutput)
            elif dataEnCtrlField == const.dataEnableField.PCEnable: self.dataBus.set(self.PC.get())
            elif dataEnCtrlField == const.dataEnableField.statWordEnable: self.dataBus.set(self.statusWord.get())
            elif dataEnCtrlField == const.dataEnableField.RAMEnableDword: self.dataBus.set(self.readRAM())
            elif dataEnCtrlField == const.dataEnableField.IO_WAIT: self.dataBus.set(self.getInput())
            elif dataEnCtrlField == const.dataEnableField.tmpEnable: self.dataBus.set(self.tmp.get())
            elif dataEnCtrlField == const.dataEnableField.RAMEnableSEXTByte: self.dataBus.set(self.readSEXTByte())

            # Rising Edge
            # Simulate the latching of data into registers and memory.
            if dataWrCtrlField == const.dataWriteField.NO_WRITE: pass
            elif dataWrCtrlField == const.dataWriteField.raWrite: self.ra.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.rbWrite: self.rb.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.rcWrite: self.rc.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.roWrite: self.ro.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.spWrite: self.sp.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.aoWrite: self.ao.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.PCWrite: self.PC.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.IOWrite: self.output()
            elif dataWrCtrlField == const.dataWriteField.tmpWrite: self.tmp.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.RAMWriteDword: self.writeRAM()
            elif dataWrCtrlField == const.dataWriteField.RAMWriteByte: self.writeSingleByte()
            elif dataWrCtrlField == const.dataWriteField.accWrite: self.setAcc()
            elif dataWrCtrlField == const.dataWriteField.statWordWrite: self.statusWord.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.irWrite: self.ins.set(self.dataBus.get())
            
            # If any control field is saying to increment PC, do it!
            if addrCtrlField == const.addrEnableField.incPC or LHSCtrlField == const.LHSEnableField.incPC or RHSCtrlField == const.RHSEnableField.incPC or flagSelField == const.flagSelectField.incPC or ALUFuncSelField == const.ALUFuncField.incPC:
                self.PC.set(self.PC.get() + selectedConst)
            
            # If The ALU isn't doing a NOP or incrementing PC, and the flag control field doesn't say not to write the flags, compute the flag values.
            if ALUFuncSelField != const.ALUFuncField.f_NOP and ALUFuncSelField != const.ALUFuncField.incPC and flagSelField != const.flagSelectField.noFlgWr:
                self.computeFlags(ALUOutput)

            testing = True
            if testing:
                print(f"ra: hex: {hex(self.ra.get())}, binary: {bin(self.ra.get())}, ascii: {chr(self.ra.get() // 2**8 & 0xFF)}{chr(self.ra.get() & 0xFF)}")
                print(f"rb: hex: {hex(self.rb.get())}, binary: {bin(self.rb.get())}, ascii: {chr(self.rb.get() // 2**8 & 0xFF)}{chr(self.rb.get() & 0xFF)}")
                print(f"rc: hex: {hex(self.rc.get())}, binary: {bin(self.rc.get())}, ascii: {chr(self.rc.get() // 2**8 & 0xFF)}{chr(self.rc.get() & 0xFF)}")
                print(f"ro: hex: {hex(self.ro.get())}, binary: {bin(self.ro.get())}, ascii: {chr(self.ro.get() // 2**8 & 0xFF)}{chr(self.ro.get() & 0xFF)}")
                print(f"sp: hex: {hex(self.sp.get())}, binary: {bin(self.sp.get())}, ascii: {chr(self.sp.get() // 2**8 & 0xFF)}{chr(self.sp.get() & 0xFF)}")
                print(f"ao: hex: {hex(self.ao.get())}, binary: {bin(self.ao.get())}, ascii: {chr(self.ao.get() // 2**8 & 0xFF)}{chr(self.ao.get() & 0xFF)}")
                print(f"PC: hex: {hex(self.PC.get())}, binary: {bin(self.PC.get())}, ascii: {chr(self.PC.get() // 2**8 & 0xFF)}{chr(self.PC.get() & 0xFF)}")
                print(f"tmp: hex: {hex(self.tmp.get())}, binary: {bin(self.tmp.get())}, ascii: {chr(self.tmp.get() // 2**8 & 0xFF)}{chr(self.tmp.get() & 0xFF)}")
                print(f"ins: hex: {hex(self.ins.get())},    Status Word: binary: {bin(self.statusWord.get())}")

                print("\n\nThe lower 32 bytes of memory:")
                byteCount = 0
                while byteCount <= 32:
                    print(f"Addresses {byteCount} - {byteCount + 7}: ", end = "")
                    for i in range(0, 8):
                        print(f" {hex(self.RAM[byteCount].get())}", end = "")
                        byteCount += 1
                    print("")

                input("Press ENTER to continue.")

def main ():
    # This will be expanded to include a routine for loading programs into memory but for now, it just
    # instantiates an object of type CPU and runs its clockLoop method.
    test = CPU()
    test.clockLoop()

if __name__ == "__main__": main()
The constants file:
###
### emulator_constants.py
###

class const:

    # /*
    # These c-style multiline comment looking things go above and
    # below shortened versions of these constants used for the
    # microprogram generator and the test microprogram gen code.
    # I used the long form constants in the emulator to make the
    # code more self-documenting and readable. The compact
    # versions are for compactness.
    # */

    class addrEnableField:
        NO_ENABLE = 0; raEnable = 1; rbEnable = 2; rcEnable = 3
        roEnable = 4; spEnable = 5; aoEnable = 6; ALUEnable = 7
        PCEnable = 8; tmpEnable = 9; incPC = 15

    # /* The address enable field constants:
    add_NOE = addrEnableField.NO_ENABLE; add_ra = addrEnableField.raEnable
    add_rb = addrEnableField.rbEnable; add_rc = addrEnableField.rcEnable
    add_ro = addrEnableField.roEnable; add_sp = addrEnableField.spEnable
    add_ao = addrEnableField.aoEnable; add_ALU = addrEnableField.ALUEnable
    add_PC = addrEnableField.PCEnable; add_tmp = addrEnableField.tmpEnable
    add_INC = addrEnableField.incPC
    # */

    class dataEnableField:
        NO_ENABLE = 0; raEnable = 1; rbEnable = 2; rcEnable = 3
        roEnable = 4; spEnable = 5; aoEnable = 6; ALUEnable = 7
        PCEnable = 8; statWordEnable = 9;
        RAMEnableDword = 10 # I had already used this constant
                            # extensively before realizing it was
                            # improperly named. Not my proudest moment.
                            # Should have been RAMEnableWord.
        IO_WAIT = 11; tmpEnable = 12; RAMEnableSEXTByte = 13
        INVALID_STATE_HALT = 14; INVALID_INSTR_HALT = 15

    # /* The data enable field constants:
    dtaE_NOE = dataEnableField.NO_ENABLE; dtaE_ra = dataEnableField.raEnable
    dtaE_rb = dataEnableField.rbEnable; dtaE_rc = dataEnableField.rcEnable
    dtaE_ro = dataEnableField.roEnable; dtaE_sp = dataEnableField.spEnable
    dtaE_ao = dataEnableField.aoEnable; dtaE_ALU = dataEnableField.ALUEnable
    dtaE_PC = dataEnableField.PCEnable; dtaE_stw = dataEnableField.statWordEnable
    dtaE_RWE = dataEnableField.RAMEnableDword; dtaE_IO = dataEnableField.IO_WAIT
    dtaE_tmp = dataEnableField.tmpEnable; dtaE_RBE = dataEnableField.RAMEnableSEXTByte
    dtaE_ISH = dataEnableField.INVALID_STATE_HALT
    dtaE_IIH = dataEnableField.INVALID_INSTR_HALT
    # */

    class dataWriteField:
        NO_WRITE = 0; raWrite = 1; rbWrite = 2; rcWrite = 3
        roWrite = 4; spWrite = 5; aoWrite = 6; PCWrite = 7
        IOWrite = 8; tmpWrite = 9
        RAMWriteDword = 10 # DOOH!!!
        RAMWriteByte = 11; accWrite = 12; statWordWrite = 13
        irWrite = 14; HALT = 15

    # /* The data write field constants:
    dtaW_NOW = dataWriteField.NO_WRITE; dtaW_ra = dataWriteField.raWrite
    dtaW_rb = dataWriteField.rbWrite; dtaW_rc = dataWriteField.rcWrite
    dtaW_ro = dataWriteField.roWrite; dtaW_sp = dataWriteField.spWrite
    dtaW_ao = dataWriteField.aoWrite; dtaW_PC = dataWriteField.PCWrite
    dtaW_IO = dataWriteField.IOWrite; dtaW_tmp = dataWriteField.tmpWrite
    dtaW_RWW = dataWriteField.RAMWriteDword; dtaW_RWB = dataWriteField.RAMWriteByte
    dtaW_acc = dataWriteField.accWrite; dtaW_SWW = dataWriteField.statWordWrite
    dtaW_ir = dataWriteField.irWrite; dtaW_HLT = dataWriteField.HALT
    # */

    class LHSEnableField:
        NO_ENABLE = 0; raEnable = 1; rbEnable = 2; rcEnable = 3
        roEnable = 4; spEnable = 5; aoEnable = 6; RAMEnable = 7
        tmpEnable = 8; accEnable = 9; selectAcc = 10; incPC = 11
        PCEnable = 12

    # /* The LHS enable field constants:
    LHS_NOE = LHSEnableField.NO_ENABLE; LHS_ra = LHSEnableField.raEnable
    LHS_rb = LHSEnableField.rbEnable; LHS_rc = LHSEnableField.rcEnable
    LHS_ro = LHSEnableField.roEnable; LHS_sp = LHSEnableField.spEnable
    LHS_ao = LHSEnableField.aoEnable; LHS_RAM = LHSEnableField.RAMEnable
    LHS_tmp = LHSEnableField.tmpEnable; LHS_acc = LHSEnableField.accEnable
    LHS_sel = LHSEnableField.selectAcc; LHS_INC = LHSEnableField.incPC
    LHS_PC = LHSEnableField.PCEnable
    # */

    class RHSEnableField:
        NO_ENABLE = 0; raEnable = 1; rbEnable = 2; rcEnable = 3
        roEnable = 4; spEnable = 5; aoEnable = 6; RAMEnable = 7
        tmpEnable = 8; constEnable = 9; incPC = 10

    # /* The RHS enable field constants:
    RHS_NOE = RHSEnableField.NO_ENABLE; RHS_ra = RHSEnableField.raEnable
    RHS_rb = RHSEnableField.rbEnable; RHS_rc = RHSEnableField.rcEnable
    RHS_ro = RHSEnableField.roEnable; RHS_sp = RHSEnableField.spEnable
    RHS_ao = RHSEnableField.aoEnable; RHS_RAM = RHSEnableField.RAMEnable
    RHS_tmp = RHSEnableField.tmpEnable; RHS_con = RHSEnableField.constEnable
    RHS_INC = RHSEnableField.incPC
    # */

    constTable = [0x0000, 0x0001, 0xFFFF, 0x0005, 0x0004,
                  0x0003, 0x0002, 0xFFFE]

    class flagSelectField:
        sign = 0; carry = 1; overflow = 2; zero = 3
        not_signOrZero = 4; noFlg = 5; noFlgWr = 6
        # Same as noFlg but it also increments the
        # program counter by the selected constant.
        incPC = 7

    # /* The flag select field constants:
    FLG_S = flagSelectField.sign; FLG_C = flagSelectField.carry
    FLG_O = flagSelectField.overflow; FLG_Z = flagSelectField.zero
    FLG_PNZ = flagSelectField.not_signOrZero # "Positive Non-Zero"
    FLG_NOF = flagSelectField.noFlg; FLG_INC = flagSelectField.incPC
    FLG_NFW = flagSelectField.noFlgWr
    # */

    class flagPos:
        sign = 2**7
        carry = 2**6
        of = 2**5
        zero = 2**4

    class ALUFuncField:
        f_add = 0; f_sub = 1; f_and = 2; f_or = 3; f_xor = 4
        f_nor = 5; f_lsh = 6; f_rsh = 7; f_not = 8; f_addC = 9
        f_subB = 10; f_NOP = 11; incPC = 12

    # /* The ALU functions constants:
    ALF_add = ALUFuncField.f_add; ALF_sub = ALUFuncField.f_sub
    ALF_and = ALUFuncField.f_and; ALF_or = ALUFuncField.f_or
    ALF_xor = ALUFuncField.f_xor; ALF_nor = ALUFuncField.f_nor
    ALF_lsh = ALUFuncField.f_lsh; ALF_rsh = ALUFuncField.f_rsh
    ALF_not = ALUFuncField.f_not; ALF_addC = ALUFuncField.f_addC
    ALF_subB = ALUFuncField.f_subB; ALF_NOP = ALUFuncField.f_NOP
    ALF_INC = ALUFuncField.incPC
    # */

    class fieldPositionsAndMasks:
        negFlagsPos = 2**0; negFlagsMask = 0x1
        flagSelPos = 2**1; flagSelMask = 0x7
        immSelPos = 2**4; immSelMask = 0x7
        microCounterResetPos = 2**7; microCounterResetMask = 0x1
        ALUFuncSelPos = 2**8; ALUFuncSelMask = 0xF
        RHSBusCtrlPos = 2**12; RHSBusCtrlMask = 0xF
        LHSBusCtrlPos = 2**16; LHSBusCtrlMask = 0xF
        dataBusWriteCtrlPos = 2**20; dataBusWriteCtrlMask = 0xF
        dataBusCtrlPos = 2**24; dataBusCtrlMask = 0xF
        addrBusCtrlPos = 2**28; addrBusCtrlMask = 0xF

    # These lists will be used to generate microcode by taking
    # a list of field constants in order from most significant bit
    # to least significant bit, checking the bit length and
    # printing an error message if the value is too large for the
    # fields and then multiplying each field position by its
    # corresponding field position constant and bitwise-oring them
    # into an initial zero value.
    insFieldPosList = [
        fieldPositionsAndMasks.addrBusCtrlPos,
        fieldPositionsAndMasks.dataBusCtrlPos,
        fieldPositionsAndMasks.dataBusWriteCtrlPos,
        fieldPositionsAndMasks.LHSBusCtrlPos,
        fieldPositionsAndMasks.RHSBusCtrlPos,
        fieldPositionsAndMasks.ALUFuncSelPos,
        fieldPositionsAndMasks.microCounterResetPos,
        fieldPositionsAndMasks.immSelPos,
        fieldPositionsAndMasks.flagSelPos,
        fieldPositionsAndMasks.negFlagsPos,
        ]

    insFieldMaskList = [
        fieldPositionsAndMasks.addrBusCtrlMask,
        fieldPositionsAndMasks.dataBusCtrlMask,
        fieldPositionsAndMasks.dataBusWriteCtrlMask,
        fieldPositionsAndMasks.LHSBusCtrlMask,
        fieldPositionsAndMasks.RHSBusCtrlMask,
        fieldPositionsAndMasks.ALUFuncSelMask,
        fieldPositionsAndMasks.microCounterResetMask,
        fieldPositionsAndMasks.immSelMask,
        fieldPositionsAndMasks.flagSelMask,
        fieldPositionsAndMasks.negFlagsMask,
        ]

    class ctrlROMFieldPositions:
        UProgCtr = 2**0
        flagBit = 2**4
        ins = 2**5
        
        UProgCtrMask = 0xF
        flagBitMask = 0x1
        insMask = 0xFF

    # These lists will be used to generate microinstruction addresses
    # in a similar manner to how the microinstructions are generated
    # above.

    addrFieldPosList = [
        ctrlROMFieldPositions.ins,
        ctrlROMFieldPositions.flagBit,
        ctrlROMFieldPositions.UProgCtr
        ]

    addrFieldMaskList = [
        ctrlROMFieldPositions.insMask,
        ctrlROMFieldPositions.flagBitMask,
        ctrlROMFieldPositions.UProgCtrMask
        ]

    class instrSetRegIDs:
        ra = 0; rb = 1; rc = 2; ro = 3; sp = 4; ao = 5; IO = 6
        PC = 7    

    # Constants related to the instruction set. I want to write these compactly,
    # so I'm not going to namespace them in a class at all and will likely end up
    # creating constants local to where they're being used to get rid of that
    # annoying 'const.' prefix. These encode instruction type and appear in the
    # most significant 2 bits of the instruction.
    # The op-type constants:
    ins_regOp = 0; ins_ALUOp = 1; ins_brnOp = 2; ins_memOp = 3
    
    # I already have instruction set regID constants in use in the emulator. I'll
    # just reuse them.
    ins_ra = instrSetRegIDs.ra; ins_rb = instrSetRegIDs.rb; ins_rc = instrSetRegIDs.rc
    ins_ro = instrSetRegIDs.ro; ins_sp = instrSetRegIDs.sp; ins_ao = instrSetRegIDs.ao
    ins_IO = instrSetRegIDs.IO; ins_PC = instrSetRegIDs.PC
    
    # op/dest field constants used for ALU instructions:
    ins_add = 0; ins_sub = 1; ins_and = 2; ins_or = 3
    ins_nor = 4; ins_xor = 5; ins_addC = 6; ins_subB = 7
    
    # Other instruction constants for ALU ops:
    ins_not = 0x47; ins_rsh = 0x46; ins_inc = 0x47; ins_dec = 0x48
    
    # subb (sub borrow) can only operate on ra, rb and rc. This is because the
    # most significant bit is fixed at 0. When it becomes a 1, the operation
    # becomes an addition/subtraction of an immediate or a value at an
    # immediate address offset by ro. These are the constants for
    # such instructions:
    ins_add_imm = 0b01_111_100; ins_sub_imm = 0b01_111_101
    ins_add_roO = 0b01_111_110; ins_sub_roO = 0b01_111_111
    
    # Op/dest field constants used for flow control instructions:
    ins_jfl = 0; ins_jfc = 1; ins_call = 2; ins_ret = 3
    ins_cmp = 4; ins_tst = 5; ins_hlt = 6; ins_NOP = 7
    
    # A seven in the source field of a call instruction indicates an immediate address.
    # This constant is used in that case:
    ins_call_imm = 7
    
    # Flag select field constants used for flow control instructions:
    ins_S = 0; ins_C = 1; ins_O = 2; ins_Z = 3
    ins_PNZ = 4
    
    # Constants for the return instruction:
    ins_RFI = 7; ins_NRV = 6
    
    # Op/dest constants for mem-op instructions:
    # The ins_ra through ro constants are already the correct value for
    # the purpose of these instructions. We just need the four addressing modes.
    ins_imma = 4   # Immediate address
    ins_roO = 5    # ro offset of operand load/store acc.
    ins_opa = 6    # Opperand as address load/store acc.
    ins_stack = 7  # Stack push/pop
    
    # Special case instructions:
    # The surplus code space created by redundant moves of registers into
    # registers or addressing modes into addressing modes are used to encode
    # additional instructions.
    ld_ra_imm = 0b11_000_000; ld_rb_imm = 0b11_001_001 # Load immediate
    ld_rc_imm = 0b11_010_010; ld_ro_imm = 0b11_011_011
    
    # Loads of ra, rb, and rc with sign extended bytes according to one of
    # the 2 addressing modes supported for this operation:
    ldb_ra_imma = 0b11_000_001; ldb_rb_imma = 0b11_000_010
    ldb_rc_imma = 0b11_000_011
    
    # Loads of a sign extended byte from an addr = acc + ro into ra, rb and
    # rc:
    ldb_ra_roO = 0b11_001_000; ldb_rb_roO = 0b11_001_010
    ldb_rc_roO = 0b11_001_011
    
    # Stores of a byte in ra, rb or rc to an immediate address:
    stb_ra_imma = 0b11_010_000; stb_rb_imma = 0b11_010_001
    stb_rc_imma = 0b11_010_011
    
    # Stores of a byte in ra, rb, or rc to addr = acc + ro:
    stb_ra_roO = 0b11_011_000; stb_rb_roO = 0b11_011_001
    stb_rc_roO = 0b11_011_010
    
    # Loads and stores of ra, rb and rc in the addressing mode sp + ro:
    ld_ra_spPro = 0b11_100_101; ld_rb_spPro = 0b11_100_110
    ld_rc_spPro = 0b11_100_111
    
    sto_ra_spPro = 0b11_101_100; sto_rb_spPro = 0b_11_101_110
    sto_rc_spPro = 0b11_101_111
    
    # Loads and stores of ra, rb, and rc in the addressing mode ao + ro:
    ld_ra_aoPro = 0b11_110_100; ld_rb_aoPro = 0b11_110_101
    ld_rc_aoPro = 0b11_110_111
    
    sto_ra_aoPro = 0b11_111_100; sto_rb_aoPro = 0b11_111_101
    sto_rc_aoPro = 0b11_111_110
I've also implemented the microcode generator. It uses the constants in this crude microassembler and the assembleMicrocode function to generate the microcode.

The test microprogram generator:
###
### testMicroprogramGenerator.py
###

from emulator_constants import *

def assembleMicrocode (code, outputFileName):
    assembledCode = {0:0}

    for i in range(0, len(code), 2):
        address = 0; instr = 0

        for index, addrField in enumerate(code[i]):
            
            if index >= len(const.addrFieldMaskList):
                print("Error on line {i // 2 + 1}:\nMore address fields than expected.")
                input()
                exit()
            else:
                # If the value is larger than its corresponding field signal an error
                # and quit.
                if addrField & const.addrFieldMaskList[index] != addrField:
                    print(f"Error on line {i // 2 + 1}:\nAddress field {index} has been provided a value too large for its size.")
                    input()
                    exit()
                else:
                    address |= (addrField * const.addrFieldPosList[index])

        if i + 1 >= len(code):
            print(f"error on line {i // 2 + 1}:\nInstruction address field unaccompanied by corresponding instruction.")
            input()
            exit()

        for index, insField in enumerate(code[i + 1]):

            if index >= len(const.insFieldMaskList):
                print(f"Error on line {i // 2 + 1}:\nMore instruction fields than expected.")
                input()
                exit()
            else:
                # If the value is larger than its corresponding field, signal an error and quit.
                if insField & const.insFieldMaskList[index] != insField:
                    print(f"Error on line {i // 2 + 1}:\nInstruction field {index} has been provided a value too large for its size.")
                    print(f"The offending instruction is:\n{code[i + 1]}")
                    input()
                    exit()
                else:
                    instr |= (insField * const.insFieldPosList[index])

        assembledCode[address] = instr
    
    with open(outputFileName, "w") as f:

        f.write("{\n")
        for i in assembledCode.keys():
            f.write(f"{hex(i)}: {hex(assembledCode[i])},\n")
        f.write("}\n")

# I am unwrappinping the constants out of the constants namespace.
# Even the use of the shortened constants has proven unreadable prefixed with 'const.'
# at the beginning of every constant.
NOE = 0; NOW = 0

add_ra = const.add_ra; add_rb = const.add_rb; add_rc = const.add_rc;
add_ro = const.add_ro; add_sp = const.add_sp; add_ao = const.add_ao;
add_ALU = const.add_ALU; add_PC = const.add_PC; add_tmp = const.add_tmp
add_INC = const.add_INC

dtaE_ra = const.dtaE_ra; dtaE_rb = const.dtaE_rb; dtaE_rc = const.dtaE_rc
dtaE_ro = const.dtaE_ro; dtaE_sp = const.dtaE_sp; dtaE_ao = const.dtaE_ao
dtaE_ALU = const.dtaE_ALU; dtaE_PC = const.dtaE_PC; dtaE_RWE = const.dtaE_RWE
dtaE_IO = const.dtaE_IO; dtaE_tmp = const.dtaE_tmp; dtaE_RBE = const.dtaE_RBE
dtaE_ISH = const.dtaE_ISH; dtaE_IIH = const.dtaE_IIH; dtaE_stw = const.dtaE_stw

dtaW_ra = const.dtaW_ra; dtaW_rb = const.dtaW_rb; dtaW_rc = const.dtaW_rc
dtaW_ro = const.dtaW_ro; dtaW_sp = const.dtaW_sp; dtaW_ao = const.dtaW_ao
dtaW_PC = const.dtaW_PC; dtaW_IO = const.dtaW_IO; dtaW_RWW = const.dtaW_RWW
dtaW_RWB = const.dtaW_RWB; dtaW_acc = const.dtaW_acc; dtaW_SWW = const.dtaW_SWW
dtaW_ir = const.dtaW_ir; dtaW_HLT = const.dtaW_HLT; dtaW_tmp = const.dtaW_tmp

LHS_ra = const.LHS_ra; LHS_rb = const.LHS_rb; LHS_rc = const.LHS_rc
LHS_ro = const.LHS_ro; LHS_sp = const.LHS_sp; LHS_ao = const.LHS_ao
LHS_RAM = const.LHS_RAM; LHS_tmp = const.LHS_tmp; LHS_acc = const.LHS_acc;
LHS_sel = const.LHS_sel; LHS_INC = const.LHS_INC; LHS_PC = const.LHS_PC

RHS_ra = const.RHS_ra; RHS_rb = const.RHS_rb; RHS_rc = const.RHS_rc
RHS_ro = const.RHS_ro; RHS_sp = const.RHS_sp; RHS_ao = const.RHS_ao
RHS_RAM = const.RHS_RAM; RHS_tmp = const.RHS_tmp; RHS_con = const.RHS_con
RHS_INC = const.RHS_INC

FLG_S = const.FLG_S; FLG_C = const.FLG_C; FLG_O = const.FLG_O
FLG_Z = const.FLG_Z; FLG_PNZ = const.FLG_PNZ; FLG_NOF = const.FLG_NOF
FLG_INC = const.FLG_INC; FLG_NFW = const.FLG_NFW

ALF_add = const.ALF_add; ALF_sub = const.ALF_sub; ALF_and = const.ALF_and
ALF_or = const.ALF_or; ALF_xor = const.ALF_xor; ALF_nor = const.ALF_nor
ALF_lsh = const.ALF_lsh; ALF_rsh = const.ALF_rsh; ALF_not = const.ALF_not
ALF_addC = const.ALF_addC; ALF_subB = const.ALF_subB; ALF_NOP = const.ALF_NOP
ALF_INC = const.ALF_INC

# Some constants for selecting values from the constants field. Should make code
# more readable. Underscores mean the constants are negative
ZERO = 0; ONE = 1; _ONE = 2; FIVE = 3
FOUR = 4; THREE = 5; TWO = 6; _TWO = 7

# [0x00, 0x0, 0x0], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
c0de = [
# Just when I thought I had finished testing, I went and changed the emulator code
# This test is to ensure that PC increment is working correctly. I just use all
# the fields that can tell the program counter to increment to tell the program
# counter to increment by 1.
[0x00, 0x0, 0x0], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0x0],
[0x00, 0x0, 0x1], [NOE, NOE, NOW, LHS_INC, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0x0],
[0x00, 0x0, 0x2], [NOE, NOE, NOW, NOE, RHS_INC, ALF_NOP, 0x0, ONE, FLG_NOF, 0x0],
[0x00, 0x0, 0x3], [NOE, NOE, NOW, NOE, NOE, ALF_INC, 0x0, ONE, FLG_NOF, 0x0],
[0x00, 0x0, 0x4], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, FLG_INC, 0x0],
[0x00, 0x0, 0x5], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0x6], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0x7], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0x8], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0x9], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xA], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xB], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xC], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xD], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xE], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
[0x00, 0x0, 0xF], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
]

# The assembleMicrocode function will be useful for generating the actual microprogram.
# I used the if __name__ == "__main__" boilerplate to allow me to import this module and
# use its constants as well. I wouldn't want the above microcode being assembled and
# stored to the ROM file by the import.
if __name__ == "__main__": assembleMicrocode(c0de, "leverage_architecture_ROM.txt")
And finally, we have the microprogram generator proper.
It requires the constants in the test microprogram generator as well as the
assembleMicrocode function to work.
MODERATOR NOTE: See reply below for microProgramGen.py

Attached Files

.pdf   Documentation.pdf (Size: 56.96 KB / Downloads: 5)
Reply
#2
I was not able to fix the wrong display at the end of the last file. Please post that file again in its entirety.
Reply
#3
I suspect the post hit a size limit. I'm posting the micorprogram generation in its entirety in this reply.
###
### microProgramGen.py
###


# I want all my unwrapped constants and the assembleMicrocode function.
from testMicroprogramGenerator import *
# I also want my instruction constants which are declared in the main constants file.
from emulator_constants import *

# I am going to unwrap my instruction set constants from the constants namespace
# because 'const.ins_jfl' is bleh! 'ins_jfl' is good.
# The op-type constants:
ins_regOp = const.ins_regOp; ins_ALUOp = const.ins_ALUOp
ins_brnOp = const.ins_brnOp; ins_memOp = const.ins_memOp

# The register constants:
ins_ra = const.ins_ra; ins_rb = const.ins_rb; ins_rc = const.ins_rc
ins_ro = const.ins_ro; ins_sp = const.ins_sp; ins_ao = const.ins_ao
ins_IO = const.ins_IO; ins_PC = const.ins_PC

# op/dest field constants for ALU instructions:
ins_add = const.ins_add; ins_sub = const.ins_sub; ins_and = const.ins_and
ins_or = const.ins_or; ins_nor = const.ins_nor; ins_xor = const.ins_xor
ins_addC = const.ins_addC; ins_subB = const.ins_subB

# Special case instructions for ALU ops:
ins_not = const.ins_not; ins_rsh = const.ins_rsh; ins_inc = const.ins_inc
ins_dec = const.ins_dec

ins_add_imm = const.ins_add_imm; ins_sub_imm = const.ins_sub_imm
ins_add_roO = const.ins_add_roO; ins_sub_roO = const.ins_sub_roO

# Op/dest field constants used for flow control instructions:
ins_jfl = const.ins_jfl; ins_jfc = const.ins_jfc; ins_call = const.ins_call
ins_ret = const.ins_ret; ins_cmp = const.ins_cmp; ins_tst = const.ins_tst
ins_hlt = const.ins_hlt; ins_NOP = const.ins_NOP

# Flag select field constants used for flow control instructions:
ins_S = const.ins_S; ins_C = const.ins_C; ins_O = const.ins_O
ins_Z = const.ins_Z; ins_PNZ = const.ins_PNZ

# Return instruction constants:
ins_RFI = const.ins_RFI; ins_NRV = const.ins_NRV

# Used in the source field of a call instruction to call an immediate address.
ins_call_imm = const.ins_call_imm

# Op/dest constants for mem-op instructions:
ins_imma = const.ins_imma   # Immediate address
ins_roO = const.ins_roO     # ro offset of operand (load/stores acc.)
ins_opa = const.ins_opa     # Operand as address (loads/stores acc.)
ins_stack = const.ins_stack # Stack push/pop (Depending on whether it's in the source or the destination.)

# Special case mem-op instructions:
ld_ra_imm = const.ld_ra_imm; ld_rb_imm = const.ld_rb_imm
ld_rc_imm = const.ld_rc_imm; ld_ro_imm = const.ld_ro_imm

ldb_ra_imma = const.ldb_ra_imma; ldb_rb_imma = const.ldb_rb_imma
ldb_rc_imma = const.ldb_rc_imma

ldb_ra_roO = const.ldb_ra_roO; ldb_rb_roO = const.ldb_rb_roO
ldb_rc_roO = const.ldb_rc_roO

stb_ra_imma = const.stb_ra_imma; stb_rb_imma = const.stb_rb_imma
stb_rc_imma = const.stb_rc_imma

stb_ra_roO = const.stb_ra_roO; stb_rb_roO = const.stb_rb_roO
stb_rc_roO = const.stb_rc_roO

ld_ra_spPro = const.ld_ra_spPro; ld_rb_spPro = const.ld_rb_spPro
ld_rc_spPro = const.ld_ra_spPro

sto_ra_spPro = const.sto_ra_spPro; sto_rb_spPro = const.sto_rb_spPro
sto_rc_spPro = const.sto_rc_spPro

ld_ra_aoPro = const.ld_ra_aoPro; ld_rb_aoPro = const.ld_rb_aoPro
ld_rc_aoPro = const.ld_rc_aoPro

sto_ra_aoPro = const.sto_ra_aoPro; sto_rb_aoPro = const.sto_rb_aoPro
sto_rc_aoPro = const.sto_rc_aoPro


def main ():

    microProgram = []
    microProgram.extend(genRegisterTransferInstr())
    microProgram.extend(genALUInstr())
    microProgram.extend(genBranchInstr())
    microProgram.extend(genMemOps())
    
    assembleMicrocode(microProgram, "leverage_architecture_ROM.txt")


def assembleInstrField(typeField, opDest, source):
    instrField = 0
    
    if typeField & 3 != typeField:
        print(f"The type field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 2 bits.")
        input()
        exit()
    
    instrField |= typeField * 2**6
    
    if opDest & 7 != opDest:
        print(f"The op/dest field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 3 bits.")
        input()
        exit()
    
    instrField |= opDest * 2**3
    
    if source & 7 != source:
        print(f"The source field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 3 bits.")
        input()
        exit()
    
    instrField |= source * 2**0
    
    return instrField

# A constant for the fetch microinstruction:
FETCH = [add_PC, dtaE_RBE, dtaW_ir, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0]

def genRegisterTransferInstr ():
    microcode = []

    for dest in range(0, 8):
        for source in range(0, 8):
            
            currentInstr = assembleInstrField(ins_regOp, dest, source)
            
            if dest == source and source != ins_PC and source != ins_IO:
                
                # Generate the select as accumulator instructions.
                microcode.extend([
                [currentInstr, 0x0, 0x0], FETCH,
                [currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, LHS_sel, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0],
                ])
            
            elif dest == source and source == ins_PC:
                
                # Generate the unconditional jump to immediate address.
                microcode.extend([
                [currentInstr, 0x0, 0x0], FETCH,
                [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_PC, LHS_PC, RHS_con, ALF_add, 0x1, ONE, FLG_NFW, 0x0]
                ])
            
            elif dest == source and source == ins_IO:
                
                # Generate the wait interrupt instruction.
                microcode.extend([
                [currentInstr, 0x0, 0x0], FETCH,
                [currentInstr, 0x0, 0x1], [add_INC, dtaE_IO, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                ])
            
            else:
                
                # Generate the mov instructions.
                microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro, dtaW_sp, dtaW_ao, dtaW_IO, dtaW_PC][dest]
                microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, dtaE_sp, dtaE_ao, dtaE_IO, dtaE_PC][source]
                
                microcode.extend([
                [currentInstr, 0x0, 0x0], FETCH,
                [currentInstr, 0x0, 0x1], [add_INC, microSource, microDest, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                ])
    
    return microcode

def genALUInstr ():
    microcode = []
    
    for oper in range(0, 8):
        for source in range(0, 8):
            
            currentInstr = assembleInstrField(ins_ALUOp, oper, source)
            # step1: Check whether the instruction is any of the special case ALU ops
            #        or it's a regular ALU op.
            # step2: Generate the microcode accordingly.
            if oper == ins_subB:
                
                # Generate a subB instruction if the source is ra, rb, rc or ro.
                # Otherwise, perform an add or sub of immediate or value at an
                # immediate address offset by ro.
                if source < 0x4:
                    microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_subB, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else:
                    # Add or subtract an immediate or immediate address offset by ro
                    # depending on the value of the least significant 2 bits.
                    if currentInstr == ins_add_imm:
                        
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        # Get the immediate from memory.
                        [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                        # Add the immediate to the accumulator, increment PC, and reset the microcounter.
                        [currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_add, 0x1, THREE, FLG_INC, 0x0]
                        ])
                        
                    elif currentInstr == ins_sub_imm:
                        
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        # Get the immediate from memory.
                        [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                        # Subtract the immediate from the accumulator, increment PC, and reset the microcounter.
                        [currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_sub, 0x1, THREE, FLG_INC, 0x0]
                        ])
                        
                    elif currentInstr == ins_add_roO:
                        
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        # Get the immediate address from memory.
                        [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                        # Get the value at address tmp + ro into tmp.
                        [currentInstr, 0x0, 0x2], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_ro, RHS_tmp, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
                        # Add tmp to the acc, reset the microprogram counter and increment PC.
                        [currentInstr, 0x0, 0x3], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_add, 0x1, THREE, FLG_NOF, 0x0]
                        ])
                        
                    elif currentInstr == ins_sub_roO:
                        
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        # Get the immediate address from memory.
                        [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                        # Get the value at address tmp + ro into tmp.
                        [currentInstr, 0x0, 0x2], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_ro, RHS_tmp, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
                        # Add tmp to the acc, reset the microprogram counter and increment PC.
                        [currentInstr, 0x0, 0x3], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_sub, 0x1, THREE, FLG_NOF, 0x0]
                        ])
            
            elif oper == ins_add:
                
                # If the source is PC or IO, these represent unary operations, not additions.
                # These cases must be handled first.
                if currentInstr == ins_not:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, NOE, ALF_not, 0x1, ONE, FLG_NOF, 0x0],
                    ])
                
                elif currentInstr == ins_rsh:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, NOE, ALF_rsh, 0x1, ONE, FLG_NOF, 0x0],
                    ])
                
                else: # Handle cases of normal addition.
                    
                    microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_add, 0x1, ONE, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_sub:
                
                # If the source is PC or IO, these represent unary operations, not subtractions.
                # These cases must be handled first.
                if currentInstr == ins_inc:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_con, ALF_add, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                elif currentInstr == ins_dec:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_con, ALF_sub, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else: # Handle cases of normal subtraction.
                    
                    microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][source]
                    if microSource == 0xFFFF:
                        
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
                        ])
                    
                    else:
                        microcode.extend([
                        [currentInstr, 0x0, 0x0], FETCH,
                        [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_sub, 0x1, ONE, FLG_NOF, 0x0]
                        ])
            
            # None of the other instructions need any special treatment so they go down here
            # in an else clause:
            else:
                
                microOp = [ALF_add, ALF_sub, ALF_and, ALF_or, ALF_nor, ALF_xor, ALF_addC, ALF_subB][oper]
                # 0xFFFF is a sentinal value.
                microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][source]
                
                if microSource == 0xFFFF:
                    
                    # Generate microcode that will signal an invalid instruction error.
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else: # Generate microcode that corresponds to the operation specified.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, microOp, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                    
    return microcode

def genBranchInstr ():
    microcode = []
    
    for oper in range(0, 8):
        for flg_src in range(0, 8):
            
            currentInstr = assembleInstrField(ins_brnOp, oper, flg_src)
            
            if oper == ins_jfl:
                
                # 0xFFFF is a sentinal value.
                microFlg = [FLG_S, FLG_C, FLG_O, FLG_Z, FLG_PNZ, 0xFFFF, 0xFFFF, 0xFFFF][flg_src]
                # If the current instruction is an illegal opcode, generate microcode that triggers
                # an illegal instruction halt. Otherwise, generate the microcode that corresponds to
                # the appropriate instruction.
                if microFlg == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else: # Generate the appropriate branch instruction.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Multiplex the appropriate flag into the addressing input.
                    [currentInstr, 0x0, 0x1], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, microFlg, 0x0],
                    
                    # In case the flag is unset, Increment PC by 3:
                    [currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],
                    
                    # In case the flag is set, get the immediate address and stick it in PC.
                    # Cant use the ALU because it'll clobber the flags.
                    [currentInstr, 0x1, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, microFlg, 0x0],
                    [currentInstr, 0x1, 0x3], [add_PC, dtaE_RWE, dtaW_PC, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_jfc:
                
                # 0xFFFF is a sentinal value.
                microFlg = [FLG_S, FLG_C, FLG_O, FLG_Z, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF][flg_src]
                # If the current instruction is an illegal opcode, generate microcode that triggers
                # an illegal instruction halt. Otherwise, generate the microcode that corresponds to
                # the appropriate instruction.
                if microFlg == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else: # Generate the appropriate branch instruction.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Multiplex the appropriate flag into the addressing input.
                    [currentInstr, 0x0, 0x1], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, microFlg, 0x1],
                    
                    # In case the flag is unset, Increment PC by 3:
                    [currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],
                    
                    # In case the flag is set, get the immediate address and stick it in PC.
                    # Cant use the ALU because it'll clobber the flags.
                    [currentInstr, 0x1, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, microFlg, 0x1],
                    [currentInstr, 0x1, 0x3], [add_PC, dtaE_RWE, dtaW_PC, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_call:
                
                # 0xFFFF is a sentinal value.
                microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, 0xFFFF, 0xFFFF, 0xFFFF, ins_call_imm][flg_src]
                
                # If the current opcode is an illegal instruction, generate microcode that triggers an
                # illegal instruction halt. Otherwise, generate microcode that calls the appropriate register
                # or immediate address.
                if microSource == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                elif microSource == ins_call_imm:
                    # These call instructions need to be modified so that they don't
                    # clobber the flags they write to the stack.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Save the status word to tmp to avoid clobbering the flags before writing the
                    # status word to the stack.
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_stw, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Subtract 5 from ao to allocate stack memory for writing PC,
                    # SP and the status word to the stack
                    [currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, FIVE, FLG_NOF, 0x0],
                    # Write the status word value in tmp to the stack.
                    [currentInstr, 0x0, 0x3], [add_ao, dtaE_tmp, dtaW_RWB, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Write sp to ao + 1.
                    [currentInstr, 0x0, 0x4], [add_ALU, dtaE_sp, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Increment PC by 3.
                    [currentInstr, 0x0, 0x5], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, THREE, FLG_NOF, 0x0],
                    # Store the program counter at ao + 4
                    [currentInstr, 0x0, 0x6], [add_ALU, dtaE_PC, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
                    # Copy the value of ao into sp.
                    [currentInstr, 0x0, 0x7], [NOE, dtaE_ao, dtaW_sp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Load the immediate at PC-2 (because of the increment) into PC. Reset the microcounter.
                    [currentInstr, 0x0, 0x8], [add_ALU, dtaE_RWE, dtaW_PC, LHS_PC, RHS_con, ALF_sub, 0x1, TWO, FLG_NOF, 0x0]
                    ])
                
                else: # Handle calls to a register.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Save the status word to tmp to avoid clobbering the flags before writing the
                    # status word to the stack.
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_stw, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Subtract 5 from ao to allocate stack memory for writing PC,
                    # SP and the status word to the stack
                    [currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, FIVE, FLG_NOF, 0x0],
                    # Write the status word value in tmp to the stack.
                    [currentInstr, 0x0, 0x3], [add_ao, dtaE_tmp, dtaW_RWB, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Write sp to ao + 1.
                    [currentInstr, 0x0, 0x4], [add_ALU, dtaE_sp, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Increment PC by 1.
                    [currentInstr, 0x0, 0x5], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, THREE, FLG_NOF, 0x0],
                    # Store the program counter at ao + 4
                    [currentInstr, 0x0, 0x6], [add_ALU, dtaE_PC, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
                    # Copy the value of ao into sp.
                    [currentInstr, 0x0, 0x7], [NOE, dtaE_ao, dtaW_sp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Copy the appropriate register into PC and reset the microinstruction counter.
                    [currentInstr, 0x0, 0x8], [NOE, microSource, dtaW_PC, NOE, NOE, ALF_add, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_ret:
                
                # 0xFFFF is a sentinal value.
                microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, 0xFFFF, 0xFFFF, ins_NRV, 0xFFFF][flg_src]
                
                if microSource == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                elif microSource == ins_NRV:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Store sp + 5 in ao.
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_sp, RHS_con, ALF_add, 0x0, FIVE, FLG_NOF, 0x0],
                    # Pop the status word into tmp to avoid clobbering the flags with the ALU ops.
                    [currentInstr, 0x0, 0x2], [add_sp, dtaE_RBE, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Pop the program counter.
                    [currentInstr, 0x0, 0x3], [add_ALU, dtaE_RWE, dtaW_PC, LHS_sp, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
                    # Pop the stack pointer.
                    [currentInstr, 0x0, 0x4], [add_ALU, dtaE_RWE, dtaW_sp, LHS_sp, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Move the status word value from tmp to the status word register
                    # and reset the microinstruction counter.
                    [currentInstr, 0x0, 0x5], [NOE, dtaE_tmp, dtaW_SWW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
                
                else: # Handle cases of a register value being returned on the stack.
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Store sp + 5 in ao.
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_sp, RHS_con, ALF_add, 0x0, FIVE, FLG_NOF, 0x0],
                    # Pop the status word into tmp to avoid clobbering the flags with the ALU ops.
                    [currentInstr, 0x0, 0x2], [add_sp, dtaE_RBE, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
                    # Pop the program counter.
                    [currentInstr, 0x0, 0x3], [add_ALU, dtaE_RWE, dtaW_PC, LHS_sp, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
                    # Pop the stack pointer.
                    [currentInstr, 0x0, 0x4], [add_ALU, dtaE_RWE, dtaW_sp, LHS_sp, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Push the desired register onto the stack. 
                    [currentInstr, 0x0, 0x5], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, TWO, FLG_NOF, 0x0],
                    [currentInstr, 0x0, 0x6], [add_ao, microSource, dtaW_RWW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0X0],
                    # Write the tmp register into the status word.
                    [currentInstr, 0x0, 0x7], [NOE, dtaE_tmp, dtaW_SWW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0],
                    ])
            
            elif oper == ins_cmp:
                
                microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][flg_src]
                
                if microSource == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, LHS_acc, microSource, ALF_sub, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_tst:
                
                microSource = [LHS_ra, LHS_rb, LHS_rc, LHS_ro, LHS_sp, LHS_ao, 0xFFFF, 0xFFFF][flg_src]
                
                if microSource == 0xFFFF:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [NOE, NOE, NOW, microSource, RHS_con, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
                    # The program counter increment must happen in a separate microinstruction
                    # because the constants field is occupied.
                    [currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0X0]
                    ])
            
            elif oper == ins_hlt:
                
                if flg_src == 0:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, NOE, dtaW_HLT, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
                
                else:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
            
            elif oper == ins_NOP:
                
                if flg_src == 0:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
                    ])
    
    return microcode

def genMemOps ():
    microcode = []
    
    for dest in range(0, 8):
        for source in range(0, 8):
            
            currentInstr = assembleInstrField(ins_memOp, dest, source)
            
            # If the source is an addressing mode and the destination is a register,
            # the operation is a load instruction in a standard addressing mode.
            # These get handled first:
            if dest in range(ins_ra, ins_ro + 1) and source in range(ins_imma, ins_stack + 1):
                
                # Handle the loads from an immediate address.
                if source == ins_imma:
                    
                    microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro][dest]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Get the immediate address.
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Load the value from the immediate address into the destination, reset the microcounter
                    # and increment PC.
                    [currentInstr, 0x0, 0x2], [add_tmp, dtaE_RWE, microDest, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
                    ])
                
                # Handle the loads from ro offset to operand (Load into acc.)
                elif source == ins_roO:
                    
                    microDest = [LHS_ra, LHS_rb, LHS_rc, LHS_ro][dest]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_acc, microDest, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
                
                # Handle the loads from the address in the operand into the accumulator.
                elif source == ins_opa:
                    
                    microDest = [add_ra, add_rb, add_rc, add_ro][dest]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [microDest, dtaE_RWE, dtaW_acc, LHS_INC, NOE, ALF_NOP, 0X1, ONE, FLG_NOF, 0x0]
                    ])
                
                # Handle pops from the stack.
                elif source == ins_stack:
                    
                    microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro][dest]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # PC has to be incremented here because the constants
                    # field is occupied in the final microinstruction.
                    [currentInstr, 0x0, 0x1], [add_ao, dtaE_RWE, microDest, LHS_INC, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0x0],
                    [currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_add, 0x1, TWO, FLG_NOF, 0x0]
                    ])
            
            # If the soure is a register and the destination is a register, then the operation is a store
            # in a standard addressing mode. These get handled next.
            elif dest in range(ins_imma, ins_stack + 1) and source in range(ins_ra, ins_ro + 1):
                
                # Handle stores to an immediate address.
                if dest == ins_imma:
                    
                    microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Get the immediate address.
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Store the appropriate register value to that address,
                    # increment PC, and reset the micro counter.
                    [currentInstr, 0x0, 0x2], [add_tmp, microSource, dtaW_RWW, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],
                    ])
                
                # Handle stores of the accumulator to a register offset to ro.
                elif dest == ins_roO:
                    
                    microSource = [LHS_ra, LHS_rb, LHS_rc, LHS_ro][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # There is now way to directly enable the accumulator onto the data bus. That means
                    # going through the ALU so the offset address cannot be computed in paralell.
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_tmp, microSource, RHS_ro, ALF_add, 0x0, ONE, FLG_INC, 0x0],
                    [currentInstr, 0x0, 0x2], [add_tmp, dtaE_ALU, dtaW_RWW, LHS_acc, RHS_con, ALF_add, 0x1, ZERO, FLG_NOF, 0x0],
                    ])
                
                # Handle stores of the accumulator to a register address.
                elif dest == ins_opa:
                    
                    microSource = [add_ra, add_rb, add_rc, add_ro][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [microSource, dtaE_ALU, dtaW_RWW, LHS_acc, RHS_con, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
                    [currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
                    ])
                
                # Handle pushes onto the stack.
                elif dest == ins_stack:
                    
                    microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, TWO, FLG_NOF, 0X0],
                    [currentInstr, 0x0, 0x2], [add_ao, microSource, dtaW_RWW, LHS_INC, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
                    ])
            
            # Load immediate instructions:
            elif dest == source:
                
                # 0xFFFF is a sentinal value. Moves of an addressing mode into itself are
                # currently illegal instructions as they'll be used in future versions of
                # this architecture to implement port IO instructions and interrupt handling
                # instructions, along with other free code space.
                microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF][dest]
                if microDest == 0xFFFF:
                    
                    # Generate illegal instruction microcode.
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
                    ])
                
                else:
                    
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x0], [add_ALU, dtaE_RWE, microDest, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    [currentInstr, 0x0, 0x0], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
                    ])
            
            # When the source and destination are both registers and not equal, they
            # effectively represent redundant register transfer instructions. To utilize
            # the available codespace, these are used to represent load and store of sign extended
            # bytes in immediate addressing mode or ro offset by acc.
            elif dest in range(ins_ra, ins_ro + 1) and source in range(ins_ra, ins_ro + 1) and dest != source:
                
                # Handle loads of sign extended bytes in immediate addressing mode.
                if currentInstr in [ldb_ra_imma, ldb_rb_imma, ldb_rc_imma]:
                    
                    # The elif clause for load immediate instructions above already handles cases of
                    # source and destination codes being the same. 0xFFF is just a placeholder, not a
                    # sentinal value.
                    #               0       1        2        3
                    microSource = [0xFFFF, dtaW_ra, dtaW_rb, dtaW_rc][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Get the immediate address from memory.
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Load the byte from the immediate address into the appropriate register.
                    [currentInstr, 0x0, 0x2], [add_tmp, dtaE_RBE, microSource, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
                    ])
                
                # Handle stores of sign extended bytes in immediate addressing mode.
                elif currentInstr in [stb_ra_imma, stb_rb_imma, stb_rc_imma]:
                    #               0        1        2       3
                    microSource = [dtaE_ra, dtaE_rb, 0xFFFF, dtaE_rc][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    # Get the immediate address from memory.
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
                    # Copy from the byte from the appropriate
                    # register to the immediate address.
                    [currentInstr, 0x0, 0x2], [add_tmp, microSource, dtaW_RWB, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
                    ])
                
                # Handle loads of sign extended bytes in the addressing mode ro + acc.
                elif currentInstr in [ldb_ra_roO, ldb_rb_roO, ldb_rc_roO]:
                    #               0        1        2       3
                    microSource = [dtaW_ra, 0xFFFF,  dtaW_rb, dtaW_rc][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RBE, microSource, LHS_acc, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
                
                # Handle stores of bytes in the addressing mode ro + acc.
                elif currentInstr in [stb_ra_roO, stb_rb_roO, stb_rc_roO]:
                    #               0        1        2        3
                    microSource = [dtaE_ra, dtaE_rb, dtaE_rc, 0xFFFF][source]
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, microSource, dtaW_RWB, LHS_acc, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
            
            # Handle loads and stores of ra, rb and rc in the addressing modes sp + ro and ao + ro.
            elif source in range(ins_imma, ins_stack + 1) and dest in range(ins_imma, ins_stack + 1) and source != dest:
                
                # Handle loads in the addressing mode sp + ro.
                if currentInstr in range(ld_ra_spPro, ld_rc_spPro + 1):
                    
                    microSource = [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
                    0xFFFF, dtaW_ra, dtaW_rb, dtaW_rc][source]
                    #4       5        6        7
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, microSource, LHS_sp, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
                
                # Handle stores in the addressing mode sp + ro.
                elif currentInstr in range(sto_ra_spPro, sto_rc_spPro + 1):
                    
                    microSource = [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
                    dtaE_ra, 0xFFFF, dtaE_rb, dtaE_rc][source]
                    #4        5       6        7
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, microSource, dtaW_RWW, LHS_sp, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
                
                # Handle loads in the addressing mode ao + ro.
                elif currentInstr in range(ld_ra_aoPro, ld_rc_aoPro + 1):
                    
                    microSource = [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
                    dtaW_ra, dtaW_rb, 0xFFFF, dtaW_rc][source]
                    #4        5        6       7
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, microSource, LHS_ao, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
                
                # Handle stores in the addressing mode ao + ro.
                elif currentInstr in range(sto_ra_aoPro, sto_rc_aoPro + 1):
                    
                    microSource = [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
                    dtaE_ra, dtaE_rb, dtaE_rc, 0xFFFF][source]
                    #4        5        6        7
                    microcode.extend([
                    [currentInstr, 0x0, 0x0], FETCH,
                    [currentInstr, 0x0, 0x1], [add_ALU, microSource, dtaW_RWW, LHS_ao, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
                    ])
    
    return microcode

if __name__ == "__main__": main()
It may also be worth mentioning that I'm on a Linux machine. The hard coded file paths may need to be altered to make the code run on your machine. I only recently added the noFlagWrite feature to the flag control field. The microcode hasn't yet been updated accordingly. Once I've done that, all that remains is coding an assembler for this.
Reply
#4
if I may suggest some improvements - your code can benefit a lot from refactoring

get rid of these huge if/elifs where you can. For example the getAcc and setAcc methods can be written without this huge and repetitive if blocks. Not tested, but something like:

# define this in __init__ 
self.accSelections = {const.instrSetRegIDs.ra:self.ra, const.instrSetRegIDs.rb:self.rb,
                      const.instrSetRegIDs.rc:self.rc, const.instrSetRegIDs.ro:self.ro,
                      const.instrSetRegIDs.sp:self.sp, const.instrSetRegIDs.ao:self.ao, 
                      const.instrSetRegIDs.PC:self.PC}

@staticmethod
def error_msg(val):
    if val == const.instrSetRegIDs.IO:
        msg = "Kyle, your fucking microcode gen is broken again! The IO\ndevice should never be selected as the accumulator!"
    else:
        msg = "Kyle, your damn selectAcc function is broken! It shouldn't\nbe storing numbers larger than 7 in the lower 4 bits of the\nstatus word!"
    input(msg)
    exit()

def getAcc(self):
        accSelection = self.statusWord.get() & 0xF
        try:
            return self.accSelections[accSelection].get()
        except KeyError:
            error_msg(accselection)

 
def setAcc(self):
    accSelection = self.statusWord.get() & 0xF
    try:
        self.accSelections[accSelection].set(self.dataBus.get())
    except KeyError:
        error_msg(accSelection)
your code will be much much shorter if you don't do all these transformations (long names/short names) and unwrapping of constants. In fact it it clutters the code, e.g. you don't need to create class const - you don't use it and still have to "uwrap" the constants as you say. If you want to document - write docstrings and comments instead. Also you may want to look at enum module

don't use multiple statements separated by semi-colon. it's not pythonic and makes the code harder to read.

avoid star imports

follow consistent naming convention. PEP-8 is recommended, but if you prefer not to follow it, at least be consistent

you have dedicated module for constants, yet you define constant FETCH in microProgramGen.py. You define FETCH and use it in the code afterwords and the simultaneously you use similar lists directly:

# A constant for the fetch microinstruction:
FETCH = [add_PC, dtaE_RBE, dtaW_ir, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0]
 # skip some code here
                microcode.extend([
                [currentInstr, 0x0, 0x0], FETCH,
                [currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, LHS_sel, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0],

and one bug
on line 16 in testMicroprogramGenerator.py you want to print f-string:
                print(f"Error on line {i // 2 + 1}:\nMore address fields than expected.")
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#5
I didn't notice FixedWidthInt has get() and set() methods. Don't write getter and setters.
actually nothing would prevent to assign value property directly, e.g.

self.ra = 0, instead of self.ra.set(0)

here is updated vesrion of my example
class FixedWidthInt:
    def __init__ (self, bitwidth, init = 0):
        self.bitwidth = bitwidth
        self.bitmask = (2 ** self.bitwidth) - 1
        self.value = init

    @value.property
    def value(self):
        return self._value # here the underscore means internal variable. still not private/protected, just meaning "for internal use"
 
    @value.setter
    def value(self, newValue):
        self._value = self.bitmask & newValue # here the underscore means internal variable. still not private/protected, just meaning "for internal use"
# some code skipped here
# define this in __init__ 
self.accSelections = {const.instrSetRegIDs.ra: self.ra, const.instrSetRegIDs.rb: self.rb,
                      const.instrSetRegIDs.rc: self.rc, const.instrSetRegIDs.ro: self.ro,
                      const.instrSetRegIDs.sp: self.sp, const.instrSetRegIDs.ao: self.ao, 
                      const.instrSetRegIDs.PC: self.PC}

def error_msg(val):
    if val == const.instrSetRegIDs.IO:
        msg = "Kyle, your fucking microcode gen is broken again! The IO\ndevice should never be selected as the accumulator!"
    else:
        msg = "Kyle, your damn selectAcc function is broken! It shouldn't\nbe storing numbers larger than 7 in the lower 4 bits of the\nstatus word!"
    input(msg)
    exit()

def getAcc(self):
        accSelection = self.statusWord.value & 0xF
        try:
            return self.accSelections[accSelection].value
        except KeyError:
            error_msg(accselection)

 
def setAcc(self):
    accSelection = self.statusWord.value & 0xF
    try:
        self.accSelections[accSelection].value = self.dataBus.value
    except KeyError:
        error_msg(accSelection)
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#6
I appreciate the advice. I was so excited to share what I had, I didn't notice the errors in my micro program generation. I've caught 2 address assignment errors in that code so far. I wrote a hand-assembled hello world thinking after writing just the flimsiest excuse for a test program and I still haven't worked out all the bugs in the microcode. I do agree now that the emulator is quite inelegant. I'm going to apply your refactoring suggestions when I have more free time. I'm working retail.

Here's the program load logic I've implemented:
def main ():
    
    test = CPU()
    
    # I've decided to implement program loading from text files containing
    # string representations of dictionaries with nested lists. The keys will
    # be the starting addresses for loading particular code segment. The
    # lists will be the sequence of bytes to be loaded into memory starting at
    # that address. 
    
    programPath = input("Please type the path to the program you want to run: ")
    prg = {}
    with open(programPath, 'r') as f:
        prg = eval(f.read())
    
    for i in prg.keys():
        
        for index, byte in enumerate(prg[i]):
            
            # Trap any overlaping code segment errors, signal an error, and exit.
            if test.RAM[i + index].get() != 0:
                input(f"The code segment starting at address {i} overlaps another code segment!")
                exit()
            
            test.RAM[i + index].set(byte)
    
    test.clockLoop()
This is the hand assembled hello world I wrote. Hopefully I can get this to run.
###
### testProgram.txt
###

{
0x0000: [
# sel    Ara               ; Ara will hold the characters from the string
0b00_000_000,
# ldi    ro,    0x0100     ; ro will be the pointer to the address of the string.
0b11_011_011, 0x01, 0x00,
# ldi    rb,    0x0000     ; rb will be an offset to the pointer.
0b11_001_001, 0x00, 0x00,
# ldi    rc,    0x0002     ; rc will hold an increment constant for rb.
0b11_010_010, 0x00, 0x02,

# ld     Ara,   [ro + rb]  ::loop1::   ; loop1 = 0x0009
0b11_000_101,
# out    Ara
0b00_110_000,
# sel    Arb
0b00_001_001,
# add    Arb,    rc
0b01_000_010,
# push   rc                ; stack = [rc]
0b11_111_010,
# ldi    rc,    0x00FF
0b11_011_010, 0x00, 0xFF,
# sel    Arc               ; Use rc for a comparison of the lower byte of ra to 0x09 (a tab space)
0b00_010_010,
# and    Arc,   ra
0b01_010_000,
# sub    Arc,   0x0009     ; Set the flags according to the subtraction.
0b01_111_101, 0x00, 0x09,
# jfl    Z      end
0b10_000_011, 0x00, 0x1F,
# pop    Arc               ; stack = []
0b11_010_111,
# sel    Ara
0b00_000_000,
# jmp    loop1
0b00_111_111, 0x00, 0x09,

# pop    rc                ::end::     ; end = 0x001F
0b11_010_111,
# hlt
0b10_110_000
],

# The hello world string in hexadecimal
0x0100: [
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x09, 0x00, 0x00
]
}
I've also had to edit the microcode and the emulator. I hit the size limit again so I'll put them in separate posts.
Reply
#7
I tracked down the last of my bugs to the RHS bus enabling the accumulator onto it when it's told to enable tmp. I've found no further issues with the microcode, but I still have other tests to perform. In any case, this test program will run on the emulator code provided and I'll have refactored code up on the forum on the weekend. There were also several incorrect op-codes in the original program. I've corrected those.
###
### testProgram.txt
###

{
0x0000: [
# sel    Ara               ; Ara will hold the characters from the string
0b00_000_000,
# ldi    ro,    msg        ; ro will be the pointer to the address of the string.
0b11_011_011, 0x01, 0x00,
# ldi    rb,    0x0000     ; rb will be an offset to the pointer.
0b11_001_001, 0x00, 0x00,
# ldi    rc,    0x0002     ; rc will hold an increment constant for rb.
0b11_010_010, 0x00, 0x02,

# ld     Ara,   [ro + rb]  ::loop1::   ; loop1 = 0x0009
0b11_001_101,
# out    Ara
0b00_110_000,
# sel    Arb
0b00_001_001,
# add    Arb,    rc
0b01_000_010,
# push   rc                ; stack = [rc]
0b11_111_010,
# ldi    rc,    0x00FF
0b11_010_010, 0x00, 0xFF,
# sel    Arc               ; Use rc for a comparison of the lower byte of ra to 0x09 (a tab space)
0b00_010_010,
# and    Arc,   ra
0b01_010_000,
# sub    Arc,   0x0009     ; Set the flags according to the subtraction.
0b01_111_101, 0x00, 0x09,
# jfl    Z      end
0b10_000_011, 0x00, 0x1F,
# pop    Arc               ; stack = []
0b11_010_111,
# sel    Ara
0b00_000_000,
# jmp    loop1
0b00_111_111, 0x00, 0x09,

# pop    rc                ::end::     ; end = 0x001F
0b11_010_111,
# hlt
0b10_110_000
],

# .org 0x0100
# db     "Hello World! I am the leverage architecture and this is my first program!\n\n\t" ::msg:: ; msg = 0x0100
0x0100: [
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x20, 0x49, 0x20, 0x61, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x79, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x21, 0xa, 0xa, 0x9
]
}
Hand assembling is such a tedious and error prone process. Here's the emulator that ran that beast:
###
### emulator.py
###

from emulator_constants import *


# Usually a great convenience, Pythons expanding integer type
# is a hinderance when writing an emulator.
# 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF is a valid Python integer
# even though it would overflow any common integer size in other languages
# because Python just keeps adding bytes. Using Python for purposes for
# which it was not intended FTW!!!
class FixedWidthInt:

    def __init__ (self, bitwidth, init = 0):
        self.bitwidth = bitwidth
        self.bitmask = (2 ** self.bitwidth) - 1
        self.value = init & self.bitmask

    def set (self, newValue):
        self.value = self.bitmask & newValue

    def get (self):
        return self.value

class CPU:

    def __init__ (self):

        # Set up the register stack and all the CPU internal data.
        self.ra = FixedWidthInt(16)
        self.rb = FixedWidthInt(16)
        self.rc = FixedWidthInt(16)
        self.ro = FixedWidthInt(16)
        self.sp = FixedWidthInt(16, 0xFFFF)
        self.ao = FixedWidthInt(16, 0xFFFF)
        self.PC = FixedWidthInt(16)
        self.tmp = FixedWidthInt(16)
        self.statusWord = FixedWidthInt(8)

        self.RAM = [FixedWidthInt(8) for i in range(0, 2**16)]
        
        self.PC = FixedWidthInt(16)
        self.tmp = FixedWidthInt(16)

        # The CPU buses.
        self.addrBus = FixedWidthInt(16)
        self.dataBus = FixedWidthInt(16)
        self.RHSBus = FixedWidthInt(16)
        self.LHSBus = FixedWidthInt(16)

        # Set up of all the memory components of the control logic
        # including the control ROM.
        self.ins = FixedWidthInt(8)
        self.flagIn = FixedWidthInt(1)
        self.UProgCtr = FixedWidthInt(4, 0xF)
        self.ctrlWord = FixedWidthInt(32)
        self.loadMicroprogram()

    def loadMicroprogram (self):
        ROMFile = "./leverage_architecture_ROM.txt"
        with open(ROMFile, "r") as f:
            self.controlROM = eval(f.read())

    def readRAM (self):
        significantByte = self.RAM[self.addrBus.get()].get()
        insignificantByte = self.RAM[(self.addrBus.get() + 1) & 0xFFFF].get()
        return (significantByte * 2**8) | insignificantByte

    def writeRAM (self):
        significantByte = (self.dataBus.get() // 2 ** 8) & 0xFF
        insignificantByte = self.dataBus.get() & 0xFF
        self.RAM[self.addrBus.get()].set(significantByte)
        self.RAM[(self.addrBus.get() + 1) & 0xFFFF].set(insignificantByte)

    def getAcc(self):
        accSelection = self.statusWord.get() & 0xF
        if accSelection == const.instrSetRegIDs.ra: return self.ra.get()
        elif accSelection == const.instrSetRegIDs.rb: return self.rb.get()
        elif accSelection == const.instrSetRegIDs.rc: return self.rc.get()
        elif accSelection == const.instrSetRegIDs.ro: return self.ro.get()
        elif accSelection == const.instrSetRegIDs.sp: return self.sp.get()
        elif accSelection == const.instrSetRegIDs.ao: return self.ao.get()
        elif accSelection == const.instrSetRegIDs.IO:
            print("Kyle, your fucking microcode gen is broken again! The IO")
            print("device should never be selected as the accumulator!")
            input()
            exit()
        elif accSelection == const.instrSetRegIDs.PC: return self.PC.get()
        else:
            print("Kyle, your damn selectAcc function is broken! It shouldn't")
            print("be storing numbers larger than 7 in the lower 4 bits of the")
            print("status word!")
            input()
            exit()

    def setAcc(self):
        accSelection = self.statusWord.get() & 0xF
        if accSelection == const.instrSetRegIDs.ra: self.ra.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.rb: self.rb.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.rc: self.rc.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.ro: self.ro.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.sp: self.sp.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.ao: self.ao.set(self.dataBus.get())
        elif accSelection == const.instrSetRegIDs.IO:
            print("Kyle, your fucking microcode gen is broken again! The IO")
            print("device should never be selected as the accumulator!")
            input()
            exit()
        elif accSelection == const.instrSetRegIDs.PC: self.PC.set(self.dataBus.get())
        else:
            print("Kyle, your damn selectAcc function is broken! It shouldn't")
            print("be storing numbers larger than 7 in the lower 4 bits of the")
            print("status word!")
            input()
            exit()

    def selectAcc (self):
        self.statusWord.set((self.statusWord.get() & 0xF0) | (self.ins.get() & 0x7))

    def computeFlags (self, ALUOutput):
        self.statusWord.set(self.statusWord.get() & 0xF) # Get rid of the old flag values.

        # Compute the sign flag.
        if ALUOutput & 2**15 != 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.sign)

        # Compute the carry flag.
        if ALUOutput & 0x1_0000 != 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.carry)

        # Compute the overflow flag.
        if (ALUOutput & 2**15 != 0 and self.LHSBus.get() & 2**15 == 0 and self.RHSBus.get() & 2**15 == 0) or (ALUOutput & 2**15 == 0 and self.LHSBus.get() & 2**15 != 0 and self.RHSBus.get() & 2**15 != 0):
            self.statusWord.set(self.statusWord.get() | const.flagPos.of)

        # Compute the zero flag.
        if ALUOutput & 0xFFFF == 0:
            self.statusWord.set(self.statusWord.get() | const.flagPos.zero)

    def getInput (self):
        userInput = input("\n::>")
        if len(userInput) == 0: return 0
        if len(userInput) == 1: return ord(userInput) * 2**8
        significantByte = ord(userInput[-2])
        insignificantByte = ord(userInput[-1])
        return (significantByte * 2**8) | insignificantByte

    def output (self):
        firstChr = chr((self.dataBus.get() // 2**8) & 0xFF)
        secondChr = chr(self.dataBus.get() & 0xFF)
        print(firstChr + secondChr, end = "")

    def readSEXTByte (self):
        byte = self.RAM[self.addrBus.get()].get()
        if byte & 2**7 != 0:
            return (0xFF * 2**8) | byte
        else:
            return byte

    def writeSingleByte (self):
        byte = self.dataBus.get() & 0xFF
        self.RAM[self.addrBus.get()].set(byte)

    def clockLoop (self):

        while True:

            # Falling Edge

            # first compute the address of the next
            # control word to be read from the control ROM.
            flagBit = 0
            ctrlWord = self.ctrlWord.get()
            currentFlagSelection = (ctrlWord // const.fieldPositionsAndMasks.flagSelPos) & const.fieldPositionsAndMasks.flagSelMask
            # Get the flag bit that goes into the addressing input.
            currentStatWord = self.statusWord.get()
            if (ctrlWord // const.fieldPositionsAndMasks.negFlagsPos) & const.fieldPositionsAndMasks.negFlagsMask == 1:
                currentStatWord = ~currentStatWord

            if currentFlagSelection == const.flagSelectField.sign:
                flagBit = (currentStatWord // const.flagPos.sign) & 1
            elif currentFlagSelection == const.flagSelectField.carry:
                flagBit = (currentStatWord // const.flagPos.carry) & 1
            elif currentFlagSelection == const.flagSelectField.overflow:
                flagBit = (currentStatWord // const.flagPos.of) & 1
            elif currentFlagSelection == const.flagSelectField.zero:
                flagBit = (currentStatWord // const.flagPos.zero) & 1
            elif currentFlagSelection == const.flagSelectField.not_signOrZero:
                if (currentStatWord // const.flagPos.sign) & 1 == 0 and (currentStatWord // const.flagPos.zero) & 1 == 0:
                    flagBit = 1
                else:
                    flagBit = 0
            elif currentFlagSelection == const.flagSelectField.noFlg:
                flagBit = 0
            elif currentFlagSelection == const.flagSelectField.incPC:
                flagBit = 0
            elif currentFlagSelection == const.flagSelectField.noFlgWr:
                flagBit = 0
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            # Reset the microinstruction counter if the UProgCtr reset bit is set.
            # Otherwise, increment.
            if (ctrlWord // const.fieldPositionsAndMasks.microCounterResetPos) & const.fieldPositionsAndMasks.microCounterResetMask == 1:
                self.UProgCtr.set(0)
            else:
                self.UProgCtr.set(self.UProgCtr.get() + 1)

            # Compute the address input to the control ROM to get the next control word.
            ctrlRomAddr = 0 | (self.UProgCtr.get() * const.ctrlROMFieldPositions.UProgCtr)
            ctrlRomAddr |= flagBit * const.ctrlROMFieldPositions.flagBit
            ctrlRomAddr |= self.ins.get() * const.ctrlROMFieldPositions.ins
            self.ctrlWord.set(self.controlROM[ctrlRomAddr]); ctrlWord = self.controlROM[ctrlRomAddr]

            # First check whether any of the bus control fields have halt codes in them
            # then simulate the propagation of data onto busses. 
            addrCtrlField = (ctrlWord // const.fieldPositionsAndMasks.addrBusCtrlPos) & const.fieldPositionsAndMasks.addrBusCtrlMask
            dataEnCtrlField = (ctrlWord // const.fieldPositionsAndMasks.dataBusCtrlPos) & const.fieldPositionsAndMasks.dataBusCtrlMask
            dataWrCtrlField = (ctrlWord // const.fieldPositionsAndMasks.dataBusWriteCtrlPos) & const.fieldPositionsAndMasks.dataBusWriteCtrlMask
            LHSCtrlField = (ctrlWord // const.fieldPositionsAndMasks.LHSBusCtrlPos) & const.fieldPositionsAndMasks.LHSBusCtrlMask
            RHSCtrlField = (ctrlWord // const.fieldPositionsAndMasks.RHSBusCtrlPos) & const.fieldPositionsAndMasks.RHSBusCtrlMask
            ALUFuncSelField = (ctrlWord // const.fieldPositionsAndMasks.ALUFuncSelPos) & const.fieldPositionsAndMasks.ALUFuncSelMask
            constSelField = (ctrlWord // const.fieldPositionsAndMasks.immSelPos) & const.fieldPositionsAndMasks.immSelMask
            flagSelField = (ctrlWord // const.fieldPositionsAndMasks.flagSelPos) & const.fieldPositionsAndMasks.flagSelMask

            if dataEnCtrlField == const.dataEnableField.INVALID_STATE_HALT:
                print("The CPU has reached an invalid state.")
                input()
                exit()
            elif dataEnCtrlField == const.dataEnableField.INVALID_INSTR_HALT:
                print(f"The instruction {hex(self.ins.get())} is invalid.")
                input()
                exit()

            if dataWrCtrlField == const.dataWriteField.HALT:
                print("The CPU has halted. The program produced no errors.")
                input()
                exit()

            # Compute the value of the selected constant.
            selectedConst = const.constTable[constSelField]
            
            # Compute the value of the address bus in cases where it does not come from the ALU.
            if addrCtrlField == const.addrEnableField.raEnable: self.addrBus.set(self.ra.get())
            elif addrCtrlField == const.addrEnableField.rbEnable: self.addrBus.set(self.rb.get())
            elif addrCtrlField == const.addrEnableField.rcEnable: self.addrBus.set(self.rc.get())
            elif addrCtrlField == const.addrEnableField.roEnable: self.addrBus.set(self.ro.get())
            elif addrCtrlField == const.addrEnableField.spEnable: self.addrBus.set(self.sp.get())
            elif addrCtrlField == const.addrEnableField.aoEnable: self.addrBus.set(self.ao.get())
            elif addrCtrlField == const.addrEnableField.PCEnable: self.addrBus.set(self.PC.get())
            elif addrCtrlField == const.addrEnableField.tmpEnable: self.addrBus.set(self.tmp.get())
            elif addrCtrlField == const.addrEnableField.incPC: pass
            elif addrCtrlField == const.addrEnableField.NO_ENABLE:
                pass
            elif addrCtrlField == const.addrEnableField.ALUEnable:
                pass
            else:
                print("Kyle, fix your damn microcode generator!!!")
                input()
                exit()
            
            # Since the ALU output can be enabled onto the address bus, the ALU output must
            # be computed first. This means RHS and LHS are computed before addr. The
            # address bus can influence data, meaning data must be computed last.
            if LHSCtrlField == const.LHSEnableField.NO_ENABLE: pass
            elif LHSCtrlField == const.LHSEnableField.raEnable: self.LHSBus.set(self.ra.get())
            elif LHSCtrlField == const.LHSEnableField.rbEnable: self.LHSBus.set(self.rb.get())
            elif LHSCtrlField == const.LHSEnableField.rcEnable: self.LHSBus.set(self.rc.get())
            elif LHSCtrlField == const.LHSEnableField.roEnable: self.LHSBus.set(self.ro.get())
            elif LHSCtrlField == const.LHSEnableField.spEnable: self.LHSBus.set(self.sp.get())
            elif LHSCtrlField == const.LHSEnableField.aoEnable: self.LHSBus.set(self.ao.get())
            elif LHSCtrlField == const.LHSEnableField.RAMEnable: self.LHSBus.set(self.readRAM())
            elif LHSCtrlField == const.LHSEnableField.accEnable: self.LHSBus.set(self.getAcc())
            elif LHSCtrlField == const.LHSEnableField.selectAcc: self.selectAcc()
            elif LHSCtrlField == const.LHSEnableField.PCEnable: self.LHSBus.set(self.PC.get())
            elif LHSCtrlField == const.LHSEnableField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            if RHSCtrlField == const.RHSEnableField.NO_ENABLE: pass
            elif RHSCtrlField == const.RHSEnableField.raEnable: self.RHSBus.set(self.ra.get())
            elif RHSCtrlField == const.RHSEnableField.rbEnable: self.RHSBus.set(self.rb.get())
            elif RHSCtrlField == const.RHSEnableField.rcEnable: self.RHSBus.set(self.rc.get())
            elif RHSCtrlField == const.RHSEnableField.roEnable: self.RHSBus.set(self.ro.get())
            elif RHSCtrlField == const.RHSEnableField.spEnable: self.RHSBus.set(self.sp.get())
            elif RHSCtrlField == const.RHSEnableField.aoEnable: self.RHSBus.set(self.ao.get())
            elif RHSCtrlField == const.RHSEnableField.RAMEnable: self.RHSBus.set(self.readRam())
            elif RHSCtrlField == const.RHSEnableField.tmpEnable: self.RHSBus.set(self.tmp.get())
            elif RHSCtrlField == const.RHSEnableField.constEnable: self.RHSBus.set(selectedConst)
            elif RHSCtrlField == const.RHSEnableField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()

            # Compute the ALU output
            ALUOutput = 0
            if ALUFuncSelField == const.ALUFuncField.f_add: ALUOutput = self.LHSBus.get() + self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_sub: ALUOutput = self.LHSBus.get() - self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_and: ALUOutput = self.LHSBus.get() & self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_or: ALUOutput = self.LHSBus.get() | self.RHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_xor: ALUOutput = (~self.LHSBus.get() & self.RHSBus.get()) | (self.LHSBus.get() & ~self.RHSBus.get())
            elif ALUFuncSelField == const.ALUFuncField.f_nor: ALUOutput = ~(self.LHSBus.get() | self.RHSBus.get())
            elif ALUFuncSelField == const.ALUFuncField.f_lsh: ALUOutput = self.LHSBus.get() * 2
            elif ALUFuncSelField == const.ALUFuncField.f_rsh: ALUOutput = self.LHSBus.get() // 2
            elif ALUFuncSelField == const.ALUFuncField.f_not: ALUOutput = ~self.LHSBus.get()
            elif ALUFuncSelField == const.ALUFuncField.f_addC: ALUOutput = self.LHSBus.get() + self.RHSBus.get() + ((self.statusWord.get() // const.flagPos.carry) & 1)
            elif ALUFuncSelField == const.ALUFuncField.f_subB: ALUOutput = self.LHSBus.get() - self.RHSBus.get() - ((self.statusWord.get() // const.flagPos.carry) & 1)
            elif ALUFuncSelField == const.ALUFuncField.f_NOP:
                pass
            elif ALUFuncSelField == const.ALUFuncField.incPC: pass
            else:
                print("Kyle, fix your damn microcode generator!")
                input()
                exit()
            
            # Compute the value of the address bus in case of the value coming from the ALU.
            if addrCtrlField == const.addrEnableField.ALUEnable: self.addrBus.set(ALUOutput)

            # Compute the value of the data bus.
            if dataEnCtrlField == const.dataEnableField.NO_ENABLE: pass
            elif dataEnCtrlField == const.dataEnableField.raEnable: self.dataBus.set(self.ra.get())
            elif dataEnCtrlField == const.dataEnableField.rbEnable: self.dataBus.set(self.rb.get())
            elif dataEnCtrlField == const.dataEnableField.rcEnable: self.dataBus.set(self.rc.get())
            elif dataEnCtrlField == const.dataEnableField.roEnable: self.dataBus.set(self.ro.get())
            elif dataEnCtrlField == const.dataEnableField.spEnable: self.dataBus.set(self.sp.get())
            elif dataEnCtrlField == const.dataEnableField.aoEnable: self.dataBus.set(self.ao.get())
            elif dataEnCtrlField == const.dataEnableField.ALUEnable: self.dataBus.set(ALUOutput)
            elif dataEnCtrlField == const.dataEnableField.PCEnable: self.dataBus.set(self.PC.get())
            elif dataEnCtrlField == const.dataEnableField.statWordEnable: self.dataBus.set(self.statusWord.get())
            elif dataEnCtrlField == const.dataEnableField.RAMEnableDword: self.dataBus.set(self.readRAM())
            elif dataEnCtrlField == const.dataEnableField.IO_WAIT: self.dataBus.set(self.getInput())
            elif dataEnCtrlField == const.dataEnableField.tmpEnable: self.dataBus.set(self.tmp.get())
            elif dataEnCtrlField == const.dataEnableField.RAMEnableSEXTByte: self.dataBus.set(self.readSEXTByte())

            # Rising Edge
            # Simulate the latching of data into registers and memory.
            if dataWrCtrlField == const.dataWriteField.NO_WRITE: pass
            elif dataWrCtrlField == const.dataWriteField.raWrite: self.ra.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.rbWrite: self.rb.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.rcWrite: self.rc.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.roWrite: self.ro.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.spWrite: self.sp.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.aoWrite: self.ao.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.PCWrite: self.PC.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.IOWrite: self.output()
            elif dataWrCtrlField == const.dataWriteField.tmpWrite: self.tmp.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.RAMWriteDword: self.writeRAM()
            elif dataWrCtrlField == const.dataWriteField.RAMWriteByte: self.writeSingleByte()
            elif dataWrCtrlField == const.dataWriteField.accWrite: self.setAcc()
            elif dataWrCtrlField == const.dataWriteField.statWordWrite: self.statusWord.set(self.dataBus.get())
            elif dataWrCtrlField == const.dataWriteField.irWrite: self.ins.set(self.dataBus.get())
            
            # If any control field is saying to increment PC, do it!
            if addrCtrlField == const.addrEnableField.incPC or LHSCtrlField == const.LHSEnableField.incPC or RHSCtrlField == const.RHSEnableField.incPC or flagSelField == const.flagSelectField.incPC or ALUFuncSelField == const.ALUFuncField.incPC:
                self.PC.set(self.PC.get() + selectedConst)
            
            # If The ALU isn't doing a NOP or incrementing PC, and the flag control field doesn't say not to write the flags, compute the flag values.
            if ALUFuncSelField != const.ALUFuncField.f_NOP and ALUFuncSelField != const.ALUFuncField.incPC and flagSelField != const.flagSelectField.noFlgWr:
                self.computeFlags(ALUOutput)

            testing = False
            testingMicrocode = False
            if testing and self.UProgCtr.get() == 0 or testingMicrocode:
                print(f"ra: hex: {hex(self.ra.get())}, binary: {bin(self.ra.get())}, ascii: {chr(self.ra.get() // 2**8 & 0xFF)}{chr(self.ra.get() & 0xFF)}")
                print(f"rb: hex: {hex(self.rb.get())}, binary: {bin(self.rb.get())}, ascii: {chr(self.rb.get() // 2**8 & 0xFF)}{chr(self.rb.get() & 0xFF)}")
                print(f"rc: hex: {hex(self.rc.get())}, binary: {bin(self.rc.get())}, ascii: {chr(self.rc.get() // 2**8 & 0xFF)}{chr(self.rc.get() & 0xFF)}")
                print(f"ro: hex: {hex(self.ro.get())}, binary: {bin(self.ro.get())}, ascii: {chr(self.ro.get() // 2**8 & 0xFF)}{chr(self.ro.get() & 0xFF)}")
                print(f"sp: hex: {hex(self.sp.get())}, binary: {bin(self.sp.get())}, ascii: {chr(self.sp.get() // 2**8 & 0xFF)}{chr(self.sp.get() & 0xFF)}")
                print(f"ao: hex: {hex(self.ao.get())}, binary: {bin(self.ao.get())}, ascii: {chr(self.ao.get() // 2**8 & 0xFF)}{chr(self.ao.get() & 0xFF)}")
                print(f"PC: hex: {hex(self.PC.get())}, binary: {bin(self.PC.get())}, ascii: {chr(self.PC.get() // 2**8 & 0xFF)}{chr(self.PC.get() & 0xFF)}")
                print(f"tmp: hex: {hex(self.tmp.get())}, binary: {bin(self.tmp.get())}, ascii: {chr(self.tmp.get() // 2**8 & 0xFF)}{chr(self.tmp.get() & 0xFF)}")
                print(f"ins: hex: {hex(self.ins.get())},    Status Word: binary: {bin(self.statusWord.get())}")

                print("\n\nThe lower 32 bytes of memory:")
                byteCount = 0
                while byteCount <= 32:
                    print(f"Addresses {byteCount} - {byteCount + 7}: ", end = "")
                    for i in range(0, 8):
                        print(f" {hex(self.RAM[byteCount].get())}", end = "")
                        byteCount += 1
                    print("")

                command = input("Press ENTER to continue or type a command: \n")
                if command == "tstMcode": testingMicrocode = True

def main ():
    
    test = CPU()
    
    # I've decided to implement program loading from text files containing
    # string representations of dictionaries with nested lists. The keys will
    # be the starting addresses for loading particular code segment. The
    # lists will be the sequence of bytes to be loaded into memory starting at
    # that address. 
    
    programPath = input("Please type the path to the program you want to run: ")
    prg = {}
    with open(programPath, 'r') as f:
        prg = eval(f.read())
    
    for i in prg.keys():
        
        for index, byte in enumerate(prg[i]):
            
            # Trap any overlaping code segment errors, signal an error, and exit.
            if test.RAM[i + index].get() != 0:
                print(f"The code segment starting at address {i} overlaps another code segment!")
                input()
                exit()
            
            test.RAM[i + index].set(byte)
    
    test.clockLoop()

if __name__ == "__main__": main()
I've also had to correct the microcode generator. The lists to the left of each microinstruction are the addresses of the microinstructions and they are broken up into their instruction fields, the single-bit flag input, and the 4-bit microprogram counter. If the address is the same for multiple instructions, the same key in the dictionary that represents the control ROM gets reassigned repeatedly rather than the separate instructions being written to consecutive addresses. I've found this and another bizarre address assignment error in the conditional branch instruction microcode.
[python]
###
### microProgramGen.py
###


# I want all my unwrapped constants and the assembleMicrocode function.
from testMicroprogramGenerator import *
# I also want my instruction constants which are declared in the main constants file.
from emulator_constants import *

# I am going to unwrap my instruction set constants from the constants namespace
# because 'const.ins_jfl' is bleh! 'ins_jfl' is good.
# The op-type constants:
ins_regOp = const.ins_regOp; ins_ALUOp = const.ins_ALUOp
ins_brnOp = const.ins_brnOp; ins_memOp = const.ins_memOp

# The register constants:
ins_ra = const.ins_ra; ins_rb = const.ins_rb; ins_rc = const.ins_rc
ins_ro = const.ins_ro; ins_sp = const.ins_sp; ins_ao = const.ins_ao
ins_IO = const.ins_IO; ins_PC = const.ins_PC

# op/dest field constants for ALU instructions:
ins_add = const.ins_add; ins_sub = const.ins_sub; ins_and = const.ins_and
ins_or = const.ins_or; ins_nor = const.ins_nor; ins_xor = const.ins_xor
ins_addC = const.ins_addC; ins_subB = const.ins_subB

# Special case instructions for ALU ops:
ins_not = const.ins_not; ins_rsh = const.ins_rsh; ins_inc = const.ins_inc
ins_dec = const.ins_dec

ins_add_imm = const.ins_add_imm; ins_sub_imm = const.ins_sub_imm
ins_add_roO = const.ins_add_roO; ins_sub_roO = const.ins_sub_roO

# Op/dest field constants used for flow control instructions:
ins_jfl = const.ins_jfl; ins_jfc = const.ins_jfc; ins_call = const.ins_call
ins_ret = const.ins_ret; ins_cmp = const.ins_cmp; ins_tst = const.ins_tst
ins_hlt = const.ins_hlt; ins_NOP = const.ins_NOP

# Flag select field constants used for flow control instructions:
ins_S = const.ins_S; ins_C = const.ins_C; ins_O = const.ins_O
ins_Z = const.ins_Z; ins_PNZ = const.ins_PNZ

# Return instruction constants:
ins_RFI = const.ins_RFI; ins_NRV = const.ins_NRV

# Used in the source field of a call instruction to call an immediate address.
ins_call_imm = const.ins_call_imm

# Op/dest constants for mem-op instructions:
ins_imma = const.ins_imma # Immediate address
ins_roO = const.ins_roO # ro offset of operand (load/stores acc.)
ins_opa = const.ins_opa # Operand as address (loads/stores acc.)
ins_stack = const.ins_stack # Stack push/pop (Depending on whether it's in the source or the destination.)

# Special case mem-op instructions:
ld_ra_imm = const.ld_ra_imm; ld_rb_imm = const.ld_rb_imm
ld_rc_imm = const.ld_rc_imm; ld_ro_imm = const.ld_ro_imm

ldb_ra_imma = const.ldb_ra_imma; ldb_rb_imma = const.ldb_rb_imma
ldb_rc_imma = const.ldb_rc_imma

ldb_ra_roO = const.ldb_ra_roO; ldb_rb_roO = const.ldb_rb_roO
ldb_rc_roO = const.ldb_rc_roO

stb_ra_imma = const.stb_ra_imma; stb_rb_imma = const.stb_rb_imma
stb_rc_imma = const.stb_rc_imma

stb_ra_roO = const.stb_ra_roO; stb_rb_roO = const.stb_rb_roO
stb_rc_roO = const.stb_rc_roO

ld_ra_spPro = const.ld_ra_spPro; ld_rb_spPro = const.ld_rb_spPro
ld_rc_spPro = const.ld_ra_spPro

sto_ra_spPro = const.sto_ra_spPro; sto_rb_spPro = const.sto_rb_spPro
sto_rc_spPro = const.sto_rc_spPro

ld_ra_aoPro = const.ld_ra_aoPro; ld_rb_aoPro = const.ld_rb_aoPro
ld_rc_aoPro = const.ld_rc_aoPro

sto_ra_aoPro = const.sto_ra_aoPro; sto_rb_aoPro = const.sto_rb_aoPro
sto_rc_aoPro = const.sto_rc_aoPro


def main ():

microProgram = []
microProgram.extend(genRegisterTransferInstr())
microProgram.extend(genALUInstr())
microProgram.extend(genBranchInstr())
microProgram.extend(genMemOps())

assembleMicrocode(microProgram, "./leverage_architecture_ROM.txt")


def assembleInstrField(typeField, opDest, source):
instrField = 0

if typeField & 3 != typeField:
print(f"The type field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 2 bits.")
input()
exit()

instrField |= typeField * 2**6

if opDest & 7 != opDest:
print(f"The op/dest field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 3 bits.")
input()
exit()

instrField |= opDest * 2**3

if source & 7 != source:
print(f"The source field provided in the instruction field source\n{typeField}, {opDest}, {source} is too large for 3 bits.")
input()
exit()

instrField |= source * 2**0

return instrField

# A constant for the fetch microinstruction:
FETCH = [add_PC, dtaE_RBE, dtaW_ir, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0]

def genRegisterTransferInstr ():
microcode = []

for dest in range(0, 8):
for source in range(0, 8):

currentInstr = assembleInstrField(ins_regOp, dest, source)

if dest == source and source != ins_PC and source != ins_IO:

# Generate the select as accumulator instructions.
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, LHS_sel, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0],
])

elif dest == source and source == ins_PC:

# Generate the unconditional jump to immediate address.
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_PC, LHS_PC, RHS_con, ALF_add, 0x1, ONE, FLG_NFW, 0x0]
])

elif dest == source and source == ins_IO:

# Generate the wait interrupt instruction.
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IO, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else:

# Generate the mov instructions.
microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro, dtaW_sp, dtaW_ao, dtaW_IO, dtaW_PC][dest]
microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, dtaE_sp, dtaE_ao, dtaE_IO, dtaE_PC][source]

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, microSource, microDest, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

return microcode

def genALUInstr ():
microcode = []

for oper in range(0, 8):
for source in range(0, 8):

currentInstr = assembleInstrField(ins_ALUOp, oper, source)
# step1: Check whether the instruction is any of the special case ALU ops
# or it's a regular ALU op.
# step2: Generate the microcode accordingly.
if oper == ins_subB:

# Generate a subB instruction if the source is ra, rb, rc or ro.
# Otherwise, perform an add or sub of immediate or value at an
# immediate address offset by ro.
if source < 0x4:
microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_subB, 0x1, ONE, FLG_NOF, 0x0]
])

else:
# Add or subtract an immediate or immediate address offset by ro
# depending on the value of the least significant 2 bits.
if currentInstr == ins_add_imm:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate from memory.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Add the immediate to the accumulator, increment PC, and reset the microcounter.
[currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_add, 0x1, THREE, FLG_INC, 0x0]
])

elif currentInstr == ins_sub_imm:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate from memory.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NFW, 0x0],
# Subtract the immediate from the accumulator, increment PC, and reset the microcounter.
[currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_sub, 0x1, THREE, FLG_INC, 0x0]
])

elif currentInstr == ins_add_roO:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate address from memory.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Get the value at address tmp + ro into tmp.
[currentInstr, 0x0, 0x2], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_ro, RHS_tmp, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
# Add tmp to the acc, reset the microprogram counter and increment PC.
[currentInstr, 0x0, 0x3], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_add, 0x1, THREE, FLG_NOF, 0x0]
])

elif currentInstr == ins_sub_roO:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate address from memory.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Get the value at address tmp + ro into tmp.
[currentInstr, 0x0, 0x2], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_ro, RHS_tmp, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
# Add tmp to the acc, reset the microprogram counter and increment PC.
[currentInstr, 0x0, 0x3], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_tmp, ALF_sub, 0x1, THREE, FLG_NOF, 0x0]
])

elif oper == ins_add:

# If the source is PC or IO, these represent unary operations, not additions.
# These cases must be handled first.
if currentInstr == ins_not:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, NOE, ALF_not, 0x1, ONE, FLG_NOF, 0x0],
])

elif currentInstr == ins_rsh:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, NOE, ALF_rsh, 0x1, ONE, FLG_NOF, 0x0],
])

else: # Handle cases of normal addition.

microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_add, 0x1, ONE, FLG_NOF, 0x0]
])

elif oper == ins_sub:

# If the source is PC or IO, these represent unary operations, not subtractions.
# These cases must be handled first.
if currentInstr == ins_inc:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_con, ALF_add, 0x1, ONE, FLG_NOF, 0x0]
])

elif currentInstr == ins_dec:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, RHS_con, ALF_sub, 0x1, ONE, FLG_NOF, 0x0]
])

else: # Handle cases of normal subtraction.

microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][source]
if microSource == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
])

else:
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, ALF_sub, 0x1, ONE, FLG_NOF, 0x0]
])

# None of the other instructions need any special treatment so they go down here
# in an else clause:
else:

microOp = [ALF_add, ALF_sub, ALF_and, ALF_or, ALF_nor, ALF_xor, ALF_addC, ALF_subB][oper]
# 0xFFFF is a sentinal value.
microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][source]

if microSource == 0xFFFF:

# Generate microcode that will signal an invalid instruction error.
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else: # Generate microcode that corresponds to the operation specified.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_ALU, dtaW_acc, LHS_acc, microSource, microOp, 0x1, ONE, FLG_NOF, 0x0]
])

return microcode

def genBranchInstr ():
microcode = []

for oper in range(0, 8):
for flg_src in range(0, 8):

currentInstr = assembleInstrField(ins_brnOp, oper, flg_src)

if oper == ins_jfl:

# 0xFFFF is a sentinal value.
microFlg = [FLG_S, FLG_C, FLG_O, FLG_Z, FLG_PNZ, 0xFFFF, 0xFFFF, 0xFFFF][flg_src]
# If the current instruction is an illegal opcode, generate microcode that triggers
# an illegal instruction halt. Otherwise, generate the microcode that corresponds to
# the appropriate instruction.
if microFlg == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else: # Generate the appropriate branch instruction.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Multiplex the appropriate flag into the addressing input.
[currentInstr, 0x0, 0x1], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, microFlg, 0x0],

# In case the flag is unset, Increment PC by 3:
[currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],

# In case the flag is set, get the immediate address and stick it in PC.
# Cant use the ALU because it'll clobber the flags.
[currentInstr, 0x1, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, microFlg, 0x0],
[currentInstr, 0x1, 0x3], [add_PC, dtaE_RWE, dtaW_PC, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
])

elif oper == ins_jfc:

# 0xFFFF is a sentinal value.
microFlg = [FLG_S, FLG_C, FLG_O, FLG_Z, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF][flg_src]
# If the current instruction is an illegal opcode, generate microcode that triggers
# an illegal instruction halt. Otherwise, generate the microcode that corresponds to
# the appropriate instruction.
if microFlg == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else: # Generate the appropriate branch instruction.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Multiplex the appropriate flag into the addressing input.
[currentInstr, 0x0, 0x1], [NOE, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ZERO, microFlg, 0x1],

# In case the flag is unset, Increment PC by 3:
[currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],

# In case the flag is set, get the immediate address and stick it in PC.
# Cant use the ALU because it'll clobber the flags.
[currentInstr, 0x1, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, microFlg, 0x1],
[currentInstr, 0x1, 0x3], [add_PC, dtaE_RWE, dtaW_PC, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
])

elif oper == ins_call:

# 0xFFFF is a sentinal value.
microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, 0xFFFF, 0xFFFF, 0xFFFF, ins_call_imm][flg_src]

# If the current opcode is an illegal instruction, generate microcode that triggers an
# illegal instruction halt. Otherwise, generate microcode that calls the appropriate register
# or immediate address.
if microSource == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

elif microSource == ins_call_imm:
# These call instructions need to be modified so that they don't
# clobber the flags they write to the stack.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Save the status word to tmp to avoid clobbering the flags before writing the
# status word to the stack.
[currentInstr, 0x0, 0x1], [NOE, dtaE_stw, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Subtract 5 from ao to allocate stack memory for writing PC,
# SP and the status word to the stack
[currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, FIVE, FLG_NOF, 0x0],
# Write the status word value in tmp to the stack.
[currentInstr, 0x0, 0x3], [add_ao, dtaE_tmp, dtaW_RWB, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Write sp to ao + 1.
[currentInstr, 0x0, 0x4], [add_ALU, dtaE_sp, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Increment PC by 3.
[currentInstr, 0x0, 0x5], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, THREE, FLG_NOF, 0x0],
# Store the program counter at ao + 4
[currentInstr, 0x0, 0x6], [add_ALU, dtaE_PC, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
# Copy the value of ao into sp.
[currentInstr, 0x0, 0x7], [NOE, dtaE_ao, dtaW_sp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Load the immediate at PC-2 (because of the increment) into PC. Reset the microcounter.
[currentInstr, 0x0, 0x8], [add_ALU, dtaE_RWE, dtaW_PC, LHS_PC, RHS_con, ALF_sub, 0x1, TWO, FLG_NOF, 0x0]
])

else: # Handle calls to a register.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Save the status word to tmp to avoid clobbering the flags before writing the
# status word to the stack.
[currentInstr, 0x0, 0x1], [NOE, dtaE_stw, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Subtract 5 from ao to allocate stack memory for writing PC,
# SP and the status word to the stack
[currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, FIVE, FLG_NOF, 0x0],
# Write the status word value in tmp to the stack.
[currentInstr, 0x0, 0x3], [add_ao, dtaE_tmp, dtaW_RWB, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Write sp to ao + 1.
[currentInstr, 0x0, 0x4], [add_ALU, dtaE_sp, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Increment PC by 1.
[currentInstr, 0x0, 0x5], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, THREE, FLG_NOF, 0x0],
# Store the program counter at ao + 4
[currentInstr, 0x0, 0x6], [add_ALU, dtaE_PC, dtaW_RWW, LHS_ao, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
# Copy the value of ao into sp.
[currentInstr, 0x0, 0x7], [NOE, dtaE_ao, dtaW_sp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Copy the appropriate register into PC and reset the microinstruction counter.
[currentInstr, 0x0, 0x8], [NOE, microSource, dtaW_PC, NOE, NOE, ALF_add, 0x1, ZERO, FLG_NOF, 0x0]
])

elif oper == ins_ret:

# 0xFFFF is a sentinal value.
microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro, 0xFFFF, 0xFFFF, ins_NRV, 0xFFFF][flg_src]

if microSource == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

elif microSource == ins_NRV:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Store sp + 5 in ao.
[currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_sp, RHS_con, ALF_add, 0x0, FIVE, FLG_NOF, 0x0],
# Pop the status word into tmp to avoid clobbering the flags with the ALU ops.
[currentInstr, 0x0, 0x2], [add_sp, dtaE_RBE, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Pop the program counter.
[currentInstr, 0x0, 0x3], [add_ALU, dtaE_RWE, dtaW_PC, LHS_sp, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
# Pop the stack pointer.
[currentInstr, 0x0, 0x4], [add_ALU, dtaE_RWE, dtaW_sp, LHS_sp, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Move the status word value from tmp to the status word register
# and reset the microinstruction counter.
[currentInstr, 0x0, 0x5], [NOE, dtaE_tmp, dtaW_SWW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
])

else: # Handle cases of a register value being returned on the stack.

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Store sp + 5 in ao.
[currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_sp, RHS_con, ALF_add, 0x0, FIVE, FLG_NOF, 0x0],
# Pop the status word into tmp to avoid clobbering the flags with the ALU ops.
[currentInstr, 0x0, 0x2], [add_sp, dtaE_RBE, dtaW_tmp, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0x0],
# Pop the program counter.
[currentInstr, 0x0, 0x3], [add_ALU, dtaE_RWE, dtaW_PC, LHS_sp, RHS_con, ALF_add, 0x0, FOUR, FLG_NOF, 0x0],
# Pop the stack pointer.
[currentInstr, 0x0, 0x4], [add_ALU, dtaE_RWE, dtaW_sp, LHS_sp, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Push the desired register onto the stack.
[currentInstr, 0x0, 0x5], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, TWO, FLG_NOF, 0x0],
[currentInstr, 0x0, 0x6], [add_ao, microSource, dtaW_RWW, NOE, NOE, ALF_NOP, 0x0, ZERO, FLG_NOF, 0X0],
# Write the tmp register into the status word.
[currentInstr, 0x0, 0x7], [NOE, dtaE_tmp, dtaW_SWW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0],
])

elif oper == ins_cmp:

microSource = [RHS_ra, RHS_rb, RHS_rc, RHS_ro, RHS_sp, RHS_ao, 0xFFFF, 0xFFFF][flg_src]

if microSource == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, LHS_acc, microSource, ALF_sub, 0x1, ZERO, FLG_NOF, 0x0]
])

elif oper == ins_tst:

microSource = [LHS_ra, LHS_rb, LHS_rc, LHS_ro, LHS_sp, LHS_ao, 0xFFFF, 0xFFFF][flg_src]

if microSource == 0xFFFF:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [NOE, NOE, NOW, microSource, RHS_con, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
# The program counter increment must happen in a separate microinstruction
# because the constants field is occupied.
[currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0X0]
])

elif oper == ins_hlt:

if flg_src == 0:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, NOE, dtaW_HLT, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

else:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

elif oper == ins_NOP:

if flg_src == 0:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0x0]
])

return microcode

def genMemOps ():
microcode = []

for dest in range(0, 8):
for source in range(0, 8):

currentInstr = assembleInstrField(ins_memOp, dest, source)

# If the source is an addressing mode and the destination is a register,
# the operation is a load instruction in a standard addressing mode.
# These get handled first:
# Load immediate instructions:
if dest == source:

# 0xFFFF is a sentinal value. Moves of an addressing mode into itself are
# currently illegal instructions as they'll be used in future versions of
# this architecture to implement port IO instructions and interrupt handling
# instructions, along with other free code space.
microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF][dest]
if microDest == 0xFFFF:

# Generate illegal instruction microcode.
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_INC, dtaE_IIH, NOW, NOE, NOE, ALF_NOP, 0x1, ZERO, FLG_NOF, 0x0]
])

else:

microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, microDest, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NFW, 0x0],
[currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
])

elif dest in range(ins_ra, ins_ro + 1) and source in range(ins_imma, ins_stack + 1):

# Handle the loads from an immediate address.
if source == ins_imma:

microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro][dest]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate address.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Load the value from the immediate address into the destination, reset the microcounter
# and increment PC.
[currentInstr, 0x0, 0x2], [add_tmp, dtaE_RWE, microDest, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0]
])

# Handle the loads from ro offset to operand (Load into acc.)
elif source == ins_roO:

microDest = [LHS_ra, LHS_rb, LHS_rc, LHS_ro][dest]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_acc, microDest, RHS_ro, ALF_add, 0x1, ONE, FLG_INC, 0x0]
])

# Handle the loads from the address in the operand into the accumulator.
elif source == ins_opa:

microDest = [add_ra, add_rb, add_rc, add_ro][dest]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [microDest, dtaE_RWE, dtaW_acc, LHS_INC, NOE, ALF_NOP, 0X1, ONE, FLG_NOF, 0x0]
])

# Handle pops from the stack.
elif source == ins_stack:

microDest = [dtaW_ra, dtaW_rb, dtaW_rc, dtaW_ro][dest]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# PC has to be incremented here because the constants
# field is occupied in the final microinstruction.
[currentInstr, 0x0, 0x1], [add_ao, dtaE_RWE, microDest, LHS_INC, NOE, ALF_NOP, 0x0, ONE, FLG_NOF, 0x0],
[currentInstr, 0x0, 0x2], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_add, 0x1, TWO, FLG_NOF, 0x0]
])

# If the soure is a register and the destination is a register, then the operation is a store
# in a standard addressing mode. These get handled next.
elif dest in range(ins_imma, ins_stack + 1) and source in range(ins_ra, ins_ro + 1):


# Handle stores to an immediate address.
if dest == ins_imma:

microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate address.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
# Store the appropriate register value to that address,
# increment PC, and reset the micro counter.
[currentInstr, 0x0, 0x2], [add_tmp, microSource, dtaW_RWW, LHS_INC, NOE, ALF_NOP, 0x1, THREE, FLG_NOF, 0x0],
])

# Handle stores of the accumulator to a register offset to ro.
elif dest == ins_roO:

microSource = [LHS_ra, LHS_rb, LHS_rc, LHS_ro][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# There is now way to directly enable the accumulator onto the data bus. That means
# going through the ALU so the offset address cannot be computed in paralell.
[currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_tmp, microSource, RHS_ro, ALF_add, 0x0, ONE, FLG_INC, 0x0],
[currentInstr, 0x0, 0x2], [add_tmp, dtaE_ALU, dtaW_RWW, LHS_acc, RHS_con, ALF_add, 0x1, ZERO, FLG_NOF, 0x0],
])

# Handle stores of the accumulator to a register address.
elif dest == ins_opa:

microSource = [add_ra, add_rb, add_rc, add_ro][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [microSource, dtaE_ALU, dtaW_RWW, LHS_acc, RHS_con, ALF_add, 0x0, ZERO, FLG_NOF, 0x0],
[currentInstr, 0x0, 0x2], [add_INC, NOE, NOW, NOE, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
])

# Handle pushes onto the stack.
elif dest == ins_stack:

microSource = [dtaE_ra, dtaE_rb, dtaE_rc, dtaE_ro][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
[currentInstr, 0x0, 0x1], [NOE, dtaE_ALU, dtaW_ao, LHS_ao, RHS_con, ALF_sub, 0x0, TWO, FLG_NOF, 0X0],
[currentInstr, 0x0, 0x2], [add_ao, microSource, dtaW_RWW, LHS_INC, NOE, ALF_NOP, 0x1, ONE, FLG_NOF, 0X0]
])

# When the source and destination are both registers and not equal, they
# effectively represent redundant register transfer instructions. To utilize
# the available codespace, these are used to represent load and store of sign extended
# bytes in immediate addressing mode or ro offset by acc.
elif dest in range(ins_ra, ins_ro + 1) and source in range(ins_ra, ins_ro + 1) and dest != source:

# Handle loads of sign extended bytes in immediate addressing mode.
if currentInstr in [ldb_ra_imma, ldb_rb_imma, ldb_rc_imma]:

# The elif clause for load immediate instructions above already handles cases of
# source and destination codes being the same. 0xFFF is just a placeholder, not a
# sentinal value.
# 0 1 2 3
microSource = [0xFFFF, dtaW_ra, dtaW_rb, dtaW_rc][source]
microcode.extend([
[currentInstr, 0x0, 0x0], FETCH,
# Get the immediate address from memory.
[currentInstr, 0x0, 0x1], [add_ALU, dtaE_RWE, dtaW_tmp, LHS_PC, RHS_con, ALF_add, 0x0, ONE, FLG_NOF, 0x0],
Reply
#8
I would suggest you make a git repo e.g on github/gitlab/bitbucket. Currently your file exceeds post size limit and is not dispayed in full
If you can't explain it to a six year old, you don't understand it yourself, Albert Einstein
How to Ask Questions The Smart Way: link and another link
Create MCV example
Debug small programs

Reply
#9
I believe I've started this thread prematurely. There is still much refactoring to be done and I want to perfect the instruction set microcode. When the project is more presentable, I'll start a new thread with links to a git repo rather than posting the code on here. It's just too big to share directly on the forum. Thank you buran for confirming that there is a size limit and I wasn't doing anything wrong (other than exceeding said size limit.)

I will apply your suggestions and thank you for making them.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Intel 8051 microcontroller emulator estarq 0 2,105 Apr-22-2022, 10:59 AM
Last Post: estarq
  I wrote a cryptographic algorithm! keames 3 3,263 May-11-2021, 06:10 AM
Last Post: Gribouillis
  four commands i wrote the other day Skaperen 4 2,859 Jun-20-2019, 05:47 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020