I wrote an emulator! - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code sharing (https://python-forum.io/forum-5.html) +--- Thread: I wrote an emulator! (/thread-22335.html) |
I wrote an emulator! - keames - Nov-10-2019 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_110I'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 RE: I wrote an emulator! - Gribouillis - Nov-10-2019 I was not able to fix the wrong display at the end of the last file. Please post that file again in its entirety. RE: I wrote an emulator! - keames - Nov-10-2019 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. RE: I wrote an emulator! - buran - Nov-11-2019 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 moduledon'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.") RE: I wrote an emulator! - buran - Nov-11-2019 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) RE: I wrote an emulator! - keames - Nov-12-2019 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. RE: I wrote an emulator! - keames - Nov-13-2019 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], RE: I wrote an emulator! - buran - Nov-13-2019 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 RE: I wrote an emulator! - keames - Nov-13-2019 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. |