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


Messages In This Thread
I wrote an emulator! - by keames - Nov-10-2019, 09:07 PM
RE: I wrote an emulator! - by Gribouillis - Nov-10-2019, 10:02 PM
RE: I wrote an emulator! - by keames - Nov-10-2019, 11:10 PM
RE: I wrote an emulator! - by buran - Nov-11-2019, 12:08 PM
RE: I wrote an emulator! - by buran - Nov-11-2019, 12:45 PM
RE: I wrote an emulator! - by keames - Nov-12-2019, 04:41 AM
RE: I wrote an emulator! - by keames - Nov-13-2019, 04:12 AM
RE: I wrote an emulator! - by buran - Nov-13-2019, 04:39 AM
RE: I wrote an emulator! - by keames - Nov-13-2019, 10:13 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Intel 8051 microcontroller emulator estarq 0 2,170 Apr-22-2022, 10:59 AM
Last Post: estarq
  I wrote a cryptographic algorithm! keames 3 3,364 May-11-2021, 06:10 AM
Last Post: Gribouillis
  four commands i wrote the other day Skaperen 4 2,942 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