Nov-10-2019, 09:07 PM
(This post was last modified: Nov-10-2019, 11:24 PM by Gribouillis.)
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.
The test microprogram generator:
It requires the constants in the test microprogram generator as well as the
assembleMicrocode function to work.
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
Attached Files