diff --git a/Assembler.py b/Assembler.py index 26d0698..4b40d7d 100644 --- a/Assembler.py +++ b/Assembler.py @@ -30,7 +30,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/Capua.py b/CapuaEnvironment/Capua.py index 9da9590..9f90309 100644 --- a/CapuaEnvironment/Capua.py +++ b/CapuaEnvironment/Capua.py @@ -29,7 +29,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -58,7 +58,7 @@ def __init__(self, ma=None, mioc=None, name="System"): self.ma = ma if mioc is None: - self.mioc = MemoryIOController(self.ma) + self.mioc = MemoryIOController(self.ma, testOnly=False) else: self.mioc = mioc diff --git a/CapuaEnvironment/ExecutionUnit/ExecutionUnit.py b/CapuaEnvironment/ExecutionUnit/ExecutionUnit.py index 09e9c7b..065c817 100644 --- a/CapuaEnvironment/ExecutionUnit/ExecutionUnit.py +++ b/CapuaEnvironment/ExecutionUnit/ExecutionUnit.py @@ -22,19 +22,26 @@ from CapuaEnvironment.Instruction.Instruction import Instruction from CapuaEnvironment.IntructionFetchUnit.InstructionFetchUnit import InstructionFetchUnit +from CapuaEnvironment.IntructionFetchUnit.FormDescription import formDescription from CapuaEnvironment.IOComponent.MemoryIOController import MemoryIOController -from Configuration.Configuration import MEMORY_START_AT, \ - MEMORY_END_AT, \ - REGISTER_A, \ +from Configuration.Configuration import REGISTER_A, \ REGISTER_B, \ REGISTER_C, \ - REGISTER_S + REGISTER_D, \ + REGISTER_E, \ + REGISTER_F, \ + REGISTER_G, \ + REGISTER_S, \ + MEMORY_END_AT, \ + MEMORY_START_AT + +import threading __author__ = "CSE" __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -49,14 +56,28 @@ class ExecutionUnit: A = 0 # Software limited to 32 bits General purpose register B = 0 # Software limited to 32 bits General purpose register C = 0 # Software limited to 32 bits General purpose register + D = 0 # Software limited to 32 bits General purpose register + E = 0 # Software limited to 32 bits General purpose register + F = 0 # Software limited to 32 bits General purpose register + G = 0 # Software limited to 32 bits General purpose register S = 0 # Software limited to 32 bits Stack pointer. Can be used as a GPR is not using the stack I = 0 # Software limited to 32 bits Instruction pointer. This one is not accessible from instructions FLAGS = 0b000 # 3 bits limited ZLH = Zero, Lower, Higher + IS = 0b0 # 1 bit boolean indicating if interrupts are activated or not + IVR = 0x00 # 32 bits Interrupt Vector Register. This points to the area containing the interrupts vector + + # This is the buffer/register that tells if an hardware interrupt is being signaled + # None indicates no interrupt waiting to be processed. Anything else would + # be the interrupt number for a specific device. Note that a hardware interrupt + # will cause interrupts to be disabled until the hardware interrupt had been handled. + # This means that a single hardware interrupt at a time can be handled. + _interruptSignal = None + interruptSignalLock = threading.Lock() # Other required hardware components - mioc = None # MemoryInputOutputController - ifu = None # InstructionFetchUnit - lu = None # LogicUnit + mioc = None # MemoryInputOutputController + ifu = None # InstructionFetchUnit + lu = None # LogicUnit # Simple "process" identification token, this is changed to the name of the player # program when in game mode @@ -78,9 +99,10 @@ def __init__(self, mioc: MemoryIOController=None, ifu: InstructionFetchUnit=None raise RuntimeError("Capua core initialisation error - unstable state") self.mioc = mioc + self.mioc.eu = self # Need to make MIOC eu aware for memory mapped device to be able to signal interrupts self.ifu = ifu self.name = name - self.lu = LogicUnit(self) # LogicUnit is lower in this file + self.lu = LogicUnit(self) # LogicUnit is lower in this file def setupCore(self, I: int=MEMORY_START_AT): """ @@ -91,10 +113,38 @@ def setupCore(self, I: int=MEMORY_START_AT): :return: Nothing just does what it's being asked. """ if MEMORY_START_AT <= I <= MEMORY_END_AT: - self.I = I + self.reset(I) else: raise RuntimeError("Capua core {} Initialisation failed".format(self.name)) + def reset(self, I: int=MEMORY_START_AT): + """ + This is a convenience method made available for cases where we need to ensure a clean state + of the execution unit. This is used in test case as the EU does not currently support a "RESET" + instruction. After reset is issued, all registers will be set to 0 + :param I: int, a memory address where I pointer should be pointing + :return: nothing + """ + self.A = 0 + self.B = 0 + self.C = 0 + self.D = 0 + self.E = 0 + self.F = 0 + self.G = 0 + self.I = I + self.IS = 0 + self.IVR = 0 + self.S = 0 + self.FLAGS = 0 + + def halt(self): + """ + This method is to be called when the core is going down. This method will signal the MIOC so that it can + let devices be aware that they need to stop their threads. + :return: + """ + self.mioc.prepareForShutdown() def execute(self): """ @@ -104,12 +154,22 @@ def execute(self): next instruction to be executed. :return: Nothing """ + # Running things is this order makes it easier to debug + # When handling interrupt first, we end-up with an instruction + # that is executed without it being visible to the debugger. - # Get next instruction to be executed + # First we need to run the current instruction instruction, nextInstructionAddress = self.ifu.fetchInstructionAtAddress(self.I) self.I = nextInstructionAddress self.lu.executeInstruction(instruction) + # Then we can handle the interrupt + self.interruptSignalLock.acquire() + currentInterrupt = self._interruptSignal + self.interruptSignalLock.release() + if currentInterrupt is not None: + self._handleHardwareInterrupt() + def setRegisterValue(self, registerCode: int=None, value: int=None): """ This is the gate keeper to setting registers value. It make sure that the @@ -126,6 +186,14 @@ def setRegisterValue(self, registerCode: int=None, value: int=None): self.B = value elif registerCode == REGISTER_C: self.C = value + elif registerCode == REGISTER_D: + self.D = value + elif registerCode == REGISTER_E: + self.E = value + elif registerCode == REGISTER_F: + self.F = value + elif registerCode == REGISTER_G: + self.G = value elif registerCode == REGISTER_S: self.S = value else: @@ -144,6 +212,14 @@ def getRegisterValue(self, registerCode=None): register = self.B elif registerCode == REGISTER_C: register = self.C + elif registerCode == REGISTER_D: + register = self.D + elif registerCode == REGISTER_E: + register = self.E + elif registerCode == REGISTER_F: + register = self.F + elif registerCode == REGISTER_G: + register = self.G elif registerCode == REGISTER_S: register = self.S else: @@ -151,6 +227,45 @@ def getRegisterValue(self, registerCode=None): return register + def signalHardwareInterrupt(self, interruptNumber=None): + """ + This will safely line up an interrupt to be handled by the execution unit. + :param interruptNumber: int, this is the number to be used for the interrupt handler + :return: bool, True if interrupt was signaled, false otherwise. This gives a second chance if ever needed + """ + signalingValue = False + + self.interruptSignalLock.acquire() + + # Need to check Interrupt State before signaling + if self.IS != 0 and self._interruptSignal is None: + self._interruptSignal = interruptNumber + signalingValue = True + + self.interruptSignalLock.release() + + return signalingValue + + def _handleHardwareInterrupt(self): + """ + This will handle an hardwareInterrupt. It will deactivate the interrupts on a given core and + attempt at jumping into the code handling the latest successfully signaled interrupt. + :return: Nothing + """ + + self.interruptSignalLock.acquire() + + self.IS = 0 # Interrupts are now deactivated across the core + + intInstruction = Instruction(binaryInstruction=(0b10000011 << 8 * 4) | self._interruptSignal, + form=formDescription["InsImm"]) + self.lu.executeInstruction(instruction=intInstruction, hardware=True) + + self._interruptSignal = None # Interrupt has been handled, we need to reset this + + self.interruptSignalLock.release() + + class LogicUnit: """ The LogicUnit is tightly coupled with the ExecutionUnit. It is integral part of the @@ -191,12 +306,15 @@ def __init__(self, executionUnit: ExecutionUnit=None): else: RuntimeError("Capua environment, error initializing the logic unit") - def executeInstruction(self, instruction: Instruction=None): + def executeInstruction(self, instruction: Instruction=None, hardware: bool=False): """ This is the "public" entry point for the LogicUnit. The individual operations are not to be directly called. This method will call the individual instruction and make sure that the FLAGS register gets correctly updated after each instruction is executed. :param instruction: Instruction, the instruction to be executed. + :param hardware: bool, if the instruction is generated in hardware this will be true + This is required because some instructions can be hardware generated and launched by + the execution unit (outside of the LogicUnit but with a slight variation in behaviour. :return: Nothing! """ if instruction is None or type(instruction) is not Instruction: @@ -208,7 +326,7 @@ def executeInstruction(self, instruction: Instruction=None): # Using the mnemonic to find the correct method for the call callableOperation = getattr(self, self.ci.operationMnemonic) # This simply calls the correct mnemonic method - result = callableOperation() + result = callableOperation(hardware) # Some mnemonic return values causing FLAGS update if 0 <= result <= 0b111: @@ -219,7 +337,19 @@ def executeInstruction(self, instruction: Instruction=None): else: RuntimeError("LogicUnit Unknown error,operation {} went bad".format(instruction.operationMnemonic)) - def ADD(self): + def ACTI(self, hardware=False): + """ + 1 possibility: + ACTI + This activates the interrupts for the current ExecutionUnit + :return: 0, resets the flags + """ + self.eu.interruptSignalLock.acquire() + self.eu.IS = 0b1 + self.eu.interruptSignalLock.release() + return 0 + + def ADD(self, hardware=False): """ 2 possibilities: ADD sReg, dReg : Total Length 2B : ID 0b0111 10 : sR 00 : dR 1B : exec time = 1 : Addition @@ -241,7 +371,7 @@ def ADD(self): return 0 - def AND(self): + def AND(self, hardware=False): """ 2 possibilities: AND sReg, dReg : Total Length 2B : ID 0b0101 00 : sR 00 : dR 1B : exec time = 1 : Binary and @@ -263,7 +393,7 @@ def AND(self): return 0 - def CALL(self): + def CALL(self, hardware=False): """ More complex case. This does multiple things: 1- Push next I on top of stack @@ -298,7 +428,7 @@ def CALL(self): self.eu.I = sourceValue return 0 - def CMP(self): + def CMP(self, hardware=False): """ This will not change the register values... simply set the flags accordingly. 2 possibilities: @@ -331,7 +461,19 @@ def CMP(self): return flagsResult - def DIV(self): + def DACTI(self, hardware=False): + """ + 1 possibility: + DACTI + This deactivates the interrupts for the current ExecutionUnit + :return: 0, resets the flags + """ + self.eu.interruptSignalLock.acquire() + self.eu.IS = 0b0 + self.eu.interruptSignalLock.release() + return 0 + + def DIV(self, hardware=False): """ 1 possibility: DIV sReg, dReg : Total Length 2B : ID 0b1001 10 : sR 00 : dR 1B : exec time = 3 : Division, result is put in A, rest is put in B @@ -354,7 +496,87 @@ def DIV(self): return 0 - def JMP(self): + def HIRET(self, hardware=False): + """ + This instruction is used to return from a hardware interrupt. It will reactivate the interrupts + on the core, restore the flags and will then return. + :param hardware: bool, optional, hardware, This indicates if the instruction executed was hardware generated + :return: 0, this method does not affect the flags register + """ + currentInstruction = self.ci + + oldAValue = self.eu.A + # First save the return address + popInstruction = Instruction(binaryInstruction=(0b01110100 << 8) | REGISTER_A, form=formDescription["InsReg"]) + self.executeInstruction(popInstruction, hardware=True) + returnAddress = self.eu.A + self.executeInstruction(popInstruction, hardware=True) + flagsToBeRestored = self.eu.A + + self.eu.A = oldAValue + self.eu.I = returnAddress + + self.ci = currentInstruction + + # This absolutely needs to happen last since the core plays with registers. Would that happen + # before, there would be a big risk for the core to be in an inconsistent state. + self.eu.interruptSignalLock.acquire() + self.eu.IS = 0b1 + self.eu.interruptSignalLock.release() + + # Flags are restored in the return value other wise it will be overwritten + return flagsToBeRestored + + def INT(self, hardware=False): + """ + This method is a bit more complex. If interrupt are deactivated, simply return. Otherwise + the correct interrupt handler address needs to be fetched from the interrupt vector. Also + the return address needs to be pushed on the stack. Interrupt handler number is given + either in a register or as an immediate value. + 2 possibilities: + INT sReg + int sImm + :param: bool, optional, hardware, This indicates if the interrupt has been hardware generated + :return: + """ + + sourceValue = 0 + + # Interrupts are deactivated by the execution unit when handling a hardware interrupt + # Since the hardware interrupt reuse this code, a check needs to happen here in order + # to allow interrupt execution if generated in hardware. + if hardware or self.eu.IS != 0b0: + intInstruction = self.ci + + if hardware: + # If this is an hardware generated interrupt, we NEED to save the flags + flags = self.eu.FLAGS + pushInstruction = Instruction(binaryInstruction=(0b10000001 << 8 * 4) | flags, + form=formDescription["InsImm"]) + self.executeInstruction(pushInstruction, hardware=True) + + # Bring the correct instruction!!! + self.ci = intInstruction + if self.ci.sourceImmediate is None: + sourceValue = self.eu.getRegisterValue(self.ci.sourceRegister) + else: + sourceValue = self.ci.sourceImmediate + # At this moment, sourceValue hold the vector number + # we need sourceValue * 4 since the system uses 32 bits pointers + sourceValue *= 4 + + # Here, we read the vector data into source value + sourceValue = self.eu.mioc.memoryReadAtAddressForLength(self.eu.IVR + sourceValue, 4) + + # Now int can be managed like a call + callInstruction = Instruction(binaryInstruction=(0b10000010 << 8 * 4) | sourceValue, + form=formDescription["InsImm"]) + + self.executeInstruction(callInstruction, hardware=True) + self.ci = intInstruction + return 0 + + def JMP(self, hardware=False): """ More complex case. This does multiple things: 1- Check for condition @@ -386,7 +608,7 @@ def JMP(self): self.eu.I = nextI return 0 - def JMPR(self): + def JMPR(self, hardware=False): """ More complex case. This does multiple things: 1- Check for condition @@ -428,7 +650,7 @@ def JMPR(self): self.eu.I = nextI return 0 - def MEMR(self): + def MEMR(self, hardware=False): """ 2 possibilities: MEMR[WIDTH] sImm, dReg : Total Length 6B : ID 0b0000 10 : wD 00 : sI 4B : dR 1B : exec time = 2 : Read a pointer value to the register @@ -452,7 +674,7 @@ def MEMR(self): return 0 - def MEMW(self): + def MEMW(self, hardware=False): """ 4 possibilities: MEMW[WIDTH] sReg, dReg : Total Length 2B : ID 0b0001 00 : wD 00 : sR 0000 : dR 0000 : exec time = 3 : Write the content of a register to a given memory address (in register) @@ -491,7 +713,7 @@ def MEMW(self): return 0 - def MOV(self): + def MOV(self, hardware=False): """ 2 possibilities: MOV sReg, dReg : Total Length 2B : ID 0b0000 00 : sR 00 : dR 1B : exec time = 1 : Mov content of register to other register @@ -511,7 +733,7 @@ def MOV(self): return 0 - def MUL(self): + def MUL(self, hardware=False): """ 1 possibility: MUL sReg, dReg : Total Length 2B : ID 0b1001 01 : sR 00 : dR 1B : exec time = 2 : Multiplication, result is put in B:A @@ -535,14 +757,14 @@ def MUL(self): return 0 - def NOP(self): + def NOP(self, hardware=False): """ No operation, simply return 0 :return: """ return 0 - def NOT(self): + def NOT(self, hardware=False): """ 1 possibility: NOT Reg : Total Length 1B : ID 0b1000 10 : R 00 : exec time = 1 : Negation (bit inversion) @@ -557,7 +779,7 @@ def NOT(self): return 0 - def OR(self): + def OR(self, hardware=False): """ 2 possibilities: OR sReg, dReg : Total Length 2B : ID 0b0101 10 : sR 00 : dR 1B : exec time = 1 : Binary or @@ -579,7 +801,7 @@ def OR(self): return 0 - def POP(self): + def POP(self, hardware=False): """ More complex case. This does multiple things: 1- Get value from stack @@ -605,7 +827,7 @@ def POP(self): return 0 - def PUSH(self): + def PUSH(self, hardware=False): """ More complex case. This does multiple things: 1- Get current stack pointer + adjust stack pointer @@ -635,7 +857,7 @@ def PUSH(self): return 0 - def RET(self): + def RET(self, hardware=False): """ More complex case. This does multiple things: 1- Get value from stack @@ -662,7 +884,64 @@ def RET(self): return 0 - def SHL(self): + def SFSTOR(self, hardware=False): + """ + This is a more complicated instruction. This instruction will compare the source value + with the value located at the address contained in $A. If the resulting flags matches the + source value is then copied at $A. Of importance, source register can't be $A. + + 2 possibilities: + SFSTOR imm + SFSTOR reg + :return: The flags are affected by this instruction and can be used to check if the value has been copied + """ + sourceValue = 0 + destinationPointer = 0 + condition = self.ci.flags + sfstorInstruction = self.ci + + # This will be saved back in the register at the end of the operation + destinationPointer = self.eu.getRegisterValue(registerCode=REGISTER_A) + + if self.ci.sourceImmediate is None: + sourceValue = self.eu.getRegisterValue(registerCode=self.ci.sourceRegister) + else: + sourceValue = self.ci.sourceImmediate + + # Now first step is to read the data + widthAndDestination = 0b01000000 # width = 4, destination = reg A + memrInstruction = Instruction(binaryInstruction=(0b00000001 << 8 * 5) | + (widthAndDestination << 8 * 4) | + destinationPointer, + form=formDescription["InsWidthImmReg"]) + + self.executeInstruction(memrInstruction) + # Second step is to compare the data + cmpInstruction = Instruction(binaryInstruction=(0b01101000 << 8 * 5) | + (sourceValue << 8 * 1) | + REGISTER_A, + form=formDescription["InsImmReg"]) + # This will cause a modification of the live Flags register + self.executeInstruction(cmpInstruction) + + # Need to make $A be the initial $A again + self.eu.setRegisterValue(REGISTER_A, destinationPointer) + + # if this succeeds, we can move forward with the memory write + # As strange as the next conditional looks, the second part is for + # unconditional set detection. + if ((condition & self.eu.FLAGS) > 0) or (condition == self.eu.FLAGS): + memwInstruction = Instruction(binaryInstruction=(0b00000000 << 8 * 5) | + (widthAndDestination << 8 * 4) | + sourceValue, + form=formDescription["InsWidthImmReg"]) + self.executeInstruction(memwInstruction) + + self.ci = sfstorInstruction + # Need to return the current FLAGS as these would be overwritten upon return of this method. + return self.eu.FLAGS + + def SHL(self, hardware=False): """ 2 possibilities: SHL sReg, dReg : Total Length 2B : ID 0b0111 00 : sR 00 : dR 1B : exec time = 1 : Shift Left @@ -684,7 +963,7 @@ def SHL(self): return 0 - def SHR(self): + def SHR(self, hardware=False): """ 2 possibilities: SHR sReg, dReg : Total Length 2B : ID 0b0110 10 : sR 00 : dR 1B : exec time = 1 : Shift Right @@ -706,14 +985,27 @@ def SHR(self): return 0 - def SNT(self): + def SIVR(self, hardware=False): + """ + This instruction loads the Interrupt Vector Register with a value given in the sReg + 1 possibility: + SIVR sReg + :return: 0, resets the flags + """ + sourceValue = self.eu.getRegisterValue(registerCode=self.ci.sourceRegister) + sourceValue &= 0xFFFFFFFF + self.eu.IVR = sourceValue + + return 0 + + def SNT(self, hardware=False): """ Not implemented yet! :return: """ return 0 - def SUB(self): + def SUB(self, hardware=False): """ 2 possibilities: SUB sReg, dReg : Total Length 2B : ID 0b1000 00 : sR 00 : dR 1B : exec time = 1 : Subtraction @@ -735,7 +1027,7 @@ def SUB(self): return 0 - def XOR(self): + def XOR(self, hardware=False): """ 2 possibilities: XOR sReg, dReg : Total Length 2B : ID 0b0110 00 : sR 00 : dR 1B : exec time = 1 : Binary xor diff --git a/CapuaEnvironment/ExecutionUnit/test_ExecutionUnit.py b/CapuaEnvironment/ExecutionUnit/test_ExecutionUnit.py index d8da353..c112c31 100644 --- a/CapuaEnvironment/ExecutionUnit/test_ExecutionUnit.py +++ b/CapuaEnvironment/ExecutionUnit/test_ExecutionUnit.py @@ -34,7 +34,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -138,13 +138,208 @@ def execute(self): self.assertEqual(0, self.eu.C) self.assertEqual(0, self.eu.S) self.assertEqual(0, self.eu.FLAGS) - # MOV + + # Beware, ACTI and DACTI tests are dependent one on anoter + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b11110001) # ACTI 1 + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b11110001) # ACTI 2 + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 2, 1, 0b11110010) # DACTI 1 + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 3, 1, 0b11110010) # DACTI 2 + # ACTI self.eu.execute() - self.assertEqual(MEMORY_START_AT + 8, self.eu.I) - self.assertEqual(1, self.eu.A) - self.assertEqual(0, self.eu.B) - self.assertEqual(0, self.eu.C) - self.assertEqual(0, self.eu.S) - self.assertEqual(0, self.eu.FLAGS) + self.assertEqual(MEMORY_START_AT + 1, self.eu.I) + self.assertEqual(0b1, self.eu.IS) # Interrupt State should be = 1 after ACTI + self.eu.execute() + self.assertEqual(MEMORY_START_AT + 2, self.eu.I) + self.assertEqual(0b1, self.eu.IS) # Interrupt State should not change if ACTI is run when IS = 1 + # DACTI + self.eu.execute() + self.assertEqual(MEMORY_START_AT + 3, self.eu.I) + self.assertEqual(0b0, self.eu.IS) # Interrupt State should be = 0 after DACTI + self.eu.execute() + self.assertEqual(MEMORY_START_AT + 4, self.eu.I) + self.assertEqual(0b0, self.eu.IS) # Interrupt State should not change if DACTI is run when IS = 0 + + # SFSTOR - Data will be written + self.eu.setupCore(MEMORY_START_AT) + self.eu.A = MEMORY_START_AT + 20 + self.eu.B = 0xCCCCCCCC + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01010010) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00110001) # Instruction goes: SFSTOR [LH] $B + self.eu.FLAGS = 0b00 + self.eu.execute() + dataWritten = self.mioc.memoryReadAtAddressForLength(MEMORY_START_AT + 20, length=4) + self.assertEqual(MEMORY_START_AT + 2, self.eu.I) + self.assertEqual(0xCCCCCCCC, dataWritten) + # SFSTOR - Data will NOT be written + self.eu.setupCore(MEMORY_START_AT) + self.eu.A = MEMORY_START_AT + 20 + self.eu.B = 0xBBBBBBBB + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01010010) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b01000001) # Instruction goes: SFSTOR [E] $B + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 20, 4, 0xAAAAAAAA) + self.eu.FLAGS = 0b00 + self.eu.execute() + dataWritten = self.mioc.memoryReadAtAddressForLength(MEMORY_START_AT + 20, length=4) + self.assertEqual(MEMORY_START_AT + 2, self.eu.I) + self.assertNotEqual(0xBBBBBBBB, dataWritten) + + # SIVR + self.eu.setupCore(MEMORY_START_AT) + self.eu.A = 0xAAAAAAAA + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01110101) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00000000) # Instruction goes: SIVR $A + self.eu.execute() + self.assertEqual(MEMORY_START_AT + 2, self.eu.I) + self.assertEqual(0xAAAAAAAA, self.eu.IVR) + self.assertEqual(0xAAAAAAAA, self.eu.A) + + # HIRET - Deactivated Interrupts + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b11110011) + self.mioc.memoryWriteAtAddressForLength(0x40000100, 4, 0x40000200) # This is the return address + self.mioc.memoryWriteAtAddressForLength(0x400000FC, 4, 0x1) # This is the flag to be restored + self.eu.A = 0xFF + self.eu.IS = 0b0 + self.eu.S = 0x40000100 + self.eu.FLAGS = 0x0 + sMarker = self.eu.S + self.eu.execute() + self.assertEqual(0xFF, self.eu.A, "HIRET broken - bad A register value") + self.assertEqual(0x01, self.eu.FLAGS, "HIRET broken - bad flag restoration") + self.assertEqual(0b1, self.eu.IS, "HIRET broken - IS should not be 0") + self.assertEqual(0x40000200, self.eu.I, "HIRET broken - Bad I address") + self.assertEqual(sMarker, self.eu.S + 8, "HIRET broken - Bad stack") + + # INT register - Deactivated Interrupts + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01110110) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00000000) + self.eu.A = 0x0 # Holds the interrupt number + self.eu.IS = 0b0 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + self.eu.execute() + self.assertEqual(self.eu.I, MEMORY_START_AT + 2, "INT when IS is 0 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker, "INT when IS is 0 broken - Bad stack") + + # INT register - Activated Interrupts + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01110110) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00000000) + self.eu.A = 0x0 # Holds the interrupt number + self.eu.IS = 0b1 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + iMarker = self.eu.I + self.eu.execute() + self.assertEqual(self.eu.I, 0xAAAAAAAA, "INT when IS is 1 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker + 4, "INT when IS is 1 broken - Bad stack") + topStack = self.mioc.memoryReadAtAddressForLength(self.eu.S, 4) + self.assertEqual(iMarker + 2, topStack, "INT when IS is 1 broken - Bad return address") + # Testing the second vector entry + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b01110110) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00000000) + self.eu.A = 0x1 # Holds the interrupt number + self.eu.IS = 0b1 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + iMarker = self.eu.I + self.eu.execute() + self.assertEqual(self.eu.I, 0xBBBBBBBB, "INT when IS is 1 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker + 4, "INT when IS is 1 broken - Bad stack") + topStack = self.mioc.memoryReadAtAddressForLength(self.eu.S, 4) + self.assertEqual(iMarker + 2, topStack, "INT when IS is 1 broken - Bad return address") + + # INT immediate - Deactivated Interrupts + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b10000011) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 1, 0b00000000) # Interrupt number is here + self.eu.IS = 0b0 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + self.eu.execute() + self.assertEqual(self.eu.I, MEMORY_START_AT + 5, "INT when IS is 0 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker, "INT when IS is 0 broken - Bad stack") + + # INT immediate - Activated Interrupts + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b10000011) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 4, 0b00000000) # Interrupt number is here + self.eu.IS = 0b1 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + iMarker = self.eu.I + self.eu.execute() + self.assertEqual(self.eu.I, 0xAAAAAAAA, "INT when IS is 1 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker + 4, "INT when IS is 1 broken - Bad stack") + topStack = self.mioc.memoryReadAtAddressForLength(self.eu.S, 4) + self.assertEqual(iMarker + 5, topStack, "INT when IS is 1 broken - Bad return address") + # testing the second vector entry + self.eu.setupCore(MEMORY_START_AT) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT, 1, 0b10000011) + self.mioc.memoryWriteAtAddressForLength(MEMORY_START_AT + 1, 4, 0b00000001) # Interrupt number is here + self.eu.IS = 0b1 + self.eu.S = 0x40000100 + self.eu.IVR = 0x40000050 + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR, 4, 0xAAAAAAAA) + self.mioc.memoryWriteAtAddressForLength(self.eu.IVR + 4, 4, 0xBBBBBBBB) + sMarker = self.eu.S + iMarker = self.eu.I + self.eu.execute() + self.assertEqual(self.eu.I, 0xBBBBBBBB, "INT when IS is 1 broken - Bad I address") + self.assertEqual(self.eu.S, sMarker + 4, "INT when IS is 1 broken - Bad stack") + topStack = self.mioc.memoryReadAtAddressForLength(self.eu.S, 4) + self.assertEqual(iMarker + 5, topStack, "INT when IS is 1 broken - Bad return address") + + def test_reset(self): + self.eu.setupCore(MEMORY_START_AT) + self.eu.A = 1 + self.eu.B = 1 + self.eu.C = 1 + self.eu.D = 1 + self.eu.E = 1 + self.eu.F = 1 + self.eu.G = 1 + self.eu.S = 1 + self.eu.IS = 1 + self.eu.IVR = 1 + self.eu.FLAGS = 1 + self.eu.reset(0) # Making sure we can set I to value requested + count = self.eu.A + self.eu.B + self.eu.C + self.eu.D + self.eu.E + self.eu.F + self.eu.G + \ + self.eu.S + self.eu.IS + self.eu.IVR + self.eu.I + self.eu.FLAGS + self.assertEqual(count, 0) + self.eu.setupCore(MEMORY_START_AT) + self.eu.A = 1 + self.eu.B = 1 + self.eu.C = 1 + self.eu.D = 1 + self.eu.E = 1 + self.eu.F = 1 + self.eu.G = 1 + self.eu.S = 1 + self.eu.IS = 1 + self.eu.IVR = 1 + self.eu.FLAGS = 1 + self.eu.reset() # Making sure we can rely on the default value for I after reset + count = self.eu.A + self.eu.B + self.eu.C + self.eu.D + self.eu.E + self.eu.F + self.eu.G + \ + self.eu.S + self.eu.IS + self.eu.IVR + self.eu.I + self.eu.FLAGS + self.assertEqual(count, MEMORY_START_AT) diff --git a/CapuaEnvironment/IOComponent/MemoryIOController.py b/CapuaEnvironment/IOComponent/MemoryIOController.py index a063e92..2dacac5 100644 --- a/CapuaEnvironment/IOComponent/MemoryIOController.py +++ b/CapuaEnvironment/IOComponent/MemoryIOController.py @@ -22,17 +22,19 @@ from CapuaEnvironment.MemoryArray.MemoryArray import MemoryArray from CapuaEnvironment.IOComponent.MemoryMappedDevices.Clock.Clock import Clock -from CapuaEnvironment.IOComponent.MemoryMappedDevices.MemoryManagementUnit.MemoryManagementUnit import MemoryManagementUnit -from CapuaEnvironment.IOComponent.MemoryMappedDevices.SpartacusThreadMultiplexer.SpartacusThreadMultiplexer import SpartacusThreadMultiplexer +from CapuaEnvironment.IOComponent.MemoryMappedDevices.Terminal.Terminal import Terminal +from CapuaEnvironment.IOComponent.MemoryMappedDevices.InterruptClock.InterruptClock import InterruptClock +from CapuaEnvironment.IOComponent.MemoryMappedDevices.HardDrive.HardDrive import HardDrive from Configuration.Configuration import MEMORY_START_AT import struct +import threading __author__ = "CSE" __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -90,32 +92,43 @@ class MemoryIOController: _memoryArray = None _memoryMappedDevice = None - def __init__(self, memoryArray: MemoryArray=None): + def __init__(self, memoryArray: MemoryArray=None, testOnly: bool=True): """ Simple initialisation, this class is dependant on the presence of a memory array for it to work properly. :param memoryArray: A valid MemoryArray + :param eu: The execution unit owning this MIOC """ if memoryArray is None or type(memoryArray) is not MemoryArray: - raise RuntimeError("Capua MemoryIOController Error") + raise RuntimeError("Capua MemoryIOController Error - Setting up memoryArray") self._memoryArray = memoryArray self._memoryMappedDevice = [] - mmu = MemoryManagementUnit(parentMIOC=self) - self.registerMemoryMappedDevice(device=mmu, - startAddress=mmu.startAddress, - mask=mmu.mask) + self.eu = None clock = Clock(parentMIOC=self) self.registerMemoryMappedDevice(device=clock, startAddress=clock.startAddress, mask=clock.mask) - stmp = SpartacusThreadMultiplexer(parentMIOC=self) - self.registerMemoryMappedDevice(device=stmp, - startAddress=stmp.startAddress, - mask=stmp.mask) + if not testOnly: + iClock = InterruptClock(parentMIOC=self) + self.registerMemoryMappedDevice(device=iClock, + startAddress=iClock.startAddress, + mask=iClock.mask) + + terminal = Terminal(parentMIOC=self) + self.registerMemoryMappedDevice(device=terminal, + startAddress=terminal.startAddress, + mask=terminal.mask) + + hardDrive = HardDrive(parentMIOC=self) + self.registerMemoryMappedDevice(device=hardDrive, + startAddress=hardDrive.startAddress, + mask=hardDrive.mask) + + self._memoryBusLock = threading.Lock() def memoryWriteAtAddressForLength(self, address=0x00, length=4, value=0x00, source="System"): """ @@ -128,6 +141,8 @@ def memoryWriteAtAddressForLength(self, address=0x00, length=4, value=0x00, sour :return: None """ + self._memoryBusLock.acquire() + # If action taken on memory mapped hardware we need to send it to the hardware! if address < MEMORY_START_AT: self._passMemoryReadWriteToMemoryMappedHardware(address=address, @@ -135,16 +150,16 @@ def memoryWriteAtAddressForLength(self, address=0x00, length=4, value=0x00, sour value=value, isWrite=True, source=source) - return - - # First, we get the memory slice that we need to use and value to be written in individual bytes - memorySlice = self._memoryArray.extractMemory(address, length) - valueArray = self._prepareNumericValueToBeWrittenToMemory(length, value) + else: + # First, we get the memory slice that we need to use and value to be written in individual bytes + memorySlice = self._memoryArray.extractMemory(address, length) + valueArray = self._prepareNumericValueToBeWrittenToMemory(length, value) - # Now we write to memory! - for i in range(0, len(valueArray)): - memorySlice[i].writeValue(valueArray[i], accessedBy=source) + # Now we write to memory! + for i in range(0, len(valueArray)): + memorySlice[i].writeValue(valueArray[i], accessedBy=source) + self._memoryBusLock.release() return def memoryReadAtAddressForLength(self, address=0x00, length=4): @@ -156,13 +171,14 @@ def memoryReadAtAddressForLength(self, address=0x00, length=4): :return: int value """ + self._memoryBusLock.acquire() + # If action taken on memory mapped hardware we need to send it to the hardware! if address < MEMORY_START_AT: extractedValue = self._passMemoryReadWriteToMemoryMappedHardware(address=address, length=length, isWrite=False) else: - extractedValue = 0 extractedMemoryCells = self._memoryArray.extractMemory(address, length) valueToBeUnpacked = b"" @@ -176,8 +192,18 @@ def memoryReadAtAddressForLength(self, address=0x00, length=4): valueToBeUnpacked = b'\x00' + valueToBeUnpacked extractedValue = struct.unpack(">I", valueToBeUnpacked)[0] + self._memoryBusLock.release() return extractedValue + def prepareForShutdown(self): + """ + This method is called when the MIOC needs to get ready to be shutdown. This translate in the MIOC letting + all devices be aware that they should arrange for any running thread to stop. + :return: + """ + for device in self._memoryMappedDevice: + device.prepareForShutdown() + def _passMemoryReadWriteToMemoryMappedHardware(self, address=0x00, length=4, @@ -193,8 +219,6 @@ def _passMemoryReadWriteToMemoryMappedHardware(self, :param source: str, this is a marker to identify an action, usually the name associated with an execution unit :return: int value if read, None otherwise """ - returnValue = None - # We need to find a device that accept response for this memory access selectedDevice = None for device in self._memoryMappedDevice: @@ -212,7 +236,7 @@ def _passMemoryReadWriteToMemoryMappedHardware(self, source=source) else: # If we are here, no device responded to this device "call" - raise MemoryError("Access to Memory Mapped Hardware with invalid address") + raise MemoryError("Access to Memory Mapped Hardware with invalid address: {}".format(address)) return returnValue def _prepareNumericValueToBeWrittenToMemory(self, length=0, value=0): @@ -257,4 +281,3 @@ def registerMemoryMappedDevice(self, device=None, startAddress=None, mask=None): raise ValueError("Invalid mask for memory mapped device") self._memoryMappedDevice.append(device) - diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/BaseDevice.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/BaseDevice.py index c5cb6bc..5729ac9 100644 --- a/CapuaEnvironment/IOComponent/MemoryMappedDevices/BaseDevice.py +++ b/CapuaEnvironment/IOComponent/MemoryMappedDevices/BaseDevice.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -43,11 +43,23 @@ class BaseDevice: startAddress = 0x00000000 # This is the address where the device will be mapped in memory # obviously, this is different for every devices. mask = 0x00 # This indicate the memory range for this device. Think of this as a subnet mask in IPv4. - data = None # To be initialised by a device doing b"\x00" * maskValue + _data = None # To be initialised by a device doing b"\x00" * maskValue + + _interruptGenerator = False # This is used to indicate that this device will be a source of interruption + _interruptNumber = None # This will be used to hold the interrupt mapping for a device + + _shutdownProcedureInAction = False # When this is True, the devices need to terminate any running threads def __init__(self, parentMIOC=None): self._parentMIOC = parentMIOC + def prepareForShutdown(self): + """ + This to indicate to a device that it needs to prepare for core shutdown procedure. + :return: + """ + self._shutdownProcedureInAction = True + def takeAction(self, address=None, length=None, value=None, isWrite=False, source="System"): """ This is the public facing interface for the device. Both read and write happens from @@ -79,9 +91,15 @@ def _readFromDataBuffer(self, offset=None, length=None): :return: int representing the value read from the buffer """ + intData = None # Get the data - rData = self.data[offset:offset+length] - intData = struct.unpack(">I", rData)[0] + rData = self._data[offset:offset + length] + if length == 4: + intData = struct.unpack(">I", rData)[0] + elif length == 1: + intData = struct.unpack(">B", rData)[0] + else: + raise RuntimeError("Device memory read with invalid length - Should be 1 or 4") return intData @@ -100,11 +118,11 @@ def _writeIntoDataBuffer(self, offset=None, length=None, value=None, source="Sys readyData = struct.pack(">I", value) # Now write the data - dataFirstPart = self.data[0:offset] + dataFirstPart = self._data[0:offset] dataMiddlePart = readyData[-length:] - dataLastPart = self.data[offset+length:] + dataLastPart = self._data[offset + length:] - self.data = dataFirstPart + dataMiddlePart + dataLastPart + self._data = dataFirstPart + dataMiddlePart + dataLastPart self._memoryAction(source=source) @@ -120,7 +138,6 @@ def _memoryAction(self, source=None): """ raise ValueError("Device _memoryAction needs to be implemented before it is used.") - def _translateAddressToOffset(self, address=None): """ This will simply translate a memory address into an offset that can be @@ -138,7 +155,7 @@ def _confirmMemoryAccess(self, offset=None, length=None): :param length: int, for how long :return: Nothing, but throws exception in case of out of bound access """ - if (offset + length) > len(self.data): + if (offset + length) > len(self._data): raise MemoryError("Device - out of bound memory access detected!") if 0 >= length > 4: diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/Clock/Clock.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/Clock/Clock.py index 51a785b..e9f1527 100644 --- a/CapuaEnvironment/IOComponent/MemoryMappedDevices/Clock/Clock.py +++ b/CapuaEnvironment/IOComponent/MemoryMappedDevices/Clock/Clock.py @@ -29,21 +29,21 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" class Clock(BaseDevice): """ - This device is a Clock allowing users to know the EPOCH time in seconds. It can only be read + This device is a Clock providing the user with a source of entropy. It can only be read from. A write to this device will result in a system error/exception effectively crashing the context that was using the Clock device. """ def __init__(self, parentMIOC=None): super(Clock, self).__init__(parentMIOC=parentMIOC) - self.data = b"\x00" * 0xFF + self._data = b"\x00" * 0xFF self.startAddress = 0x20000100 self.mask = 0xFFFFFF00 @@ -77,12 +77,12 @@ def _readFromDataBuffer(self, offset=None, length=None): :param length: int, For how long :return: int representing the value read from the buffer """ - timeData = struct.pack(">I", int(time.time())) + timeData = struct.pack(">I", int((time.time() * 10000000)) & 0xFFFFFFFF) # Get the time in place - self.data = timeData + self.data[4:] + self._data = timeData + self._data[4:] # Now get the data - rData = self.data[offset:offset+length] + rData = self._data[offset:offset + length] intData = struct.unpack(">I", rData)[0] return intData diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/HardDrive/HardDrive.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/HardDrive/HardDrive.py new file mode 100644 index 0000000..934e960 --- /dev/null +++ b/CapuaEnvironment/IOComponent/MemoryMappedDevices/HardDrive/HardDrive.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: -*- + +""" +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +from CapuaEnvironment.IOComponent.MemoryMappedDevices.BaseDevice import BaseDevice +from Configuration.Configuration import HARD_DRIVE_FILE_PATH, \ + HARD_DRIVE_MAX_SIZE, \ + HARD_DRIVE_SECTOR_SIZE, \ + INTERRUPT_HARD_DRIVE_DONE_READ,\ + INTERRUPT_HARD_DRIVE_DONE_WRITE + + +import mmap +import struct +import threading +import time + +__author__ = "CSE" +__copyright__ = "Copyright 2017, CSE" +__credits__ = ["CSE"] +__license__ = "GPL" +__version__ = "2.0" +__maintainer__ = "CSE" +__status__ = "Dev" + + +class HardDrive(BaseDevice): + """ + This device is a virtual hard drive + """ + + def __init__(self, parentMIOC=None): + super(HardDrive, self).__init__(parentMIOC=parentMIOC) + self._data = b"\x00" * 0xFF + self.startAddress = 0x20000400 + self.mask = 0xFFFFFF00 + + self._hdLock = threading.Lock() + + self._hdFile = open(HARD_DRIVE_FILE_PATH, "r+b") + self._hdMmap = mmap.mmap(fileno=self._hdFile.fileno(), length=0) # Map the whole file + + self._watchDogThread = threading.Thread(target=self._hdWatchDog) + self._watchDogThread.start() + + def _hdWatchDog(self): + """ + This method is running within a thread. This will watch for the shutdown notice and + close file handle and memory map if required. + :return: Nothing + """ + while not self._shutdownProcedureInAction: + time.sleep(2) + self._hdMmap.close() + self._hdFile.close() + + def _memoryAction(self, source="System"): + """ + This only happens after a write. If an action should be taken, the action will be taken here + In the case of the current device, the only possible actions are read or write from/to disk + + 0x20000400 = 0x0 for a read operation or 0x01 for a write operation + 0x20000404 = LBA to be written or read + 0x20000408 = Memory Address to write TO or to read FROM + 0x2000040C = Trigger memory action = When this is set to 1, the action is triggered + + :param source: String, who is at the origin of this action + :return: + """ + trigger = self._readFromDataBuffer(offset=0xC, length=4) + if trigger != 1: + return + + operation = self._readFromDataBuffer(offset=0, length=4) + lba = self._readFromDataBuffer(offset=4, length=4) + bufferAddress = self._readFromDataBuffer(offset=8, length=4) + + if operation == 0: + # This is a read operation + readThread = threading.Thread(target=self._readDisk, args=(lba, bufferAddress,)) + readThread.start() + elif operation == 1: + # This is a write operation + writeThread = threading.Thread(target=self._writeDisk, args=(lba, bufferAddress,)) + writeThread.start() + else: + # This is invalid. + raise RuntimeError("Invalid hard drive operation code. 1=write, 0=read, else is error") + + def _readDisk(self, lba=None, destBuffer=None): + """ + This method will read a specified LBA from the disk and copy it at the specified address. + When done, it will generate the appropriate interrupt on the core. + :param lba: int, a number indicating the number of the block to be read + :param destBuffer: the address of the buffer where the LBA needs to be written in memory + :return: Nothing + """ + + startLocation = lba*HARD_DRIVE_SECTOR_SIZE + + self._hdLock.acquire() + lbaData = self._hdMmap[startLocation:startLocation+HARD_DRIVE_SECTOR_SIZE] + + # Read from the data present in the LBA and copy to memory + for i in range(0, HARD_DRIVE_SECTOR_SIZE, 4): + toBeWrittenInMemory = struct.unpack(">I", lbaData[startLocation:startLocation + 4])[0] + self._parentMIOC.memoryWriteAtAddressForLength(address=destBuffer, length=4, value=toBeWrittenInMemory) + startLocation += 4 # Reaching next block of memory + destBuffer += 4 # Reaching next block of memory + # Only thing left is signaling the interrupt. No need to hug the lock from here + self._hdLock.release() + + signaled = False + while not signaled: + signaled = self._parentMIOC.eu.signalHardwareInterrupt(INTERRUPT_HARD_DRIVE_DONE_READ) + return + + def _writeDisk(self, lba=None, srcBuffer=None): + """ + This method will write data from a specified address (sourceBuffer) to a given LBA on the disk + :param lba: int, a number indicating the number of the block to be written + :param srcBuffer: the address of the buffer where the information is to be read + :return: Nothing + """ + startLocation = lba*HARD_DRIVE_SECTOR_SIZE + + self._hdLock.acquire() + + for i in range(startLocation, startLocation + HARD_DRIVE_SECTOR_SIZE, 4): + toBeWrittenOnDisk = self._parentMIOC.memoryReadAtAddressForLength(address=srcBuffer, length=4) + readyBytes = struct.pack(">I", toBeWrittenOnDisk) + self._hdMmap[startLocation] = readyBytes[0] + self._hdMmap[startLocation + 1] = readyBytes[1] + self._hdMmap[startLocation + 2] = readyBytes[2] + self._hdMmap[startLocation + 3] = readyBytes[3] + startLocation += 4 + srcBuffer += 4 + # Only thing left is signaling the interrupt. No need to hug the lock from here + self._hdLock.release() + + signaled = False + while not signaled: + signaled = self._parentMIOC.eu.signalHardwareInterrupt(INTERRUPT_HARD_DRIVE_DONE_WRITE) + + return diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/InterruptClock/InterruptClock.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/InterruptClock/InterruptClock.py new file mode 100644 index 0000000..af7fffd --- /dev/null +++ b/CapuaEnvironment/IOComponent/MemoryMappedDevices/InterruptClock/InterruptClock.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: -*- + +""" +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +from CapuaEnvironment.IOComponent.MemoryMappedDevices.BaseDevice import BaseDevice +from Configuration.Configuration import INTERRUPT_CLOCK + +import threading +import time + +__author__ = "CSE" +__copyright__ = "Copyright 2017, CSE" +__credits__ = ["CSE"] +__license__ = "GPL" +__version__ = "2.0" +__maintainer__ = "CSE" +__status__ = "Dev" + + +class InterruptClock(BaseDevice): + """ + This device is a Clock that will generate interrupts on a given time interval. + This type of device is required in order to allow for multiprogramming. + The first 4 bytes of the memory reserved for this device holds the timer value in milliseconds. + """ + + def __init__(self, parentMIOC=None): + super(InterruptClock, self).__init__(parentMIOC=parentMIOC) + self._data = b"\x00" * 0xFF + self.startAddress = 0x20000300 + self.mask = 0xFFFFFF00 + self._interruptGenerator = True + self._interruptNumber = INTERRUPT_CLOCK + self._timerLength = 0.0 + self._timerRunning = False + self._clockThread = threading.Thread(target=self._runClock) + + def _memoryAction(self, source=None): + """ + This will simply set the timer interval after a write had been made. It will also start the timer if + it's not currently running. + :param source: + :return: + """ + interval = self._readFromDataBuffer(offset=0, length=4) + self._setTimer(interval) + + if not self._timerRunning: + self._startTimer() + + def _setTimer(self, timerLength=0): + """ + This method is meant to be used for direct manipulation of the timer value. + :param timerLength: int, this is a value representing the number of milliseconds the timer will wait + :return: Nothing is returned + """ + self._timerLength = float(timerLength) / 1000 + + def _startTimer(self): + """ + This will launch the thread that allows the timer to work in a separated thread + :return: + """ + self._clockThread.start() + + def _runClock(self): + """ + This is an endless loop that will sleep for a certain time and then generate + an interrupt to be handled by the execution unit. + :return: + """ + self._timerRunning = True + + while True: + if self._shutdownProcedureInAction: + # This allows the method to return if we need to shutdown the system + break + time.sleep(self._timerLength) + self._parentMIOC.eu.signalHardwareInterrupt(self._interruptNumber) diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/MemoryManagementUnit/__init__.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/InterruptClock/__init__.py similarity index 100% rename from CapuaEnvironment/IOComponent/MemoryMappedDevices/MemoryManagementUnit/__init__.py rename to CapuaEnvironment/IOComponent/MemoryMappedDevices/InterruptClock/__init__.py diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/MemoryManagementUnit/MemoryManagementUnit.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/MemoryManagementUnit/MemoryManagementUnit.py deleted file mode 100644 index e070899..0000000 --- a/CapuaEnvironment/IOComponent/MemoryMappedDevices/MemoryManagementUnit/MemoryManagementUnit.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python -# -*- coding: -*- - -""" -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" - -from CapuaEnvironment.IOComponent.MemoryMappedDevices.BaseDevice import BaseDevice - -import struct - -__author__ = "CSE" -__copyright__ = "Copyright 2015, CSE" -__credits__ = ["CSE"] -__license__ = "GPL" -__version__ = "1.3" -__maintainer__ = "CSE" -__status__ = "Dev" - - -class MemoryManagementUnit(BaseDevice): - """ - The MemoryManagementUnit allows the user to change the permission associated with a - region of the memory. The set of permission is RWX (in that same order). While it is - possible to change the permission associated with a region of the memory, it is not - possible too know ahead of time what are the permissions associated with a - specific region of the memory. Careful users should take for granted that they - do not have any permission in regions of memory where they are not already executing. - """ - - addressOffset = 0 # Where is the start address in the buffer this is 4 bytes - maskOffset = 4 # Where The length in memory for action format MMMM MMMM This is 1 bytes long - confirmPermissionOffset = 5 # Confirmation flag and new permission set format is CPPP 0000 This is 1 byte long - - - def __init__(self, parentMIOC=None): - super(MemoryManagementUnit, self).__init__(parentMIOC=parentMIOC) - self.data = b"\x00" * 0xFF - self.startAddress = 0x20000000 - self.mask = 0xFFFFFF00 - - def _memoryAction(self, source="System"): - """ - This only happens after a write. If an action should be taken, the action will be taken here - In the case of the current device, the only possible actions are memory permission management actions - :param source: String, who is at the origin of this action - :return: - """ - - confirmationMask = 0b10000000 - readMask = 0b01000000 - writeMask = 0b00100000 - executeMask = 0b00010000 - - # Do we have an action to take? - confirmData = self.data[self.confirmPermissionOffset] - confirmData &= confirmationMask - - if confirmData <= 0: - # No need to take action, simply return to keep code clean - return - - # If we are here, actions needs to be take - permissionData = (readMask | writeMask | executeMask) & self.data[self.confirmPermissionOffset] - - read = True if (permissionData & readMask) > 0 else False - write = True if (permissionData & writeMask) > 0 else False - execute = True if (permissionData & executeMask) > 0 else False - - addressToGo = struct.unpack(">I", self.data[self.addressOffset:self.addressOffset + 4])[0] - lengthToGo = int(self.data[self.maskOffset]) - - memoryCells = self._parentMIOC._memoryArray.extractMemory(address=addressToGo, length=lengthToGo) - - # Our individual memoryCells are in the memoryCells list - # The inside of the next loop could be done in a fancy loop avoiding code - # repetition. However, performance analysis showed that doing it that way - # saves between 0.0001 to 0.0005 seconds at execution. Since this is a tight - # loop that would usually be executed repetitively, this is worth it. - for cell in memoryCells: - # READ - if cell.canRead(): - if not read: - # We need to toggle the read flag - cell.toggleRead(accessedBy=source) - else: - if read: - cell.toggleRead(accessedBy=source) - - # WRITE - if cell.canWrite(): - if not write: - # We need to toggle the read flag - cell.toggleWrite(accessedBy=source) - else: - if write: - cell.toggleWrite(accessedBy=source) - - # EXECUTE - if cell.canExecute(): - if not execute: - # We need to toggle the read flag - cell.toggleExecute(accessedBy=source) - else: - if execute: - cell.toggleExecute(accessedBy=source) diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/SpartacusThreadMultiplexer/SpartacusThreadMultiplexer.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/SpartacusThreadMultiplexer/SpartacusThreadMultiplexer.py deleted file mode 100644 index 1af5d33..0000000 --- a/CapuaEnvironment/IOComponent/MemoryMappedDevices/SpartacusThreadMultiplexer/SpartacusThreadMultiplexer.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# -*- coding: -*- - -""" -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" - -from CapuaEnvironment.IOComponent.MemoryMappedDevices.BaseDevice import BaseDevice -from Configuration.Configuration import SPARTACUS_MAXIMUM_CONTEXT - -import struct -import time - -__author__ = "CSE" -__copyright__ = "Copyright 2015, CSE" -__credits__ = ["CSE"] -__license__ = "GPL" -__version__ = "1.3" -__maintainer__ = "CSE" -__status__ = "Dev" - - -# The following global is used to create a link between the VM and the game system. -# When GameEnvironment is None, VM understands that it is not running within the -# context of a gaming environment but rather as a standalone vm. The GameEnvironment -# variable has to be initiated by the GameManager when it is ready to start the game -GameEnvironment = None - - -class SpartacusThreadMultiplexer(BaseDevice): - """ - This is a special device that allows a link between the game environment and the virtual machine. That link - allows a player to create multiple thread that will be executed within the game environment. A maximum of - 3 thread/context can be created by the same player. Multiple threads are available only when in game mode. Using - this device when program is running in a debugger will not result in a system exception/error but will - simply be ignored. The device will still, however, allow for memory writes to happen in the device memory - buffer. Bottom line is: no game, no thread. Threading is provided by the game, not the vm. - """ - - def __init__(self, parentMIOC=None): - super(SpartacusThreadMultiplexer, self).__init__(parentMIOC=parentMIOC) - self.data = b"\x00" * 0xFF - self.startAddress = 0x20000200 - self.mask = 0xFFFFFF00 - - def _memoryAction(self, source=None): - """ - In this particular case, an action will only be taken if the device is used inside - of a GameEnvironment. Otherwise it simply returns. In case where a GameEnvironment - is available, a new thread/context will be created for that player. - :param source: - :return: - """ - if GameEnvironment is not None: - if len(GameEnvironment.currentContextBank) <= SPARTACUS_MAXIMUM_CONTEXT: - # There is a maximum number of context that a player can have at the same time! - # Yes the following imports are ugly, no I will not fix it. - from CapuaEnvironment.ExecutionUnit.ExecutionUnit import ExecutionUnit - from CapuaEnvironment.IntructionFetchUnit.InstructionFetchUnit import InstructionFetchUnit - try: - newContextInstructionAddress = struct.unpack(">I", self.data[0:4])[0] - ifu = InstructionFetchUnit(GameEnvironment.mioc._memoryArray) - newContext = ExecutionUnit(mioc=GameEnvironment.mioc, ifu=ifu, name=source) - newContext.setupCore(I=newContextInstructionAddress) - except Exception as e: - raise(ValueError("STM - Failure, unable to start new thread, likely cause: memory corruption")) - - GameEnvironment.currentContextBank.append(newContext) diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/Terminal/Terminal.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/Terminal/Terminal.py new file mode 100644 index 0000000..2e51fc4 --- /dev/null +++ b/CapuaEnvironment/IOComponent/MemoryMappedDevices/Terminal/Terminal.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: -*- + +""" +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +from CapuaEnvironment.IOComponent.MemoryMappedDevices.BaseDevice import BaseDevice +from Configuration.Configuration import DISPLAY_REFRESH_RATE, \ + DISPLAY_FONT_SIZE, \ + INTERRUPT_KEYBOARD, \ + KEYBOARD_BUFFER_SIZE, \ + KEYBOARD_REFRESH_RATE + + +import struct +import threading +import time +import tkinter +import tkinter.font + +__author__ = "CSE" +__copyright__ = "Copyright 2017, CSE" +__credits__ = ["CSE"] +__license__ = "GPL" +__version__ = "2.0" +__maintainer__ = "CSE" +__status__ = "Dev" + + +class Terminal(BaseDevice): + """ + This device is a virtual display unit. It allows for a text mode display on an 80x25 char basis + Char 0 line 0 char is mapped at 0x20001000, char 1 line 0 is mapped at 0x20001001. In order to + write text to the display, one simply need to write at the appropriate memory address. Following + a write to the display memory space, the display will then be updated. + """ + + def __init__(self, parentMIOC=None): + super(Terminal, self).__init__(parentMIOC=parentMIOC) + self._data = b"\x00" * 0xFFF + self.startAddress = 0x20001000 # Display is mapped from 0x20001000 to 0x200017ff + # Keyboard is mapped from 0x20001800 all the way to end of range + # but only the first byte is used + self.mask = 0xFFFFF000 + self._displayRefreshRate = float(DISPLAY_REFRESH_RATE) / 1000 + self._terminalBufferLock = threading.Lock() + + self._keyboardCodeListLock = threading.Lock() + self._keyboardCodeList = [] + self._keyboardRefreshRate = float(KEYBOARD_REFRESH_RATE) / 1000 + + self._displayThread = threading.Thread(target=self._displayLoop) + self._displayThread.start() + + self._keyboardThread = threading.Thread(target=self._keyboardLoop) + self._keyboardThread.start() + + self.interruptNumber = INTERRUPT_KEYBOARD + + def _generateDisplayText(self): + """ + This method is used to generate the text to be displayed on the screen label. As of now, this is working + but, should the bytes string be replaced with actual strings, this would break because of automatic trimming. + TK labels seem to support non printable character relatively well when used with byte string. + This requires more work to validate. + TODO: Validate about non printable character + :return: str, the display data ready for printing + """ + displayText = b"" + self._terminalBufferLock.acquire() + for i in range(0, 80*25, 80): + if i != 0: + displayText += b"\n" # Put here at loop beginning to prevent 1 too many lines + line = self._data[i:i + 80] + line.replace(b"\n", b" ") # Virtual display will break if we don't prevent \n in bytes + displayText += line + self._terminalBufferLock.release() + + return displayText + + def _keyboardLoop(self): + """ + This is the threaded loop that allows to put scan codes in the memory for them to be + processed by the machine. This is here so we can keep a "python" buffer of scan code + instead of an "in memory" list of scan code. The scan codes are inserted and + "signaled" at a specified (see configuration) interval in an attempt to prevent + scan code loss. + :return: + """ + while True: + if self._shutdownProcedureInAction: + # This allows the method to return if we need to shutdown the system + break + time.sleep(self._keyboardRefreshRate) # Division is to honour the milliseconds + + self._keyboardCodeListLock.acquire() + if len(self._keyboardCodeList) > 0: + # Get the oldest scan code + currentCode = self._keyboardCodeList[0] + self._writeIntoDataBuffer(offset=0x800, length=1, value=currentCode) + sigResult = self._parentMIOC.eu.signalHardwareInterrupt(self.interruptNumber) + if sigResult: + # The interrupt has been signaled, we can remove oldest scan code + poppedCode = self._keyboardCodeList.pop(0) + if poppedCode != currentCode: + # If this case is true, code list is corrupted + raise RuntimeError("Scan code list corruption has been detected") + self._keyboardCodeListLock.release() + + def _displayLoop(self): + """ + This is the display loop. This is in charge of asynchronous display operations and is + launched right after device initialisation is over. + :return: + """ + self.window = tkinter.Tk() + self.window.title("Spartacus Learning VM") + self.window.bind("", self._keyDown) + self.customFont = tkinter.font.Font(family="Courier", size=DISPLAY_FONT_SIZE) + self.displayWindow = tkinter.Label(self.window, + text=self._generateDisplayText(), + font=self.customFont, + bg="black", + fg="white", + justify=tkinter.LEFT) + self.displayWindow.grid() + + while True: + if self._shutdownProcedureInAction: + # This allows the method to return if we need to shutdown the system + self.window.quit() + break + self.displayWindow.config(text=self._generateDisplayText()) + #TODO need more research on this + self.window.update_idletasks() + self.window.update() + time.sleep(self._displayRefreshRate) + + def _keyDown(self, e): + """ + TODO: This will be used for the virtual keyboard. + :param e: + :return: + """ + self._keyboardCodeListLock.acquire() + + self._keyboardCodeList.append(e.keycode) + if len(self._keyboardCodeList) > KEYBOARD_BUFFER_SIZE: + self._keyboardCodeList.pop(0) # Flush oldest char but keep the rest + + self._keyboardCodeListLock.release() + + def _writeIntoDataBuffer(self, offset=None, length=None, value=None, source="System"): + """ + This will prepare the data to be written and will write it. Contrary to a normal device, this one need to + acquire a lock on the memory buffer. Also, no "action" is taken after a write since the action happens + asynchronously in order to help limit performance issues related to managing the display. + For these reasons, this is reimplemented here and the parent version is not used. + Important not, only write into buffer is re implemented from the parent because the display can read + the buffer but not write it. Therefore the parent read literally can't happen at the same time + as this write is happening. + :param offset: int, Where are we reading + :param length: int, For how long + :param value: int, the value to be written in the buffer + :param source: string, who is at the origin of this + :return: + """ + + # Let's prepare the data for the write + readyData = struct.pack(">I", value) + + # Now write the data + self._terminalBufferLock.acquire() + dataFirstPart = self._data[0:offset] + dataMiddlePart = readyData[-length:] + dataLastPart = self._data[offset + length:] + + self._data = dataFirstPart + dataMiddlePart + dataLastPart + self._terminalBufferLock.release() diff --git a/CapuaEnvironment/IOComponent/MemoryMappedDevices/SpartacusThreadMultiplexer/__init__.py b/CapuaEnvironment/IOComponent/MemoryMappedDevices/Terminal/__init__.py similarity index 100% rename from CapuaEnvironment/IOComponent/MemoryMappedDevices/SpartacusThreadMultiplexer/__init__.py rename to CapuaEnvironment/IOComponent/MemoryMappedDevices/Terminal/__init__.py diff --git a/CapuaEnvironment/IOComponent/test_MemoryIOController.py b/CapuaEnvironment/IOComponent/test_MemoryIOController.py index 8c99041..fd5df28 100644 --- a/CapuaEnvironment/IOComponent/test_MemoryIOController.py +++ b/CapuaEnvironment/IOComponent/test_MemoryIOController.py @@ -30,7 +30,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/Instruction/Instruction.py b/CapuaEnvironment/Instruction/Instruction.py index 6d3ce78..d9f10b0 100644 --- a/CapuaEnvironment/Instruction/Instruction.py +++ b/CapuaEnvironment/Instruction/Instruction.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/Instruction/OperationDescription.py b/CapuaEnvironment/Instruction/OperationDescription.py index 0408980..f59f1d5 100644 --- a/CapuaEnvironment/Instruction/OperationDescription.py +++ b/CapuaEnvironment/Instruction/OperationDescription.py @@ -24,7 +24,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -34,11 +34,15 @@ """ operationDescription = { + "ACTI": [0b11110001], "ADD": [0b01100110, 0b10010010], "AND": [0b01100001, 0b10010111], "CALL": [0b10000010, 0b01110010], "CMP": [0b01101000, 0b10011010], + "DACTI": [0b11110010], "DIV": [0b10010101], + "HIRET": [0b11110011], + "INT": [0b01110110, 0b10000011], "JMP": [0b01000001, 0b01010001], "JMPR": [0b01000000, 0b01010000], "MEMR": [0b00000001, 0b00010000], @@ -51,9 +55,11 @@ "POP": [0b01110100], "PUSH": [0b10000001, 0b01110011], "RET": [0b11110000], + "SFSTOR": [0b01000010, 0b01010010], + "SIVR": [0b01110101], "SHL": [0b01100101, 0b10010110], "SHR": [0b01100100, 0b10011001], - "SNT": [0b10000000, 0b01110001], # Not implemented at current time, DO NOT USE + "SNT": [0b10000000, 0b01110001], # Not implemented at current time, DO NOT USE "SUB": [0b01100111, 0b10010011], "XOR": [0b01100011, 0b10010000] } diff --git a/CapuaEnvironment/Instruction/test_Instruction.py b/CapuaEnvironment/Instruction/test_Instruction.py index 0058205..5cbe048 100644 --- a/CapuaEnvironment/Instruction/test_Instruction.py +++ b/CapuaEnvironment/Instruction/test_Instruction.py @@ -29,7 +29,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -101,7 +101,7 @@ def test_init(self): self.assertEqual(ins.instructionLength, 6) self.assertEqual(ins.operationMnemonic, "MEMW") - ins = Instruction(0b00010000 << 8 * 1, + ins = Instruction(0b00010000 << 16 * 1, formDescription["InsWidthRegReg"]) self.assertIsNotNone(ins.instructionCode) self.assertIsNotNone(ins.sourceRegister) @@ -110,7 +110,7 @@ def test_init(self): self.assertIsNone(ins.destinationImmediate) self.assertIsNotNone(ins.width) self.assertIsNone(ins.flags) - self.assertEqual(ins.instructionLength, 2) + self.assertEqual(ins.instructionLength, 3) self.assertEqual(ins.operationMnemonic, "MEMR") ins = Instruction(0b00100000 << 8 * 5, diff --git a/CapuaEnvironment/IntructionFetchUnit/FormDescription.py b/CapuaEnvironment/IntructionFetchUnit/FormDescription.py index c185602..833c24f 100644 --- a/CapuaEnvironment/IntructionFetchUnit/FormDescription.py +++ b/CapuaEnvironment/IntructionFetchUnit/FormDescription.py @@ -24,15 +24,18 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" formDescription = { "Ins": { 'typeCode': 0b1111, - 'listing': [0b0000, # RET - 0b1111], # NOP + 'listing': [0b0000, # RET + 0b0001, # ACTI + 0b0010, # DACTI + 0b0011, # HIRET + 0b1111], # NOP 'description': { "instructionCode": 0b11111111 }, @@ -40,11 +43,13 @@ }, "InsReg": { 'typeCode': 0b0111, - 'listing': [0b0000, # NOT - 0b0001, # SNT - This is not implemented, DO NOT USE - 0b0010, # CALL - 0b0011, # PUSH - 0b0100], # POP + 'listing': [0b0000, # NOT + 0b0001, # SNT - This is not implemented, DO NOT USE + 0b0010, # CALL + 0b0011, # PUSH + 0b0100, # POP + 0b0101, # SIVR + 0b0110], # INT 'description': { "instructionCode": 0b1111111100000000, "sourceRegister": 0b0000000011111111 @@ -53,9 +58,10 @@ }, "InsImm": { 'typeCode': 0b1000, - 'listing': [0b0000, # SNT - This is not implemented, DO NOT USE - 0b0001, # PUSH - 0b0010], # CALL + 'listing': [0b0000, # SNT - This is not implemented, DO NOT USE + 0b0001, # PUSH + 0b0010, # CALL + 0b0011], # INT 'description': { "instructionCode": 0b1111111100000000000000000000000000000000, "sourceImmediate": 0b0000000011111111111111111111111111111111 @@ -64,15 +70,15 @@ }, "InsImmReg": { 'typeCode': 0b0110, - 'listing': [0b0000, # MOV - 0b0001, # AND - 0b0010, # OR - 0b0011, # XOR - 0b0100, # SHR - 0b0101, # SHL - 0b0110, # ADD - 0b0111, # SUB - 0b1000], # CMP + 'listing': [0b0000, # MOV + 0b0001, # AND + 0b0010, # OR + 0b0011, # XOR + 0b0100, # SHR + 0b0101, # SHL + 0b0110, # ADD + 0b0111, # SUB + 0b1000], # CMP 'description': { "instructionCode": 0b111111110000000000000000000000000000000000000000, "sourceImmediate": 0b000000001111111111111111111111111111111100000000, @@ -82,8 +88,8 @@ }, "InsWidthImmReg": { 'typeCode': 0b0000, - 'listing': [0b0000, # MEMW - 0b0001], # MEMR + 'listing': [0b0000, # MEMW + 0b0001], # MEMR 'description': { "instructionCode": 0b111111110000000000000000000000000000000000000000, "width": 0b000000001111000000000000000000000000000000000000, @@ -94,15 +100,15 @@ }, "InsWidthRegReg": { 'typeCode': 0b0001, - 'listing': [0b0000, # MEMR - 0b0001], # MEMW + 'listing': [0b0000, # MEMR + 0b0001], # MEMW 'description': { - "instructionCode": 0b1111111100000000, - "width": 0b0000000011110000, - "sourceRegister": 0b0000000000001100, - "destinationRegister": 0b0000000000000011 + "instructionCode": 0b111111110000000000000000, + "width": 0b000000001111000000000000, + "sourceRegister": 0b000000000000111100000000, + "destinationRegister": 0b000000000000000000001111 }, - "length": 2 + "length": 3 }, "InsWidthRegImm": { 'typeCode': 0b0010, @@ -117,7 +123,7 @@ }, "InsWidthImmImm": { 'typeCode': 0b0011, - 'listing': [0b0000], # MEMW + 'listing': [0b0000], # MEMW 'description': { "instructionCode": 0b11111111000000000000000000000000000000000000000000000000000000000000000000000000, "width": 0b00000000111111110000000000000000000000000000000000000000000000000000000000000000, @@ -128,8 +134,9 @@ }, "InsFlagImm": { 'typeCode': 0b0100, - 'listing': [0b0000, # JMPR - 0b0001], # JMP + 'listing': [0b0000, # JMPR + 0b0001, # JMP + 0b0010], # SFSTOR 'description': { "instructionCode": 0b111111110000000000000000000000000000000000000000, "flags": 0b000000001111111100000000000000000000000000000000, @@ -139,8 +146,9 @@ }, "InsFlagReg": { 'typeCode': 0b0101, - 'listing': [0b0000, # JMPR - 0b0001], # JMP + 'listing': [0b0000, # JMPR + 0b0001, # JMP + 0b0010], # SFSTOR 'description': { "instructionCode": 0b1111111100000000, "flags": 0b0000000011110000, @@ -150,17 +158,17 @@ }, "InsRegReg": { 'typeCode': 0b1001, - 'listing': [0b0000, # XOR - 0b0010, # ADD - 0b0011, # SUB - 0b0100, # MUL - 0b0101, # DIV - 0b0110, # SHL - 0b0111, # AND - 0b1000, # OR - 0b1001, # SHR - 0b1010, # CMP - 0b1011], # MOV + 'listing': [0b0000, # XOR + 0b0010, # ADD + 0b0011, # SUB + 0b0100, # MUL + 0b0101, # DIV + 0b0110, # SHL + 0b0111, # AND + 0b1000, # OR + 0b1001, # SHR + 0b1010, # CMP + 0b1011], # MOV 'description': { "instructionCode": 0b1111111100000000, "sourceRegister": 0b0000000011110000, diff --git a/CapuaEnvironment/IntructionFetchUnit/InstructionFetchUnit.py b/CapuaEnvironment/IntructionFetchUnit/InstructionFetchUnit.py index 8244a96..49d000e 100644 --- a/CapuaEnvironment/IntructionFetchUnit/InstructionFetchUnit.py +++ b/CapuaEnvironment/IntructionFetchUnit/InstructionFetchUnit.py @@ -29,7 +29,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -91,11 +91,10 @@ def _fetchInstructionFormAtAddress(self, address=MEMORY_START_AT): # We found the correct form! instructionForm = formDescription[form] break - if instructionForm is None: # If we are here, no instruction were found that are corresponding # a user is trying to execute an invalid instruction! - raise ValueError("Invalid instruction detected") + raise ValueError("Invalid instruction detected at address {}".format(hex(address))) return instructionForm diff --git a/CapuaEnvironment/IntructionFetchUnit/test_InstructionFetchUnit.py b/CapuaEnvironment/IntructionFetchUnit/test_InstructionFetchUnit.py index e35adb9..9685a99 100644 --- a/CapuaEnvironment/IntructionFetchUnit/test_InstructionFetchUnit.py +++ b/CapuaEnvironment/IntructionFetchUnit/test_InstructionFetchUnit.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -76,7 +76,7 @@ def test_fetchInstructionAtAddress(self): self.assertIsNone(instruction.destinationImmediate) self.assertIsNotNone(instruction.width) self.assertIsNone(instruction.flags) - self.assertEqual(nextInstructionAddress, MEMORY_START_AT + 2) + self.assertEqual(nextInstructionAddress, MEMORY_START_AT + 3) def test_fetchInstructionFormAtAddress(self): diff --git a/CapuaEnvironment/MemoryArray/MemoryArray.py b/CapuaEnvironment/MemoryArray/MemoryArray.py index d4ed089..04e50b1 100644 --- a/CapuaEnvironment/MemoryArray/MemoryArray.py +++ b/CapuaEnvironment/MemoryArray/MemoryArray.py @@ -29,7 +29,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -68,7 +68,7 @@ def extractMemory(self, address, length=1): # Making sure the length does not reach out of the memory lengthLimit = MEMORY_END_AT - address if length <= 0 or length > lengthLimit: - raise MemoryError("Access reaching out of bound of memory") + raise MemoryError("Access reaching out of bound of memory for address: {}".format(hex(address))) # Memory extraction from base baseIndex = self._computeArrayIndexFromAddress(address) @@ -97,7 +97,7 @@ def _computeArrayIndexFromAddress(self, address): # First, is the address in a valid range if MEMORY_START_AT > address or address >= MEMORY_END_AT: # Address out of bounds! - raise MemoryError("Address out of bounds of memory") + raise MemoryError("Address out of bounds of memory {}".format(str(address))) # Calculate the actual index in memory array index = address - MEMORY_START_AT diff --git a/CapuaEnvironment/MemoryArray/MemoryCell/MemoryCell.py b/CapuaEnvironment/MemoryArray/MemoryCell/MemoryCell.py index a2754e5..50502dc 100644 --- a/CapuaEnvironment/MemoryArray/MemoryCell/MemoryCell.py +++ b/CapuaEnvironment/MemoryArray/MemoryCell/MemoryCell.py @@ -24,7 +24,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/MemoryArray/MemoryCell/test_MemoryCell.py b/CapuaEnvironment/MemoryArray/MemoryCell/test_MemoryCell.py index da1dce5..5a0e701 100644 --- a/CapuaEnvironment/MemoryArray/MemoryCell/test_MemoryCell.py +++ b/CapuaEnvironment/MemoryArray/MemoryCell/test_MemoryCell.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/MemoryArray/test_MemoryArray.py b/CapuaEnvironment/MemoryArray/test_MemoryArray.py index c6570a1..6a45744 100644 --- a/CapuaEnvironment/MemoryArray/test_MemoryArray.py +++ b/CapuaEnvironment/MemoryArray/test_MemoryArray.py @@ -32,7 +32,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/CapuaEnvironment/firmware.casm b/CapuaEnvironment/firmware.casm new file mode 100644 index 0000000..b288c21 --- /dev/null +++ b/CapuaEnvironment/firmware.casm @@ -0,0 +1,109 @@ +; This is CAPUA's firmware. It will load the first block from the disk. Validate the magic value and +; jump (through IRET on disk handler) to it. After the jump, boot is "done" and user code is in +; execution. +.global start: + +start: +MOV :readBuffer $S +ADD #1024 $S +MOV :vector $A +SIVR $A +ACTI + +; displayText($A=line, $C=text, $D=len) +MOV #0 $A +MOV :welcome $C +MOV #38 $D +CALL :diplayText + +MOV #0x20000400 $A +MEMW [4] #0x0 $A ; Set read action +ADD #4 $A +MEMW [4] #0x0 $A ; Set LBA +ADD #4 $A +MEMW [4] :readBuffer $A ; Set destination buffer +ADD #4 $A +MEMW [4] #0x01 $A ; Set the trigger + + +; This loop wait until the disk load is done +waitLoop: +NOP +JMP <> :waitLoop + +errorCode: +MOV #2 $A +MOV :error $C +MOV #26 $D +CALL :diplayText +JMP <> :waitLoop + +clockHandler: +HIRET + +keyboardHandler: +HIRET + +hardDriveReadHandler: +MOV :readBuffer $A +ADD #510 $A +MEMR [1] $A $B +CMP #0x55 $B ; First part of the magic number +JMP :errorCode +ADD #1 $A +MEMR [1] $A $B +CMP #0xAA $B +JMP :errorCode +XOR $A $A +MOV #0x40000000 $B +MOV :readBuffer $C +copyLoop: +MEMR [4] $C $E +MEMW [4] $E $B +ADD #4 $A +ADD #4 $B +ADD #4 $C +CMP #512 $A +JMP :copyLoop +MOV #2 $A +MOV :loadDone $C +MOV #34 $D +CALL :diplayText +POP $A ; Forcing return into newly loaded code +PUSH #0x40000000 +HIRET + +hardDriveWriteHandler: +HIRET + +; displayText($A=line, $C=text, $D=len) +diplayText: +XOR $E $E +MOV #80 $F +MUL $F $A +ADD #0x20001000 $A +printLoop: +MEMR [1] $C $G +MEMW [1] $G $A +ADD #1 $A +ADD #1 $C +SUB #1 $D +CMP #0 $D +JMP :printLoop +RET + +welcome: +.dataAlpha CAPUA VM booting from disk in progress +loadDone: +.dataAlpha Done loading information from disk +error: +.dataAlpha Disk image is not valid... + +vector: +.dataMemRef :clockHandler +.dataMemRef :keyboardHandler +.dataMemRef :hardDriveReadHandler +.dataMemRef :hardDriveWriteHandler + +readBuffer: +end: \ No newline at end of file diff --git a/Configuration/Configuration.py b/Configuration/Configuration.py index 42457ed..22289a5 100644 --- a/Configuration/Configuration.py +++ b/Configuration/Configuration.py @@ -24,7 +24,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -34,14 +34,34 @@ MEMORY_END_AT = MEMORY_START_AT + MEMORY_ARRAY_NUMBER_OF_MEMORY_CELL MEMORY_CELL_INITIAL_VALUE = 0XFF # NOP operation -CAPUA_NUMBER_OF_CORE = 0x04 # How many execution units are running in the Capua environment -CAPUA_SYSTEM_CORE_SPEED = 0x0F # How many unit of execution a system core can run when it is tasked -CAPUA_PLAYER_CORE_SPEED = 0x04 # How many unit of execution a player core can run when tasked +REGISTER_A = 0b0000 +REGISTER_B = 0b0001 +REGISTER_C = 0b0010 +REGISTER_D = 0b0011 +REGISTER_E = 0b0100 +REGISTER_F = 0b0101 +REGISTER_G = 0b0110 -SPARTACUS_MAXIMUM_CONTEXT = 3 # What is the maximum number of context a player can have +REGISTER_S = 0b1111 -REGISTER_A = 0b00 -REGISTER_B = 0b01 -REGISTER_C = 0b10 -REGISTER_S = 0b11 +DISPLAY_REFRESH_RATE = 5 # This is in milliseconds +DISPLAY_FONT_SIZE = 12 + +KEYBOARD_REFRESH_RATE = 5 # This is in milliseconds +KEYBOARD_BUFFER_SIZE = 20 # How big is the keyboard buffer (scan code buffer) + +HARD_DRIVE_FILE_PATH = "HD.bin" +HARD_DRIVE_SECTOR_SIZE = 512 +HARD_DRIVE_MAX_SIZE = 2048 # Size is given in sectors!!! 2048 sectors of 512 bytes each = 1MB + +INTERRUPT_CLOCK = 0x00 +INTERRUPT_KEYBOARD = 0x01 +INTERRUPT_HARD_DRIVE_DONE_READ = 0x02 +INTERRUPT_HARD_DRIVE_DONE_WRITE = 0x03 + +DEBUGGER_WAKEUP_TICK_COUNT = 0 # Used to keep debugger "in control" + +VIRTUAL_BOOT_ENABLED = True # This will enforce booting from the "hard drive" by using the "firmware" +FIRMWARE_LOAD_ADDRESS = 0x40001000 # Firmware will be loaded at this address when using virtual boot +FIRMWARE_BINARY_FILE_PATH = "CapuaEnvironment/firmware.bin" diff --git a/Debugger.py b/Debugger.py index 8d48942..aafd6c3 100644 --- a/Debugger.py +++ b/Debugger.py @@ -20,6 +20,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ +from Configuration.Configuration import FIRMWARE_BINARY_FILE_PATH, \ + FIRMWARE_LOAD_ADDRESS from ToolChain.Debugger.Debugger import Debugger from ToolChain.Linker.Constants import DEFAULT_LOAD_ADDRESS, UNDEFINED @@ -30,7 +32,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -46,7 +48,7 @@ def parseCommandLineArgs(): "licence. Feel free to distribute, modify, " "contribute and learn!".format(__license__,)) parser.add_argument("-i", "--input", - required=True, + required=False, type=str, help="Define the input file(s) to be used.") @@ -92,10 +94,21 @@ def validatePaths(argsWithPaths): return gotSymbols + if __name__ == '__main__': usableArgs = parseCommandLineArgs() - gotSymbols = validatePaths(usableArgs) # Make sure the parsed info is usable before using it! - symbolsFile = usableArgs.input.split(".")[0] + ".sym" if gotSymbols else "" + + gotSymbols = False + symbolsFile = None + + if usableArgs.input is not None: + gotSymbols = validatePaths(usableArgs) # Make sure the parsed info is usable before using it! + symbolsFile = usableArgs.input.split(".")[0] + ".sym" if gotSymbols else "" + else: + usableArgs.input = FIRMWARE_BINARY_FILE_PATH + usableArgs.software = False + usableArgs.address = FIRMWARE_LOAD_ADDRESS + print("Debug session about to begin, following options will be used") print(" input file: {}".format(usableArgs.input,)) @@ -112,6 +125,6 @@ def validatePaths(argsWithPaths): loadAddress=usableArgs.address, softwareLoader=usableArgs.software, symbolsFile=symbolsFile) - if usableArgs.output is not None and os.path.exists(usableArgs.output): + if usableArgs.output is not None and os.path.exists(usableArgs.output[0]): # The assembler did the job correctly and the out file has been written to disk! print("Debug session is over, output file has been written to {}". format(usableArgs.output,)) diff --git a/Documentation/CapuaReference_EN.pdf b/Documentation/CapuaReference_EN.pdf deleted file mode 100644 index bcb2023..0000000 Binary files a/Documentation/CapuaReference_EN.pdf and /dev/null differ diff --git a/Documentation/CapuaReference_FR.pdf b/Documentation/CapuaReference_FR.pdf deleted file mode 100644 index f9d15df..0000000 Binary files a/Documentation/CapuaReference_FR.pdf and /dev/null differ diff --git a/Documentation/Images/Architecture-En.png b/Documentation/Images/Architecture-En.png new file mode 100644 index 0000000..72b980f Binary files /dev/null and b/Documentation/Images/Architecture-En.png differ diff --git a/Documentation/Images/spartacus-logo.jpg b/Documentation/Images/spartacus-logo.jpg new file mode 100644 index 0000000..5f2bb9f Binary files /dev/null and b/Documentation/Images/spartacus-logo.jpg differ diff --git a/Documentation/QuickStart-EN.md b/Documentation/QuickStart-EN.md new file mode 100644 index 0000000..e3177d9 --- /dev/null +++ b/Documentation/QuickStart-EN.md @@ -0,0 +1,99 @@ +![Spartacus Logo](Images/spartacus-logo.jpg) +# Spartacus Quick Start +## Dependencies +Spartacus relies on: + +* python 3 +* python 3 tkinter + +Please install these before anything else. + +## First Step +Create a virtual hard drive file using the following command: +> python3 HardDriveCreator.py -o HD.bin + +If everything went well, the following text should be displayed on the terminal: +``` +Hard drive creation about to begin, following options will be used + output file: HD.bin +Hard drive creation done, output file has been written to HD.bin +``` + +Copy the testFiles/Hello.casm to the root of the project. +Use this command to assemble the Hello.casm file: +>python3 Assembler.py -i Hello.casm -o Hello.o + +Validate that no error message are present on the output of the assembler. +The output should look like: +``` +Assembler about to begin, following options will be used + input file: Hello.casm + output file: Hello.o +Assembler done, output file has been written to Hello.o +``` + +If everything went well, you can now use the following command to link the resulting file: +>python3 Linker.py -i Hello.o -o Hello.bin + +Validate that no error message are present on the output of the linker. +The output should look like: +``` +Linker about to begin, following options will be used + input file: ['Hello.o'] + symbols file: Hello.sym + output file: Hello.bin +Linker done, output file has been written to Hello.bin +``` + +If everything went well, you can now use the following command to launch the debugger with the Hello.bin file: +>python3 Debugger.py -i Hello.bin + +If the debugger was successfully launched, a black window should have appeared. Also, the debugger should have printed +the following information on the terminal: +``` +Debug session about to begin, following options will be used + input file: Hello.bin + symbols file: Hello.sym +Building Capua execution environment +Loading ('Hello.bin',) in memory +Done loading ('Hello.bin',) into memory +Loading symbols from file Hello.sym +Done loading symbols +Debugging session is ready to be used. Have fun! + +Next instruction to be executed: +0x40000000 : MOV #0x40000071 $S +('Hello.bin',): +``` + +If everything went well, you can put a breakpoint on the endless loop. To do so, you can use the +following command: +>break Hello.ENDLESSLOOP + +Following which you can type the following command: +>continue + +If you followed all direction, the message "Hello World!" should be displayed in the virtual display (the black window) + +You can now type the following at the command line: +>quit + +If you're here and the message is displayed, everything is working! Congratulations, you have a working Capua environment! + +- - - +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. \ No newline at end of file diff --git a/Documentation/QuickStart-FR.md b/Documentation/QuickStart-FR.md new file mode 100644 index 0000000..d23a255 --- /dev/null +++ b/Documentation/QuickStart-FR.md @@ -0,0 +1,101 @@ +![Spartacus Logo](Images/spartacus-logo.jpg) +# Guide de démarrage rapide de spartacus +## Dépendances +Spartacus dépend sur: + +* python 3 +* python 3 tkinter + +Veuillez les installer avant de poursuivre. + +## Première étape +Créer un fichier de disque rigide virtuel à l'aide de la commande suivante: +> python3 HardDriveCreator.py -o HD.bin + +Si tout a bien fonctionné, le texte suivant devrait être affiché à la console: +``` +Hard drive creation about to begin, following options will be used + output file: HD.bin +Hard drive creation done, output file has been written to HD.bin +``` + +Copier le fichier testFiles/Hello.casm vers la racine du projet. +Utiliser cette commande afin d'assembler le fichier Hello.casm: +>python3 Assembler.py -i Hello.casm -o Hello.o + +Valider qu'aucun message d'erreur n'est présent à la sortie de l'assembleur. +Le message de sortie de l'assembleur devrait ressembler à ceci: +``` +Assembler about to begin, following options will be used + input file: Hello.casm + output file: Hello.o +Assembler done, output file has been written to Hello.o +``` + +Si tout s'est bien passé, vous pouvez maintenant utiliser la commande suivante afin de procéder +à l'édition des liens sur le fichier résultant de la commande précédente: +>python3 Linker.py -i Hello.o -o Hello.bin + +Valider qu'aucun message d'erreur n'est présent à la sortie de l'éditeur de liens. +Le message de sortie de l'éditeur de liens devrait ressembler à ceci: +``` +Linker about to begin, following options will be used + input file: ['Hello.o'] + symbols file: Hello.sym + output file: Hello.bin +Linker done, output file has been written to Hello.bin +``` + +Si tout s'est bien passé, vous pouvez maintenant utiliser la commande suivante afin de lancer le débogueur avec +le fichier Hello.bin: +>python3 Debugger.py -i Hello.bin + +Si le débogueur a été lancé avec succès, une fenêtre noire devrait apparaître. +Le debogueur devrait aussi afficher ceci sur la console: +``` +Debug session about to begin, following options will be used + input file: Hello.bin + symbols file: Hello.sym +Building Capua execution environment +Loading ('Hello.bin',) in memory +Done loading ('Hello.bin',) into memory +Loading symbols from file Hello.sym +Done loading symbols +Debugging session is ready to be used. Have fun! + +Next instruction to be executed: +0x40000000 : MOV #0x40000071 $S +('Hello.bin',): +``` + +Si tout a bien fonctionné, vous pouvez mettre un point d'arrêt sur la boucle sans fin. +Pour se faire, vous pouvez utiliser la commande suivante: +>break Hello.ENDLESSLOOP + +Suivant laquelle vous pouvez tapper la commande suivante: +>continue + +Si vous avez suivi toutes les étape, le message "Hello World!" devrait être affiché à l'écran virtuelle (la fenêtre noire) + +À la ligne de commande, vous pouvez maintenant faire la commande suivante: +>quit + +Si vous êtes ici et que le message est affiché, tout fonctionne! Félicitations, vous avez un environnement Capua fonctionnel! + +- - - +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/Documentation/SpartacusReference-EN.md b/Documentation/SpartacusReference-EN.md new file mode 100644 index 0000000..87158ed --- /dev/null +++ b/Documentation/SpartacusReference-EN.md @@ -0,0 +1,1053 @@ +![Spartacus Logo](Images/spartacus-logo.jpg) +# Spartacus Reference Manual +Version 2.0 +December 2017 + +This document does not repeat the information available in the quick start guide. +Please read the quick start before reading this document. + +## Document history +* December 2018 + * Complete rewrite of the document. Current version of the VM is no longer + compatible with V1.0. The game has been removed from the project. Multiple + instructions have been added. Some language rules have + changed. + +## Introduction +The Capua environment is a virtual core designed to help new comers to assembly, and +operating system programming in general, learn the basics associated with such concepts. Capua was +developed after witnessing a diminution in the abilities of recent graduated students to evolve in +low level programming environment. Therefore, Capua functionality is stripped down to bare +minimum so users can benefit from the simplicity of its design in a learning context. + +### Note +Through this document, every reference to "hardware", unless specified otherwise should +be understood as "virtual hardware". + +## Capua versus Spartacus +Spartacus is the whole project. Capua is the virtual environment. + +## Important changes from version 1.0 to version 2.0 +* Capua now supports software and hardware interrupts +* Multiple instructions have been added: + * **ACTI, DACTI, HIRET, INT, SFSTOR, SIVR** + * These allow for the minimum required for operating system development +* Capua now have 8 general purpose registers (GPRs) + * This addition forced a change in machine code. Code assembled and linked + with previous version of the tool chain will not run properly on Capua V. 2.0. +* Support for multiple hardware elements was added: + * Hard drive, Interrupt clock and Terminal. +* Every hardware elements that was highly tied with the game environment was removed. +* The game environment has been removed from the project. + +## Capua +Capua is a load/store architecture. This means that only load (**MEMR**) and store (**MEMW**) +instructions can do memory access. Stack related instructions also have access to +memory but in a much stricter way. +## Registers +Capua now have 8 GPRs named from **A** to **G** and then **S**. The **S** +GPR is intended to be used as a stack register. All registers are 32 bits wide. The +instruction pointer is named **I** and is not user accessible. A flag register named **FLAGS** +is used by the core but is also no user accessible. +## Memory +At the current time, no memory management unit or equivalent device is available to the +Capua environment. We are working on this and one should be available in a soon to be +released version of the VM. +Physical memory is, therefore, always directly accessed. Start of the user memory is +defined in the **Configuration/Configuration.py** file. By default, this value is +0x40000000. By default, the VM has 1 MB of memory. These defaults values can be changed. +However, it is not recommended to do so. +## Memory mapped devices +On capua, all hardware is memory mapped. Every single hardware piece is accessible at +addresses located bellow the start of the memory (0x40000000). Presence of memory mapped +hardware is one of the reasons why we do not recommend changing memory values in the +configuration file. +## General architecture +As of December 2017 Capua architecture looks like: +![Capua Architecture](Images/Architecture-En.png) + +As you can see, architecture is very simple but resembles that of a real computer. + +# Instruction set +The syntax for the Capua assembly language is organised in a source -> destination fashion. +When 2 elements, either immediate or registers, are present in an instruction, +the first is always the source and the second one is always the destination. Following +is the full instruction listing. Examples of various forms an instruction can have are also +presented. +###### Registers, immediate values and memory references +The assembler currently requires the programmer to prefix any registers with the **$** sign. +Every immediate value has to be prefixed with the **#** sign. Use of memory reference (labels) +within an instruction is permitted where immediate values can be used. It does not require +any prefix. +### ACTI +This instruction will activate the interrupt handling mechanism. After it is issued, +INT instruction and hardware interrupts can be handled. The handling of the interrupts +is described in the appropriate section bellow. The ACTI instruction does not use +any registers or immediate values. +>**ACTI** +### ADD +This instruction allows for integer addition. The +following shows the different variants of the +add instruction. +>**ADD #0xFF $A** + +>**ADD $B $A** +### AND +The AND instruction is used for binary operation. It +is a binary comparison and follows normal AND +truth table: + +* 1 and 1 = 1 +* 1 and 0 = 0 +* 0 and 1 = 0 +* 0 and 0 = 0 + +Obviously this verification is extended to the full +length of the compared elements (32 bits). +>**AND #0xFF $A** + +>**AND $B $A** +### CALL +You must fulfill the instruction prerequisite to use +the CALL instruction. Doing otherwise will result in +bad things, usually a memory error. Before using +this instruction, you have to make sure that the +S register points to a valid and +available region of memory that can be used as a stack. The call instruction +will cause the address of the instruction following +the CALL instruction to be pushed on top of the +stack in order for that one to be used as return +address. +>**CALL #0x40000010** + +>**CALL $A** + +>**CALL testFunction** +### CMP +The compare instruction is the only one that allows +for FLAGS register modification. The FLAGS register +is, at current time, only 3 bits wide. The top bit +( 0b100 ) will be set when the source element of the +comparison is equal to the destination element. +The middle bit ( 0b010 ) is set when source is lower +than destination and the rightmost bit ( 0b001 ) is set +when source is higher than destination. +>**CMP #0xFF $A** + +>**CMP $A $B** +### DACTI +The DACTI instruction is the inverse of the ACTI instruction. It will +disable the interrupt handling mechanism. It also does not use any +registers of immediate values. +>**DACTI** +### DIV +The div instruction allows for division. The source +element will be divided by the destination element. +The quotient of the division is placed in register A and +the remainder of the division is placed in register B. +>**DIV $C $B** +### HIRET +The HIRET instruction means Hardware Interrupt Return. It is to be used as a return +instruction for the hardware interrupt handling routine. It is not to be used as part +of software interrupt handling routine. When issued, it will return to the routine +that was previously interrupted by the hardware while also making +sure that the flags is reset to the value it held before the hardware interrupt. HIRET +does not use any registers of immediate values. +>**HIRET** +### INT +The INT instruction is used to generate a software interrupt. When issued, if interrupts are +activated, the core will jump to the appropriate handling routine based on the number of +the interrupt. Please see the interrupt handling section for more details on interrupt +handling. The instruction is available in two forms. It can either be used with an +immediate value or with a register +>**INT #4** + +>**INT $A** +### JMP +The jump instruction allows to skip parts of the +code either unconditionally or conditionally by +looking at the FLAGS register value. This instruction +is special in the sense that it use a flag indicator to +allow the user to select the conditions where the +jump should be taken. Next is a listing of the +possible conditions: + +* <> Jump is always taken. +* or Jump is taken if 0b100 is set (E Flag). +* Jump is taken if 0b010 is set (L Flag). +* Jump is taken if 0b001 is set (H Flag). + +These flags indicators can be combined to form +complex conditions. For example, the +indicator would allow the jump to happen if the E +flag is set or if the L flag is set. Note that the +immediate or register used by this instruction must +hold a valid memory address from where code can be +fetched. +>**JMP #0x40000010** + +>**JMP $B** + +>**JMP <\> testLoop** +### JMPR +The JMPR instruction is identical to the JMP +instruction with a major difference. The immediate +or register used by the JMPR instruction must hold a +relative offset to the instruction pointer "I" where the +jump should result. For example, using the +immediate value #0xFF will cause the execution +unit to start fetching instruction 0xFF bytes after the +JMPR instruction. +>**JMPR #0x10** + +>**JMPR $B** +### MEMR +The MEMR instruction allows for memory read +operation. MEMR uses a width indicator that allows +the user to read 1 to 4 bytes from memory into a +register. The source can either be a register or an +immediate value but must be a valid memory +address. The width indicator is defined by using the +\[ and \] characters. +>**MEMR \[4\] #0x40000000 $B** + +>**MEMR \[4\] $A $B** +### MEMW +The MEMW instruction allows for memory write +operation. Like the MEMR instruction, it uses a width +indicator. Because of its nature, the MEMW +instruction is available in more form than the MEMR +instruction. The destination can be a register or an +immediate but must be a valid memory address. +>**MEMW \[4\] #0xFF #0x40000000** + +>**MEMW \[4\] #0xFF $A** +### MOV +The MOV instruction allows for data displacement +between register or for loading a register with an +immediate value. This instruction could be use, by +example, to set the stack pointer S to a valid +memory region in order for the stack to be usable. +>**MOV #0x40000200 $S** + +>**MOV $A $B** +### MUL +The MUL instruction allows for integer multiplication +between two registers. It is not possible to use the +MUL instruction with an immediate value. Since +multiplication can result in numbers that are bigger +than 32 bits, the B register hold the higher 32 bits +and the A register holds the lower 32 bits of the +resulting number. The number should read as B:A +after the multiplication. +>**MUL $A $B** +### NOP +The NOP instruction is the no operation instruction. +It does nothing. Since Capua does not need to be 4 +bytes aligned, the NOP instruction is typically +useless. However, it can be use to fil the memory at +boot time in order to ease development. +>**NOP** +### NOT +The NOT instruction will inverse the bits of a +register. For example, if register A is equal to 0x01 +before the NOT instruction, it will be equal to +0xFFFFFFFE after the NOT instruction. The not +instruction can, obviously, only be used with a +register. +>**NOT $A** +### OR +The OR instruction is a bitwise or operation. It +works following the Boolean logic or rules. Here is +its truth table: + +* 1 or 1 = 1 +* 0 or 1 = 1 +* 1 or 0 = 1 +* 0 or 0 = 0 + +It will impact all the bits in a register. +>**OR #0xFF $A** + +>**OR $A $B** +### POP +The POP instruction removes 32 bits from the stack +and decreases the stack pointer 4 bytes back. The +data that was on the top of the stack will be +available in the register specified by the POP +instruction. For the POP instruction to be safely +used, the stack pointer S must be set to a valid +memory address. +>**POP $A** +### PUSH +The PUSH instruction will add a 32 bits value on the +top of the stack and increase the stack pointer S 4 +bytes forward. In order for the PUSH instruction to +be safely used, the stack pointer S must be set to a +valid memory address before using the PUSH +instruction. +>**PUSH #0xFF** + +>**PUSH $A** +### RET +The return instruction is typically used to return +back from a call. It will take the element on the top +of the stack and set the instruction pointer I value to +it. In order to use the RET instruction, the caller has +to make sure that the top of the stack value is a +pointer to a region of memory where code can be +fetched by the execution unit. +>**RET** +### SFSTOR +The SFSTOR instruction means Safe Store. The "safe" part means safe as in thread safe. +IT will take the value present in the source operand (either imm or reg) and will copy it at +address in $A if condition is meet by doing a comparison between the data held at memory address +defined in $A. The condition used are the same as for the JMP instruction. The instruction will also +change the flags according to the result of the comparison. The order of the comparison goes as +(Immediate or Register) against data pointed to by register A. Not the other way around. +>**SFSTOR #1** + +>**SFSTOR $B** + +This instruction aims at making it possible for a programmer to write thread synchronisation code like mutexes. +### SHL +The SHL instruction mnemonic stand for SHift +Left. It allows its user to shift the value of a register +X bits to the left where X is a number from 1 to 8. +The exceeding values are simply lost. As an +example, consider the value 0x80000001 . Applying +a one-bit shift left to this value will result in that +value being transformed to 0x00000002. +>**SHL #0x01 $A** + +>**SHL $B $A** +### SHR +The SHR instruction is the same as SHL instruction +except that the shift happens to the right. Therefore +a one-bit right shift applied to the value +0x80000001 will result in the value 0x40000000 . +>**SHR #0x01 $A** + +>**SHR $B $A** +### SIVR +This instruction means Set Interrupt Vector Register. This is used to configure the interrupt vector pointer. +It must be configured to point to a valid vector of handling routines addresses. It can only be used +with a register. +>**SIVR $A** + +The following code snippet shows an example of setting the IVR +``` +.global start: + +start: +MOV end $S ; Stack grows up, setting S to the end is generally safer + ; The stack needs to be configure for interrupt handling to work +MOV vector $A +SIVR $A ; Set the interrupt vector to "vector" +ACTI ; Interrupt handling activation + +loop: + NOP + JMP <> loop + +clockHandler: ; All hardware interrupts are mapped to this empty handler +keyboardHandler: +hardDriveReadHandler: +hardDriveWriteHandler: + HIRET + + +vector: ; The vector is built using the dataMemRef marker +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler +end: +``` +### SUB +The SUB instruction allows for subtracting values. It +is important to understand that the operation work +like this: +* destination = destination - source + +Not the other way around. Beware. It is also +important to know that the SUB instruction only +considers 32 bits signed, in the two’s complement +format, integer numbers. +>**SUB #0x01 $A** + +>**SUB $B $A** +### XOR +The XOR instruction is a bitwise operation, just like +the AND and OR instruction. It follows the normal +XOR logic and has the following truth table: + +* 1 xor 1 = 0 +* 1 xor 0 = 1 +* 0 xor 1 = 1 +* 0 xor 0 = 0 + +It can be used to set a register value to 0. +>**XOR #0x01 $A** + +>**XOR $B $A** + +# Tool Chain +Capua, being an architecture, needs its own +assembler. For this project, 4 tools were developed +in order to help software development and testing +of the platform. + +* Assembler.py +* Linker.py +* Debugger.py +* HardDriverCreator.py + +The following sections explain each tool. Please +keep in mind that these tools were +developed, at first, to allow for Capua to be tested. +The second goal of these tools was to allow for +software development. Therefore, they do +present some bugs and, as a developer, you have to +be careful to use these tools exactly as this text +explains their use. Know that these bugs are being +addressed. We are planning on a re-write +of these. + +## Compiler +We do not currently have a compiler. This is one of the major area where +open source contribution would be more than welcome! + +## Assembler.py +##### Overview +Capua has its own assembler. It’s easy to use. The implementation is fairly +straightforward. Please remember +that the current version of the assembler has been +written as a testing tool for Capua. Be extra careful to +fully respect the syntax described here since error +messages might not always be easy to understand. +Also, in it’s current state, the assembler is very +“typo” sensitive. Every part of an instruction need +to be separated by a white space. This include end +of line comment: +>**MOV $A $B;This is not good enough** + +>**MOV $A $B ;This is fine** + +Obviously many of these problems are easy +fixes. Feel free to jump in and contribute some +code. + +When running the assembler, the input file is +transformed into a .o file. Please note that, the +extension has been chosen because of the historic +signification of a .o file. Capua is not actually using the .o file format. The file format used by the +Capua tool chain has been made (designed would +be an overstatement here) with the intent of +being able to link multiple files together in order to +form a flat binary file. The current format is also +very simple to understand and is a mix of XML and +binary data. This format is not perfect and will change in the future. +A full description of this format is available lower in this document. + +##### Writing assembly +This part concentrate not on the code but on assembler +directives. These directives are to be used when writing code. + +* "symbolName:" + * An arbitrary name, by itself, on a line + followed by a “:” character indicates a + symbol. The assembler will add this in the + symbol list and code will be able to + reference symbolName anywhere an + immediate could be used. Note that to use + the symbol name in code No “:” is + required after the symbol name when using + the name in code.For example: + **MOV stackAddress $A** + Would result in the linked address of + :stackAddress being put as an immediate + value in the mov instruction displayed. That + could also be used in loops: + **JMP <> loopStart** +* ".global symbolName" + * Will allow the assembler to add a symbol to + the external symbols list. This ultimately allows the linker to use this symbol + for linking with external files. +* ";" + * The “;” character, like in many other + assembly language, indicates a comment. + These can either be on a line of their own or + at the end of a line, after an instruction. + Please note that, in case a comment is put + after an instruction, a space must separate + the end of the instruction from the + beginning of the comment (the “;” + character). +* ".dataAlpha" + * This can be used anywhere as long as it sits + on a line of it’s own. This directive is + followed by a white space and by free + formed text. The text does not need to be + quoted. In fact, it MUST NOT be quoted. + The string ends at the end of the line. The + assembler will add a 0x00 termination + character at the end of the string at + assembling time. Usage example + testString: + **.dataAlpha This is a test string** + *Please note that no comment can follow this + line.* +* ".dataMemRef" + * This is similar to .dataNumeric except that it + allows the programmer to specify a memory reference. + When the code is linked, the memory reference will + be replace with the address of the said reference. + This is mainly used to create interrupt vectors. + This is used like this: **.dataMemref randomLabel** +* ".dataNumeric" + * This is the same as .dataAlpha except that it + allows the programmer to use 32 bits + numeric values. Usage example: + testInt: + .dataNumeric 0xFFFFFFFF +* "$" + * Is the register prefix. Every register, when + used in an assembly instruction, must be + preceded by the “$” character. +* "#" + * Is the immediate prefix. Every immediate + value (except when using the .dataNumeric directive) must be + preceded by the “#” character. + Multiple variants are possible for immediate + values: + \#0xFF + \#255 + \#-1 + \#0b11111111 + +##### Short program example +The following table simply shows a working +program that will calculate the length of a string. +``` +; File Length.casm +; This will calculate the length of testString +.global start +start: + JMP <> stackSetup ;Jump over the string + +testString: +.dataAlpha This is a test string + +stackSetup: + MOV stack $S ;Stack is now usable +codeStart: + PUSH testString + CALL strlen + SUB #0x4 $S ;stack reset +end: + +;Following is the length calculation +;strlen(stringPointer) +strlen: + MOV $S $A + SUB #0x4 $A ;Calculate parameter offset + MEMR [4] $A $A ;Get parameter in register A + MOV $A $C ;Keep pointer to string start +lenover: + MEMR [1] $A $B + CMP #0x00 $B ;are we at the end of the string? + JMP gotlen + ADD #0x1 $A + JMP <> lenover ;not at the end, jump back +gotlen: + SUB $C $A ;A will hold the len of the string at this point. + RET ;return value in register A + +; Stack is at the end of the program. +; No risk of overwriting the program +stack: +``` + +In order to assemble this file, one would use the following command: +> python3 Assembler.py -i strlen.casm -o strlen.o + +You can get help about the assembler usage by +executing the assembler with the “-h” option. + +## Linker.py +##### Overview +Capua also has its own linker. This linker is +intended to be used to assist in the creation of flat +binary file. The linker can link multiple files +together. Since the binary files produced by the +linker are meant to be used “bare metal”, no +dynamic linking is available. All addresses, after +linking, will be hardcoded into the resulting binary +file. This means that the linker needs to know, at +linking time, the memory address where the binary +is to be loaded in memory. Not providing the load +address will result in the binary being linked to be +loaded at the MEMORY_START_AT address +(default is 0x40000000). This is fine for testing +purpose since the execution unit starts fetching +instructions at that address. +##### Beware! +When linking multiple files together, the order in +which you tell the linker to input the files IS OF +MAJOR IMPORTANCE. The files will be put into +the final binary in the same order as you input them +into the linker. +##### Note about symbols +The linker will output a “ .sym ” file in the same +folder as the final binary. That file is simply a +symbol and address listing. This file, if available, +will be loaded when you run the binary in the +debugger and will allow you to use symbol names +instead of memory address when executing the +binary in the context of the debugger. Note that all +the symbols present in that file have been modified +with the name of their origin file as prefix. The +symbol name themselves are also transformed to +upper case. +##### Usage +In order to link the .o file previously generated, one would use the following command: +> python3 Linker.py -i strlen.o -o strlen.bin + +In the case where one would like to link multiple files together, multiple +.o files can be added to the input files. Like: +> python3 Linker.py -i main.o subFile.o -o main.bin + +## Debugger.py +The debugger is the programmer interface into the VM. Launching the debugger +is the same as launching the VM. +The debugger is really simple. It has many of the +basic features that other debuggers offer. At current +time, three big limitations exist. The first is that you +can’t modify a register value or an in memory value +through the debugger. You can also not “step over” +a function call easily. To do so, you would have to +put a break point at the return of the function call. +The last one is that you can’t simply “reload” the +program without relaunching the debugger. All of +these features are noted and will be added at some +point in the future (feel free to contribute code!). Using the debugger is simple +and very straightforward. Simply run the debugger +with the “-h” option to learn how to launch it. Once +launched, the debugger will break right at the +beginning of your binary file. You can access the +debugger help menu by typing “h” or “help” at the +debugger prompt. + +In order to launch the previously linked file in the debugger, one would use +the following command: +> python3 Debugger.py -i main.bin + +##### Important note about virtual boot +If the debugger is launched without any parameters. It will launch using the firmware code +present in CapuaEnvironment/firmware.bin. In order for this to work, one need to assemble and link +CapuaEnvironment/firmware.casm into CapuaEnvironment/firmware.bin. This firmware will then attempt +a disk validation. Should the disk validation succeed, it will load the first sector of the disk +at MEMORY_START_AT (default is 0x40000000) and will transfer the execution to this newly loaded code. +This aims at simulating a boot process. The firmware code can be inspected for more details. + +## HardDriveCreator.py +The hard drive creator tool is nothing more than a helper tool that will generate an +empty binary file of the proper length for the VM. It will check the VM configuration +and generate the file according to hard drive size configured within. Default configuration +is for 1MB. +In order to work properly, the VM needs a HD.bin (hard drive file) to be located +at the root of the project directory. You can generate this file with the following +command: +> python3 HardDriveCreator.py -o HD.bin + +The absence of the HD.bin at the root of the project will cause a failure when +launching the VM (either with or without the firmware). + +# Interrupts handling in Capua +Capua allows for interrupts to be handled from both hardware and software source. +In order for interruption to be handled, they first need to be enabled. At boot time, +interrupt handling is disabled. One can enable interrupt handling by using the following +instruction +> ACTI + +Inversely, interrupt handling can be disabled by using +> DACTI + +On Capua, interrupt handling is done with the help of the Interrupt Vector Register (IVR). +The IVR has to be set with a pointer to a vector of 32 bits pointers to interruption handling +routine. Every hardware interrupt is associated with a number (software interrupt specify their own). +When a hardware interrupt occurs, interrupt handling becomes disabled and execution resumes +at the interrupt handling routine corresponding an interrupt number. This is almost the same +for software interrupts. The handling of interruption is not disabled when handling +a software interrupt. This is so the system can receive hardware interrupts while handling +a software interrupt. The handling routine is selected from the interrupt vector following this formula: +``` +IN is the Interrupt Number +Vector is Address of the interrupt vector +Routine is the address where the address of the handling routine is found for an interrupt number + +Routine = Vector + (IN * 4) +``` + +It is of the utmost importance that the programmer understands that, while handling +an hardware interrupt, all interrupts are disabled on the system. Most device on Capua will cache +interrupts and wait in order to be able to deliver these. However, this is not guaranteed. +Some interrupts could be lost if the code stays for an extended period of time +within an interrupt handling routine. At the end of the routine, in the case of a hardware interrupt +handling routine, a special return instruction is used: +>HIRET + +The HIRET instruction does a little more than just return. It also reactivates interrupts handling +on the core and make sure that the flags are reset to the same value they were before the handling +of the interruption begun. HIRET is not to be used to return from software interrupt handling routines. + +Returning from a software interrupt handling routine is the same as returning from a function. Simply +use the RET instruction. +##### Interrupt configuration +On capua, as of now, only the 4 first interrupts vectors are used. However, vectors 0 to 31 should +all be considered reserved. Software interrupts handler should therefore not use them. Here is the +current interrupt mapping: + +* 0 is mapped to INTERRUPT_CLOCK + * Interrupt 0 is generated by the interrupt clock at the configured interval +* 1 Is mapped to INTERRUPT_KEYBOARD + * Interrupt 1 is generated by the terminal keyboard whenever a key is pressed +* 2 Is mapped to INTERRUPT_HARD_DRIVE_DONE_READ + * Interrupt 2 is generated by the hard drive whenever a read operation is over +* 3 Is mapped to INTERRUPT_HARD_DRIVE_DONE_WRITE + * Interrupt 3 is generated by the hard drive whenever a write operation is over + + +###### Important note: +One might not understand why he/she would go to the extend of setting up interrupt +handling for software. As of now, there are no difference, or close to no difference between +software interrupts and function call. However, please know that, support for virtual memory +and multiple level of privilege is on Capua development road map. Therefore, it is +currently recommended to use the interruption facilities for everything that would normally +be provided to the user from the kernel of an operating system. This will hopefully +prevent future code breakage. + +The following code snippet shows how to handle both software and hardware interrupts. It also +show how to setup the code in order to be able to handle interrupts. +``` +; This example shows how multiple hardware interrupts +; can be mapped to the same code. It also show how to +; properly handle software interrupts and setup the +; IVR +.global start: + +start: + MOV end $S ; Stack grows up, setting S to the end is generally safer + ; The stack needs to be configure for interrupt handling to work + MOV vector $A + SIVR $A ; Set the interrupt vector to "vector" + ACTI ; Interrupt handling activation + int #4 ; Software interrupt #4 + ; Since IVR is set to vector, the routine used when int #4 is executed will be + ; determined by: + ; routine = vector + (4 * 4) + ; Since each entries are 4 bytes long + ; routine = testHandler + +loop: + NOP + JMP <> loop + +clockHandler: ; All hardware interrupts are mapped to this empty handler +keyboardHandler: +hardDriveReadHandler: +hardDriveWriteHandler: + HIRET + +testHandler: ; Just a demonstrative software handler + MOV #0xFFFFFFFF $A + MOV #0xAAAAAAAA $B + RET + +vector: ; The vector is built using the dataMemRef marker +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler +.dataMemRef testHandler +end: +``` + +# Memory Mapped Hardware +The virtual machine offers support for memory +mapped hardware. Memory mapped hardware is +accessible at specific memory addresses. Different +addresses have different meaning. The way each device +behave is specific to a single device. + +## Clock +``` +Mapping address: 0x20000100 +Allowed operation(s): Read +``` +The clock aims at providing the VM user with a source of entropy. It is not meant +to provide time to the user. Reading 4 bytes at mapped address will provide a value +based on the following python code: +>int((time.time() * 10000000)) & 0xFFFFFFFF + +The following snippet shows how to access the clock: +``` +MOV #0x20000100 $A +MEMR [4] $A $B ; The clock value will be in register $B +``` + +## Interrupt Clock +``` +Mapping address: 0x20000300 +Allowed operation(s): Read/Write +``` +When writing 4 bytes at mapped address, the Interrupt Clock will start generating +clock hardware interrupts at a frequency (milliseconds) set by the 4 bytes value written at the +mapped address. In order for the core to receive the generated interrupts, +interrupts must have been activated on the core. No interrupts are generated (even +if interrupts are activated) if no write operations were made at the mapped address. + +The following snippet shows how to set the interrupt clock frequency: +``` +MOV #0x20000300 $A +MEMW [4] #0xFF $A ; Will set the frequency to 255 ms +``` +## Hard Drive +``` +Mapping address: 0x20000400 +Allowed operation(s): Read/Write +``` +One should note an important element. The hard drive it self is not mapped at the given +mapped address. The hard drive controller is. It is the controller +that is to be used to access the hard drive. + +A specific structure needs to be writen at mapped address for access to the hard drive +to be possible. For every access operation, the structure needs to be set properly. + +Hard drive access operation structure: + +* 0x20000400 = 0x0 for a read operation or 0x01 for a write operation +* 0x20000404 = LBA to be written or read (512 byte offset number) +* 0x20000408 = Memory Address to write TO or to read FROM +* 0x2000040C = Trigger memory action = When this is set to 1, the action is triggered + * This needs to be manually reset to 0 in between hard drive access operation otherwise + any new write to the mapped address structure will cause an operation on the disk. + +Following an operation on the drive, the corresponding interrupt will be generated by the +drive and sent to the core. + +The following code snippet shows how to read a block of data from the drive: +``` +; This will test the correct working order of the hard drive read operation +.global start + +start: + MOV readBuffer $S + ADD #1024 $S ; Set the stack (needs to be set to handle interrupts + MOV vector $A + SIVR $A ; Set the Interrupt vector + ACTI ; activates the interrupts + + MOV #0x20000400 $A + MEMW [4] #0x0 $A ; Set read action + ADD #4 $A + MEMW [4] #0x0 $A ; Set LBA + ADD #4 $A + MEMW [4] readBuffer $A ; Set destination buffer + ADD #4 $A + MEMW [4] #0x01 $A ; Set the trigger + +waitLoop: + NOP + JMP <> waitLoop + +clockHandler: + HIRET +keyboardHandler: + HIRET +hardDriveReadHandler: + ; Do nothing in the handler, this is just an example + HIRET + +hardDriveWriteHandler: + HIRET + +vector: +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler + +readBuffer: +end: +``` + +Writing to the drive is very similar except that one needs to trigger the write operation. + +## Terminal +``` +Mapping address: 0x20001000 (Display), 0x20001800 (Keyboard) +Allowed operation(s): Read/Write +``` +The terminal is a special device. It allows output (on the display) and input (from the keyboard). +Therefore, it is essentially 2 devices in 1. +##### Terminal - display +The display is 80x25 characters. Character 0 (x=0, y=0, or top left corner) is mapped at address 0x20001000. +Character 1 (x=1, y=0) is mapped at address 0x20001001. The memory is fully linear. This +means that character 0 from line 1 (x=0, y=1) is the 80th character. This is mapped at +address 0x20001000 + 80. In order to display a character to the screen one needs to +write the ascii code value for a character to the appropriated address. + +The following snippet shows an example of code that will display "ABCD" at the +top left corner of the screen. +``` +; This will print ABCD on the screen +.global codeStart + +codeStart: + MOV #0x20001000 $A ; Display address + MOV #0x41424344 $B ; ABCD ascii code + MEMW [4] $B $A +``` +##### Terminal - keyboard +The keyboard requires the interrupts to be activated and configured in order to work. +A proper keyboard handler needs to be provided. When a user press a key on the +keyboard, a keyboard interrupt is generated and control is transferred to the keyboard +handler. At that point, the scan code (beware, scan code, not character) for the key +that was pressed on the keyboard will be available for read at address 0x20001800. +In the case where an interrupt for a key press can't be delivered to the core (if the core +is already handling another hardware interrupt for example), the +keyboard has a buffer for 20 key press and will try delivering the interrupt later as long +as its 20 key buffer is not full. When the buffer is full, the oldest key strokes will be flushed out. + +The following snippet show an example of keyboard read and interrupt handling. +``` +; This will print scan code to the top left of the screen +; if a scan code does not happen to map to a printable +; character, nothing will be visible. +.global start + +start: + MOV end $S + MOV vector $A + SIVR $A + ACTI + +loop: + NOP + JMP <> loop + +clockHandler: + HIRET + +keyboardHandler: + MOV #0x20001800 $A + MEMR [1] $A $B + MOV #0x20001000 $A + MEMW [1] $B $A + HIRET + +vector: +.dataMemRef clockHandler +.dataMemRef keyboardHandler +end: +``` +## Capua ".o" file format +The Capua file format is very simple. For obvious reasons, there are bugs +associated with the file format. This will be addressed when we update this +format. For now, this is usable in most cases. + +Here is the general form: + +``` + + + + + ... + + + + + ... + +... + +``` + +The following explains the various parts of the file format. + +* AssemblySize + * This tag holds the projected size of the final + linked version of this file. The size is kept big + endian binary encoded inside the tag. The size + calculation was put in the assembler since it was + easier and faster to put it there. Also, the info is + readily available for the linker when this one + needs to calculate file wide address across + multiple files that needs to be linked together. +* ExternalSymbols + * This tag holds a list of tags (refName/refAdd + couples). The symbols present in the + ExternalSymbols list are seen as “global” and, + therefore, are available to the linker when linking + multiple object together. +* InternalSymbols + * This tag is the same as ExternalSymbols except + that the symbols listed in this tag are not available + to the linker when linking multiple files together. + This was originally made that way to prevent + name collision on the global (external) scale at + linking time. The linking process later add the + source file name to the symbols so that all + symbols are available (internal and external) when + using the debugger while still avoiding collision. +* refName + * A refName tag must be inside of an + ExternalSymbols or an InternalSymbols tag. It + also has to be followed by a refAdd tag. The + refName simply holds a text version of the + symbol name. The symbol name is determined + by memory reference/naming in the assembly + code written by the programmer. +* refAdd + * This follows a refName tag and simply + indicate the offset where the symbol can be found + from the 0 address (relative to the beginning of + the current file). Note that the offset is relative to + a fully linked file. Not to an object file. That + address eventually replace the symbol name when + the file is linked +* Text + * The text tag holds the assembled binary of the + object file. A close look will reveal the presence of + symbols name inside the text tag. These are + replaced at linking time. The symbols are present + in the text section like: “:SymbolName:”. This is + not a perfect (not even a good one) solution but + it was fast. (Always keep in mind these tools were + originally written to test the execution unit, not to + write usable code with them) + +- - - +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + + + diff --git a/Documentation/SpartacusReference-FR.md b/Documentation/SpartacusReference-FR.md new file mode 100644 index 0000000..70b751c --- /dev/null +++ b/Documentation/SpartacusReference-FR.md @@ -0,0 +1,1111 @@ +![Spartacus Logo](Images/spartacus-logo.jpg) +# Manuel de Référence Spartacus +Version 2.0 +Décembre 2017 + +Ce document ne répète pas les informations disponibles dans le guide de démarrage rapide. +Veuillez lire le guide de démarrage rapide avant de lire ce document. + +## Historique du document +* Décembre 2018 + * Réécriture complète du document. La version actuelle de la machine virtuelle n'est pas + compatible avec la première version. L'environnement de jeu a été retiré du projet. Plusieurs + instructions ont été ajoutées. Le language a aussi été modifié. + +## Introduction +Capua est un coeur virtuel conçu pour aider les nouveaux venus à la programmation assembleur et +la programmation de système d'exploitation en général. Capua a été +développé après avoir constaté une diminution des connaissances relativement +à la programmation de bas niveau. Ainsi, les fonctionnalités de Capua sont réduites au +minimum afin que les utilisateurs puissent bénéficier de la simplicité de sa conception +dans un contexte d'apprentissage. + +### Note +À travers ce document, toute référence faite au "matériel", sauf si spécifié, devrait être +interprétée comme "matériel virtuel". + +## Capua versus Spartacus +Spartacus, c'est le projet entier. Capua, c'est l'environnement virtuel. + +## Changements importants de la version 1.0 à la version 2.0 +* Capua supporte maintenant les intérruptions matérielles et logicielles +* Plusieurs instructions ont été ajoutées: + * **ACTI, DACTI, HIRET, INT, SFSTOR, SIVR** + * Celles-ci offrent les fonctionnalités minimalles requises pour le développement de système d'exploitation. +* Capua comporte maintenant 8 registres à usage général (GPRs): + * Cet ajout a forcé un changement au niveau du code machine. Le code construit + à l'aide des anciens outils ne fonctionnera pas correctement sur Capua V. 2.0. +* Le support pour plusieurs pièces de matériel a été ajouté: + * Disque rigide, horloge d'intérruption et Terminal. +* Les éléments matériels qui étaient fortement liés à l'environnement de jeu ont été retirés. +* L'environnement de jeu a été retiré du projet. + +## Capua + +Capua est une architecture de type load/store +(charger/stocker). Ainsi, seules les instructions de +chargement (**MEMR**) et de stockage (**MEMW**) peuvent +accéder à la mémoire. Les instructions relatives aux piles ont également accès à +la mémoire, mais de façon beaucoup plus restreinte. +## Registres +Capua possède maintenant 8 GPRs allant de **A** à **G** et puis **S**. Le registre S +(pour Stack) est le pointeur de pile. Tous les registres possèdent 32 bits. +Le registre d’adresse d’instruction est nommé **I** et +n’est pas accessible aux utilisateurs. Le registre des +drapeaux est nommé **FLAGS** et n’est pas non plus +accessible aux utilisateurs. + +## Mémoire +À l'heure actuelle, aucun mécanisme de gestion de la mémoire n'est disponible. +C'est un des éléments sur lesquels nous travaillons. Un tel système devrait être +disponible dans une version ultérieure. +La mémoire physique est donc accédée directement. Le début de la mémoire utilisateur +est défini à l'intérieur du fichier **Configuration/Configuration.py**. Par défault, +cette valeur est 0x40000000. Aussi par défaut, la machine virtuelle possède 1Mo de mémoire. +Ces valeurs peuvent être modifiées mais il est recommandé de conserver les valeurs par défaut. +## Dispositifs mappés en mémoire +Sur capua, l'ensemble du matériel est mappé en mémoire. Chaque élément peut être +accédé à partir d'une address situé sous l'addresse de début de la mémoire (0x40000000). +La présence de ce matériel est une des raison pour lesquelles ils n'est pas recommandé +de modifier la configuration de la mémoire. +## Architecture générale +En date de décembre 2017 l'architecture Capua ressemble à ceci: +![Capua Architecture](Images/Architecture-En.png) + +Comme vous pouvez le voir, l'architecture est très simple mais semblable à celle d'un +ordinateur réel. + +# Jeu d'instructions +La syntaxe du language assembleur Capua est organisée sur le modèle source -> destination. +Quand deux éléments sont présents dans une instruction, valeur immédiate ou registre, le +premier élément est toujours la source alors que le deuxième est toujours la destination. +L'ensemble du jeu d'instructions du language Capua sera maintenant présenté. +###### Régistres, valeurs immédiate et références mémoires +L'assembleur demande que le programmeur utilise le préfixe **$** devant un registre. Le préfixe +**#** doit quant à lui être utilisé devant une valeur immédiate. L'utilisation d'un libéllé +de référence mémoire est permise à l'intérieur des instructions utilisant des valeurs immédiates. +Il n'est cependant pas requis de leur appliquer de préfixe. +### ACTI +Cette instruction active les mécanisme de gestion des intérruptions. Après que cette +instruction ait été exécutée, l'instruction INT ainsi que les intérruptions matérielles +peuvent être transmises à l'unité d'exécution. La gestion des intérruptions est décrite +à la section appropriée. L'instruction ACTI n'utilise aucun registre ou valeur immédiate. +>**ACTI** +### ADD +Cette instruction permet l’addition de nombres +entiers. Les diverses +variantes de l’instruction d’addition sont présentées ci-dessous. +>**ADD #0xFF $A** + +>**ADD $B $A** +### AND +L’instruction AND est utilisée pour les opérations +binaires. Il s’agit d’une comparaison binaire qui suit +la table de vérité AND normale: + +* 1 and 1 = 1 +* 1 and 0 = 0 +* 0 and 1 = 0 +* 0 and 0 = 0 + +Cette vérification est étendue à toute +la longueur des éléments comparés (32 bits) +>**AND #0xFF $A** + +>**AND $B $A** +### CALL +Il est indispensable de respecter les conditions +préalables relatives à cette instruction pour utiliser +CALL . Dans le cas contraire, des +problèmes surviendront (une erreur de mémoire, en +général). Avant d’utiliser cette instruction, vous +devez vous assurer que le pointeur de pile S +pointe dans une zone de la mémoire +valide et disponible. L’instruction CALL entraînera +une copie de l’adresse de l'instruction suivante (l'instruction suivant le call) +en haut de la pile de manière à ce que cette adresse soit utilisée +comme adresse de retour. +>**CALL #0x40000010** + +>**CALL $A** + +>**CALL testFunction** +### CMP +L’instruction de comparaison est la seule qui permet +la modification du registre FLAGS . À l’heure actuelle, +le registre FLAGS possède uniquement trois bits. Le bit +supérieur (0b100) est activé lorsque l’élément +source de la comparaison correspondra à l’élément +de destination. Le bit central (0b010) est activé +lorsque la source est inférieure à la destination et le +bit le plus à droite (0b001) est activé lorsque la +source est supérieure à la destination. +>**CMP #0xFF $A** + +>**CMP $A $B** +### DACTI +L'instruction DACTI est l'inverse de l'instruction ACTI. Cette instruction +désactive le système de gestion des intérruptions. +L'instruction DACTI n'utilise aucun registre ou valeur immédiate. +>**DACTI** +### DIV +L’instruction div permet d’effectuer une division. +L’élément source sera divisé par l’élément de +destination. Le résultat de la division est placé dans +le registre A et le reste de la division est placé dans le +registre B . +>**DIV $C $B** +### HIRET +L'instruction HIRET signifie "Hardware Interrupt Return". Cette instruction +doit être utilisée comme instruction de retour par les routine de gestion d'intérruptions +matérielles. Elle ne doit pas être utilisée par les routines de gestion d'intérruptions logicelles. +Lorsque l'instruction est exécutée, le contrôle d'exécution retournera au code intérrompu tout +en s'assurant que la valeur que portait le registre FLAGS sera conservé au moment du retour. +L'instruction HIRET n'utilise aucun registre ou valeur immédiate. +>**HIRET** +### INT +L'instruction INT est utilisée afin de générer des intérruptions logicielles. Lorsque +l'instruction est exécutée, l'unité d'exécution transfère le contrôle à la routine +de gestion d'intérruption appropriée en se basant sur le numéros d'intérruption. +Veuillez consulter la section sur la gestion des intérruptions pour plus de détails. +Cette instruction existe en deux variantes. Elle peut utiliser une valeur immédiate ou un registre. +>**INT #4** + +>**INT $A** +### JMP +L’instruction de saut permet de sauter, de manière +conditionnelle ou non, des parties du code en +consultant la valeur du registre FLAGS . Cette +instruction est particulière, car elle utilise un drapeau +indicateur pour permettre à l’utilisateur de choisir les +conditions dans lesquelles le saut devrait avoir lieu. +Les conditions possibles sont répertoriées ci- +dessous. + +* <> Le saut est toujours effectué. +* ou Le saut est effectué si 0b100 est défini (Drapeau E). +* Le saut est effectué si 0b010 est défini (Drapeau L). +* Le saut est effectué si 0b001 est défini (Drapeau H). + +Il est possible de regrouper ces drapeaux indicateurs +pour établir des conditions complexes. Par exemple, +l’indicateur pourrait permettre au saut d’avoir +lieu si le drapeau E ou le drapeau L est activé. +Veuillez noter que la valeur immédiate ou le registre +utilisés par cette instruction doit posséder une +adresse mémoire valide pointant vers une instruction. +>**JMP #0x40000010** + +>**JMP $B** + +>**JMP <\> testLoop** +### JMPR +L’instruction JMPR est identique à l’instruction JMP , +avec une différence. La valeur +immédiate ou le registre utilisé(e) par l’instruction +JMPR présente un décalage relatif par rapport +au registre d’adresse d’instruction I où le saut doit +mener. Par exemple, si on utilise la valeur immédiate +\#0xFF , l’unité d’exécution commencera à lire les +instructions situées 0xFF octets après l’instruction +JMPR. +>**JMPR #0x10** + +>**JMPR $B** +### MEMR +L’instruction MEMR permet la lecture de la mémoire. L’instruction +MEMR utilise un indicateur de taille qui permet à +l’utilisateur de lire d’un à quatre octets de la +mémoire vers un registre. La source peut être un +registre ou une valeur immédiate, mais il doit s’agir +d’une adresse mémoire valide. L’indicateur de taille +est défini à l’aide des caractères [ et ] . +>**MEMR \[4\] #0x40000000 $B** + +>**MEMR \[4\] $A $B** +### MEMW +L’instruction MEMW permet l’écriture dans la mémoire. +Comme l’instruction MEMR , elle utilise un indicateur de taille. +En raison de sa nature, l’instruction MEMW est +disponible en un nombre de variantes supérieur à +celui de l’instruction MEMR. La destination peut être +un registre ou une valeur immédiate, mais il doit +s’agir d’une adresse mémoire valide. +>**MEMW \[4\] #0xFF #0x40000000** + +>**MEMW \[4\] #0xFF $A** +### MOV +L’instruction MOV permet de déplacer des données +entre registres ou de charger une valeur immédiate +dans un registre. Cette instruction pourrait être +utilisée, par exemple, pour configurer le pointeur de +pile S dans une zone de la mémoire valide afin de +pouvoir utiliser la pile. +>**MOV #0x40000200 $S** + +>**MOV $A $B** +### MUL +L’instruction MUL permet la multiplication d’entiers +entre deux registres. Il n’est pas possible d’utiliser +l’instruction MUL avec une valeur immédiate. +Puisque la multiplication peut résulter en nombres +supérieurs à 32 bits, le registre B contient les 32 bits +supérieurs et le registre A contient les 32 bits +inférieurs du nombre obtenu. Le nombre devrait se +lire sous la forme B:A après la multiplication. +>**MUL $A $B** +### NOP +L’instruction NOP est l’instruction de non-opération. +Elle ne fait rien. Comme Capua n’a pas besoin d’un +alignement sur quatre octets, l’instruction NOP est +habituellement inutile. Toutefois, elle peut être +utilisée pour remplir la mémoire au moment du +démarrage afin de faciliter le développement. +>**NOP** +### NOT +L’instruction NOT inversera les bits d’un registre. Par +exemple, si le registre A est égal à 0x01 avant +l’instruction NOT , il sera égal à 0xFFFFFFFE après +l’instruction NOT . L'instruction, est utulisable uniquement avec un registre. +>**NOT $A** +### OR +L’instruction OR est une opération OU binaire. Elle travaille suivant les règles +booléennes. Voici sa table de vérité: + +* 1 or 1 = 1 +* 0 or 1 = 1 +* 1 or 0 = 1 +* 0 or 0 = 0 + +Elle aura une incidence sur tous les bits d’un registre. +>**OR #0xFF $A** + +>**OR $A $B** +### POP +L’instruction POP enlève 32 bits de la pile et réduit le +pointeur de pile de quatre octets vers l’arrière. Les +données qui étaient en haut de la pile seront +disponibles dans le registre précisé par l’instruction +POP . Pour que l’instruction POP soit utilisée de +manière sécuritaire, le pointeur de pile S doit être +réglé à une adresse mémoire valide. +>**POP $A** +### PUSH +L’instruction PUSH ajoutera une valeur de 32 bits sur +le dessus de la pile et augmentera le pointeur de pile +S de quatre octets vers l’avant. Pour que l’instruction +PUSH soit utilisée de manière sécuritaire, le pointeur +de pile S doit pointer vers une adresse mémoire +valide avant d’utiliser l’instruction PUSH . +>**PUSH #0xFF** + +>**PUSH $A** +### RET +L’instruction de retour est habituellement utilisée +pour revenir d’un appel. Elle prendra l’élément en +haut de la pile et attribuera sa valeur au pointeur +d’instruction I. Pour utiliser l’instruction RET , +l’appelant doit s’assurer que la valeur située au haut de +la pile est un pointeur vers une région de la mémoire +pouvant être exécutée par l’unité d’exécution. +>**RET** +### SFSTOR +L'instruction SFSTOR signifie "Safe Store". L'utilisation du terme "safe" fait référence +à l'atomicité de l'instruction. L'instruction prend la valeur présente dans l'opérande +source (soit un registre ou une valeur immédiate) et copie cette valeur à l'adresse +contenue par le registre $A si la condition est respectée en effectuant une comparaison +entre l'information contenue à l'adresse mémoire et les données fournie par l'instruction. +L'instruction changera aussi la valeur du registre FLAGS en fonction du résultat de la comparaison. +L'ordre de comparaison est défini comme source (immédiate ou registre) contre l'information obtenue +à l'addresse pointée par le registre $A et non pas le contraire. +>**SFSTOR #1** + +>**SFSTOR $B** + +Cette instruction a pour objectif de permettre au programmeur d'écrire du code permettant la synchronisation +entre fils d'exécution (thread) comme des mutexes. +### SHL +La mnémonique d’instruction SHL représente un +décalage vers la gauche. L'instruction permet à +l'utilisateur de déplacer la valeur d’un registre de X bits +vers la gauche, où X est un chiffre plus grand que 0. Les +valeurs excédentaires sont simplement perdues. Par +exemple, considérons la valeur 0x80000001 . Le +déplacement vers la gauche d’un bit de cette valeur +fera en sorte que celle-ci deviendra 0x00000002 +>**SHL #0x01 $A** + +>**SHL $B $A** +### SHR +L’instruction SHR est la même que l’instruction SHL , +sauf que le déplacement se produit vers la droite. Ici, +le déplacement vers la droite d’un bit de la valeur +0x80000001 produira la valeur 0x40000000. +>**SHR #0x01 $A** + +>**SHR $B $A** +### SIVR +Cette instruction signifie "Set Interrupt Vector Register". Elle est utilisée pour configurer +le pointeur du vecteur d'intérruptions. Cette valeur doit pointer vers un vecteur de pointeurs +sur des routines de gestion des intérruptions. Cette instruction peut seulement être utilisée +avec un registre. +>**SIVR $A** + +L'exemple de code suivant montre comment configurer l'IVR. +``` +.global start: + +start: +MOV end $S ; Stack grows up, setting S to the end is generally safer + ; The stack needs to be configure for interrupt handling to work +MOV vector $A +SIVR $A ; Set the interrupt vector to "vector" +ACTI ; Interrupt handling activation + +loop: + NOP + JMP <> loop + +clockHandler: ; All hardware interrupts are mapped to this empty handler +keyboardHandler: +hardDriveReadHandler: +hardDriveWriteHandler: + HIRET + + +vector: ; The vector is built using the dataMemRef marker +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler +end: +``` +### SUB +L’instruction SUB permet de soustraire des valeurs. +Il est important de comprendre que l’opération +s’effectue comme suit: +* destination = destination - source + +Il est également important de savoir que +l’instruction SUB ne prend en compte que les +nombres entiers sous forme de complément de deux, +32 bits, signés. +>**SUB #0x01 $A** + +>**SUB $B $A** +### XOR +L’instruction XOR est une opération binaire comme l’instruction AND et l’instruction OR. +Elle suit la logique normale XOR (ou exclusif) selon la table de +vérité suivante : + +* 1 xor 1 = 0 +* 1 xor 0 = 1 +* 0 xor 1 = 1 +* 0 xor 0 = 0 + +Elle peut être utilisée pour régler la valeur d’un +registre à 0. +>**XOR #0x01 $A** + +>**XOR $B $A** + +# Outils +Capua, étant une architecture, demande son propre +assembleur. Pour ce project, 4 outils ont été developpés +afin de permettre le développement logiciel et la validation +de la plateforme. + +* Assembler.py +* Linker.py +* Debugger.py +* HardDriverCreator.py + +Cette section explique l'ensembble des outils. +Il est important de noter que ceux-ci ont, avant tout, +été développés afin de permettre de valider +le bon fonctionnement de Capua. Ainsi, les outils +présentent des "bugs" et, en tant que développeur, +vous devez vous assurer de les utiliser exactement +de la manière décrite. Plusieurs de ces problèmes +ont été corrigés depuis la version 1.0. Une réécriture +de ces outils est planifiée. + +## Compilateur +Nous n'avons présentemant aucun compilateur. C'est un des éléments majeurs +où une contribution de la communauté du logiciel libre serait plus que +bienvenue. + +## Assembler.py +##### Survol +Capua possède son propre assembleur. Il est facile à utiliser et son implémentation est assez +simple. +N'oubliez pas que la version actuelle de l'assembleur a été +écrite comme outil de test pour Capua. +Veuillez respecter la syntaxe décrite ici puisque +les messages d'erreur ne sont pas toujours faciles à comprendre. +L'assembleur est très sensible aux erreurs typographique. +Par exemple, chaque partie d'une instruction doit +être séparée par un espace blanc. Cela inclut les commentaires en fin de ligne: + +>**MOV $A $B;This is not good enough** + +>**MOV $A $B ;This is fine** + +De toute évidence, bon nombre de ces problèmes sont faciles +à corriger. N'hésitez pas à vous impliquer et à contribuer. + +Lors de l'assemblage, le fichier d'entrée est +transformé en un fichier .o. Veuillez noter que +l'extension a été choisie en raison de la signification historique d'un fichier .o. +Capua n'utilise pas réellement le format de fichier .o. Le format de fichier utilisé par +Capua a été mis au point avec l'intention de permettre +de lier plusieurs fichiers ensemble afin de former un fichier binaire plat. +Le format actuel est également +très facile à comprendre et est un mélange de XML et de +données binaires. Ce format n'est pas parfait et sera modifié dans le futur. +Une description complète de ce format est disponible plus bas dans ce document. + +##### Écriture du code assembleur +Cette partie se concentre non pas sur le code mais +sur les directives de l'assembleur. Ces directives doivent +être utilisées lors de l'écriture de code. + +* "NomDeSymbole:" + * Un nom arbitraire, seul, sur une ligne suivi + d’un caractère ":" indique un symbole. + L’assembleur l’ajoutera à la liste des + symboles et le code sera en mesure de faire + référence à NomDeSymbole partout où une + valeur immédiate pourrait être utilisée. + Par exemple, dans le cas **MOV stackAddress $A**. + L’adresse liée de stackAddress serait mise + comme valeur immédiate dans l’instruction + mov affichée. On pourrait aussi l’utiliser dans + les boucles comme **JMP <> loopStart** +* ".global NomDeSymbole" + * Permet à l’assembleur d’ajouter un symbole + à la liste des symboles externes. +* ";" + * Le caractère ";", comme dans beaucoup + d’autres langages assembleur, indique un + commentaire. Les commentaires peuvent être soit sur + leur propre ligne, soit à la fin d’une ligne, à la + suite d’une instruction. Veuillez noter que, + dans le cas où un commentaire est mis après + une instruction, un espace doit séparer la fin + de l’instruction du début du commentaire (le + caractère ";"). +* ".dataAlpha" + * On peut l’utiliser n’importe où tant qu’elle se + trouve sur sa propre ligne. Cette directive est + suivie par un espace blanc et un texte au + format libre. Le texte n’a pas besoin d’être + mis entre guillemets. En fait, il ne doit pas + être mis entre guillemets. La chaîne de caractères se + termine à la fin de la ligne. L’assembleur + ajoute un caractère de fin (0x00) à la fin de la + chaîne au moment de l’assemblage. Exemple + d’utilisation: + **.dataAlpha This is a test string** + *Notez qu'aucun commentaire ne peut suivre cette ligne.* +* ".dataMemRef" + * Ceci est similaire à .dataNumeric à l'exception + qu'un programmeur peut spécifier une référence en mémoire. + Lorsque l'édition des liens est faite, la référence mémoire + sera remplacée par son adresse. + Cette directive est principalement utilisée afin + de permettre la création de vecteur d'intérruption. + La directive est utilisée de cette manière: **.dataMemref referenceMemoire** +* ".dataNumeric" + * C’est la même chose que .dataAlpha sauf que + cela permet au programmeur d’utiliser des + valeurs numériques de 32 bits. Exemple + d’utilisation : + **.dataNumeric 0xFFFFFFFF** +* "$" + * Est le préfixe de registre. Chaque registre, + lorsqu’il est utilisé dans une instruction, + doit être précédé par le caractère + "$". +* "#" + * Est le préfixe de valeur immédiate. Chaque + valeur immédiate (sauf lors de l’utilisation + d’un symbole) doit être précédée par le + caractère "#". + De multiples variantes sont possibles pour les + valeurs immédiates : + \#0xFF + \#255 + \#-1 + \#0b11111111 + +##### Exemple: programme court +Voici un exemple de programme qui permet de calculer la +longueur d'une chaîne de caractères. +``` +; File Length.casm +; This will calculate the length of testString +.global start +start: + JMP <> stackSetup ;Jump over the string + +testString: +.dataAlpha This is a test string + +stackSetup: + MOV stack $S ;Stack is now usable +codeStart: + PUSH testString + CALL strlen + SUB #0x4 $S ;stack reset +end: + +;Following is the length calculation +;strlen(stringPointer) +strlen: + MOV $S $A + SUB #0x4 $A ;Calculate parameter offset + MEMR [4] $A $A ;Get parameter in register A + MOV $A $C ;Keep pointer to string start +lenover: + MEMR [1] $A $B + CMP #0x00 $B ;are we at the end of the string? + JMP gotlen + ADD #0x1 $A + JMP <> lenover ;not at the end, jump back +gotlen: + SUB $C $A ;A will hold the len of the string at this point. + RET ;return value in register A + +; Stack is at the end of the program. +; No risk of overwriting the program +stack: +``` + +Afin d'assembler ce fichier, vous pouvez utiliser la commande suivante: +> python3 Assembler.py -i strlen.casm -o strlen.o + +Vous pouvez obtenir de l'aide au sujet de l'assembleur en utilisant l'option "-h" lorsque +vous lancer l'assembleur. + +## Linker.py +##### Survol +Capua possède également son propre éditeur de +liens. Cet éditeur de liens est destiné à être utilisé +pour aider à la création de fichiers binaires plats. +L’éditeur de liens peut lier plusieurs fichiers. +Étant donné que les fichiers binaires produits par +l’éditeur de liens sont destinés à être utilisés +"sur le métal" (baremetal), aucun processus d'édition de liens dynamique n’est disponible. +Toutes les adresses, suivant l'édition des liens, +sont codées en dur dans le fichier binaire résultant. +Cela signifie que l’éditeur de liens a besoin de +connaître, au moment de l’établissement de liens, +l’adresse mémoire où le fichier binaire doit être +chargé. Si l’adresse de chargement n’est pas fournie, +le fichier binaire lié est chargé à l’adresse +MEMORY_START_AT (la valeur par défaut est +0x40000000). Cela est pratique pour effectuer des tests +puisque l’unité d’exécution commence à exécuter +les instructions à cette adresse. + +##### Attention! +Lors de la liaison de plusieurs fichiers, l’ordre dans +lequel les fichiers sont donnés à l’éditeur de liens revêt une IMPORTANCE MAJEURE. Les +fichiers sont positionnés dans le fichier binaire final dans +l'ordre d’insertion dans l’éditeur de liens. + +##### Note à propos des symboles +L’éditeur de liens génère un fichier ".sym" dans le +même dossier que le fichier binaire final. Ce fichier +est simplement une liste de symboles et d’adresses. +Ce fichier, s'il est disponible, est chargé lorsque vous +exécutez le fichier binaire dans le débogueur. Ceci vous +permet d’utiliser des noms de symbole à la place +d’adresse mémoire lors de l’exécution du fichier +binaire dans le contexte du débogueur. Notez que +tous les symboles présents dans le fichier ".sym" ont été +modifiés avec le nom de leur fichier d’origine +comme préfixe. Les noms de symbole eux-mêmes +sont également mis en majuscules. + +##### Utilisation +Afin de procéder à l'édition des liens sur le fichier ".o" créé plus tôt, vous pouvez +utiliser la commande suivante: +> python3 Linker.py -i strlen.o -o strlen.bin + +Dans l'éventualité où vous voudriez lier plusieurs fichiers ".o", le nom +de ceux-ci peut être ajouté à l'entré de l'éditeur de liens: +> python3 Linker.py -i main.o subFile.o -o main.bin + +## Debugger.py +Le débogueur est l'interface du programmeur à l'intérieur de la machine virtuelle. +Lancer le débogueur, c'est lancer la machine virtuelle. +Le débogueur est simple. Il possède presque +toutes les fonctionnalités de base offertes par la +plupart des débogueurs. À l’heure actuelle, il existe +trois grandes limitations. La première est que vous +ne pouvez pas modifier la valeur d'un registre ou une +valeur de mémoire avec le débogueur. Vous ne +pouvez pas non plus « sauter par dessus » un appel +de fonction facilement. Pour cela, vous devriez +placer un point d’arrêt au retour de l’appel de +fonction. La dernière limitation est que vous ne +pouvez pas "recharger" le programme +sans relancer le débogueur. Toutes ces +caractéristiques sont notées et seront ajoutées à un +moment ultérieur. L’utilisation du +débogueur est simple et facile. Il suffit d’exécuter le +débogueur avec l’option « -h » pour apprendre les fonctions de base. +Une fois lancé, le débogueur s’arrête avant l'exécution +de la première instruction. Vous pouvez accéder +au menu d’aide du débogueur en tapant « h » ou +« help » à l’invite du débogueur. + +Afin de lancer le fichier binaire créé plus tôt, vous pouvez utiliser la commande suivante: +> python3 Debugger.py -i main.bin + +##### Note importante au sujet du processus de démarrage virtuel +Si le débogueur est lancé sans aucun paramètre. Celui-ci utilisera le code du "firmware" présent +dans le fichier CapuaEnvironment/firmware.bin. Pour que ceci fonctionne, le fichier CapuaEnvironment/firmware.casm +doit être assemblé puis lié afin de créer le fichier binaire final. Ce fichier, lorsque chargé +tentera d'effectuer une validation du disque rigide. Si cette validation fonctionne, le +premier secteur du disque rigide sera chargé à l'addresse MEMORY_START_AT (valeur par défaut 0x40000000) +et commencera l'exécution du code nouvellement chargé. Ceci a pour objectif de simuler le +processur de démarrage d'un ordinateur. Le code source du "firmware" peut être inspecté pour +plus de détails. + +## HardDriveCreator.py +C'est outil est simplement un outil d'aide visant à générer un fichier binaire +vide d'une taille acceptable pour la machine virtuelle. L'outil procédera à la validation +de la configuration de la machine virtuelle afin de créer un fichier de la bonne taille. +Par défault, un fichier de 1Mo est créé. +La machine, afin de fonctionne correctement, demmande la présence d'un fichier HD.bin à la +racine du projet. Vous pouvez générer ce fichier à l'aide de la commande: +> python3 HardDriveCreator.py -o HD.bin + +L'absence du fichier HD.bin à la racine du projet causera un disfonctionnement au démarrage +de la machine virtuelle (que le "firmware" soit utilisé ou non) + +# Gestion des intérruptions sur Capua + +Capua permet de gérer les interruptions à partir de sources matérielles et logicielles. +Pour que les intérruptions soient traitées, la gestion des interruptions doit d'abord être activée. +Lorsque la machine virtuelle démarre, +la gestion des interruptions est désactivée. +On peut activer la gestion des interruptions en utilisant l'instruction suivante +> ACTI + +Inversement, la gestion des interruptions peut être désactivée en utilisant l'instruction +> DACTI + +Sur Capua, la gestion des interruptions se fait à l'aide du registre de +vecteur d'interruption (IVR). L'IVR doit être configuré avec un pointeur +vers un vecteur de pointeurs sur des routines de gestion des intérruptions. +Chaque interruption matérielle est associée à une valeur numérique +(les interruptions logicielles spécifient leur propre valeur). +Lorsqu'une interruption matérielle se produit, la gestion des interruptions +est désactivée et l'exécution reprend à la routine de gestion d'interruptions +correspondant à un numéro d'interruption. Le processus est semblable pour +les interruptions logicielles. Cependant, le traitement des interruptions n'est +pas désactivé lors de la gestion d'une interruption logicielle. Ceci permet au système de +recevoir les interruptions matérielles lors de la gestion d'une interruption de logiciel. +Lors de l'interruption, la routine de gestion est sélectionnée à partir du +vecteur d'interruption suivant cette formule: +``` +IN est le numéro d'interruption +Vecteur est l'adresse du vecteur d'intéruption +Routine est l'address à laquelle l'adresse de la routine de gestion appropriée peut être trouvée + +Routine = Vecteur + (IN * 4) +``` + +Il est de la plus haute importance que le programmeur comprenne que, pendant la gestion +d'une interruption matérielle, toutes les interruptions sont désactivées sur le système. +La plupart des périphériques sur Capua utiliseront une cache afin de conserver +les intérruptions et pouvoir les livrer plus tard. Cependant, ceci n'est pas garanti. +Certaines interruptions peuvent être perdues si l'exécution demeure +à l'intérieur d'une routine de gestion des interruptions trop longtemps. +à la fin de la routine de gestion, dans le cas d'une interruption matérielle, +une instruction de retour spéciale doit être utilisée: +>HIRET + + +L'instruction HIRET fait un peu plus que simplement permettre le retour. +Elle réactive également la gestion des interruptions sur le noyau et s'assure +que le registre FLAGS est réinitialisé à la même valeur qu'il était avant l'arrivée +de l'intérruption. +L'instruction HIRET ne doit pas être utilisée pour retourner des routines de gestion +des interruptions logicielles. + +Le retour d'une d'une intérrruption logicielle est faite, comme un retour de fonction, en utilisant +l'instruction "RET". +##### Configuration des intérruptions +Sur Capua, à l'heure actuelle, seuls les 4 premiers vecteurs d'interruptions sont utilisés. +Les vecteurs 0 à 31 devraient tous être considérés comme réservés. +Un gestionnaire d'intérruptions logicielles ne doit donc pas les utiliser. Voici la +la cartographie d'interruption actuelle: + +* 0 est mappé sur INTERRUPT_CLOCK + * L'intérruption 0 est générée par l'horloge d'intérruption à un interval configuré +* 1 est mappé sur INTERRUPT_KEYBOARD + * L'intérruption 1 est générée par le clavier du terminal dès qu'une touche est enfoncée +* 2 est mappé sur INTERRUPT_HARD_DRIVE_DONE_READ + * L'intérruption 2 est générée par le disque rigide quand une opération de lecture est terminée +* 3 est mappé sur INTERRUPT_HARD_DRIVE_DONE_WRITE + * L'intérruption 3 est générée par le disque rigide quand une opération d'écriture est terminée + +###### Note importante: +On pourrait ne pas comprendre pourquoi un programmeur doit mettre +en place un gestionnaire d'interruptions logicielles à la palce de simplement utiliser +des appels de fonction. Après tout, il n'y a pas de différence, ou presque, entre une +interruption logicielle et un appel de fonction. +Cependant, bien que non supportés à l'heure actuelle, le support pour la mémoire virtuelle +et des niveaux de privilège multiples est présentement en cours de développement sur Capua. Il est donc +actuellement recommandé d'utiliser les fonctions d'interruption pour tout ce qui serait normalement +fourni à l'utilisateur par le noyau d'un système d'exploitation. Suivre cette recommandation +permettra d'éviter de futurs problèmes. + +L'exemple de code suivant montre comment gérer les intérruptions logicielles et matérielles. +Il montre aussi comment configurer le registre IVR. + +``` +; This example shows how multiple hardware interrupts +; can be mapped to the same code. It also show how to +; properly handle software interrupts and setup the +; IVR +.global start: + +start: + MOV end $S ; Stack grows up, setting S to the end is generally safer + ; The stack needs to be configured for interrupt handling to work + MOV vector $A + SIVR $A ; Set the interrupt vector to "vector" + ACTI ; Interrupt handling activation + int #4 ; Software interrupt #4 + ; Since IVR is set to vector, the routine used when int #4 is executed will be + ; determined by: + ; routine = vector + (4 * 4) + ; Since each entries are 4 bytes long + ; routine = testHandler + +loop: + NOP + JMP <> loop + +clockHandler: ; All hardware interrupts are mapped to this empty handler +keyboardHandler: +hardDriveReadHandler: +hardDriveWriteHandler: + HIRET + +testHandler: ; Just a demonstrative software handler + MOV #0xFFFFFFFF $A + MOV #0xAAAAAAAA $B + RET + +vector: ; The vector is built using the dataMemRef marker +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler +.dataMemRef testHandler +end: +``` + +# Matériel mappé en mémoire +La machine virtuelle supporte le matériel mappé en mémoire. +Le matériel mappé en mémoire est +accessible à l'aide d'adresses mémoire spécifiques. +La façon dont chaque élément matériel doit être utilisé est +spécifique à chaque appareil. + +## Horloge (Clock) +``` +Adresse de mappage: 0x20000100 +Opération permise: Lecture +``` +L'horloge a pour objectif d'offrir une source d'entropie à l'utilisateur. +et non pas pour fournir l'heure à l'utilisateur. +La lecture de 4 octets à l'adresse mappée fournira une valeur +basé sur le code python suivant: +>int((time.time() * 10000000)) & 0xFFFFFFFF + +L'exemple de code suivant montre comment accéder à l'horloge: +``` +MOV #0x20000100 $A +MEMR [4] $A $B ; The clock value will be in register $B +``` + +## Horloge d'intérruption (Interrupt Clock) +``` +Adresse de mappage: 0x20000300 +Opération permise: Lecture/Écriture +``` + +Lors de l'écriture de 4 octets à l'adresse mappée, l'horloge d'interruption commence à générer +des interruptions matérielles à la fréquence (millisecondes) définie par la valeur de 4 octets écrite à +l'adresse de mappage. Pour que l'unité d'exécution puisse recevoir +les interruptions générées, les interruptions doivent avoir été activées +sur ce dernier. Aucune interruption n'est générée (même +si les interruptions sont activées) si aucune opération d'écriture n'a été effectuée +à l'adresse mappée de l'horloge d'intérruption. + +L'exemple de code suivant montre comment configurer la fréquence de l'horloge: +``` +MOV #0x20000300 $A +MEMW [4] #0xFF $A ; Will set the frequency to 255 ms +``` +## Disque rigide (Hard Drive) +``` +Adresse de mappage: 0x20000400 +Opération permise: Lecture/Écriture +``` + +Un élément important doit être noté. Le disque dur lui-même n'est pas +cartographié à l'adresse de mappage. C'est le contrôleur du disque dur qui +est mappé sur cette addresse. +C'est celui-ci qui est utilisé pour accéder au disque dur. + +Pour pouvoir accéder au disque, une structure spécifique doit être écrite à +l'adresse de mappage. Chaque opération faite sur le disque demande que cette +structure soit écrite de manière correcte. + +Structure d'opérations d'accès au disque: + +* 0x20000400 = 0x0 pour une opération de lecture ou 0x01 pour une écriture +* 0x20000404 = Le numéros LBA qui fait l'objet d'une opération (décallage de 512 octets) +* 0x20000408 = Addresse mémoire devant être utilisée pour la lecture ou l'écriture +* 0x2000040C = Déclanchement de l'action = Quand ceci est mis à 1 l'action est déclanchée. + * Ce champ doit être remis à 0 manuellement entre chaque opération faite sur le disque. + +Après une opération sur le disque, une intérruption sera généré selon l'opération. + +L'exemple de code suivant montre comment lire un bloque de données à partir +du disque. +``` +; This will test the correct working order of the hard drive read operation +.global start + +start: + MOV readBuffer $S + ADD #1024 $S ; Set the stack (needs to be set to handle interrupts + MOV vector $A + SIVR $A ; Set the Interrupt vector + ACTI ; activates the interrupts + + MOV #0x20000400 $A + MEMW [4] #0x0 $A ; Set read action + ADD #4 $A + MEMW [4] #0x0 $A ; Set LBA + ADD #4 $A + MEMW [4] readBuffer $A ; Set destination buffer + ADD #4 $A + MEMW [4] #0x01 $A ; Set the trigger + +waitLoop: + NOP + JMP <> waitLoop + +clockHandler: + HIRET +keyboardHandler: + HIRET +hardDriveReadHandler: + ; Do nothing in the handler, this is just an example + HIRET + +hardDriveWriteHandler: + HIRET + +vector: +.dataMemRef clockHandler +.dataMemRef keyboardHandler +.dataMemRef hardDriveReadHandler +.dataMemRef hardDriveWriteHandler + +readBuffer: +end: +``` + +Une opération d'écriture sur le disque est très similaire à l'exception que l'opération +d'écriture doit être utilisée. +## Terminal +``` +Adresse de mappage: 0x20001000 (Affichage), 0x20001800 (Clavier) +Opération permise: Lecture/Écriture +``` +Le terminal est un appareil spécial car il permet l'affichage d'information (à l'écran) +ainsi que la saisie d'information à partir du clavier. +Ainsi, le terminal est, en réalité, 2 appareils en un seul. +##### Terminal - affichage +L'écran permet un affichage de 80x25 caractères. Le caractère 0 (x=0, y=0, ou celui situé +dans le coin supérieur gauche) est mappé à l'addresse 0x20001000. Le caractère +1 (x=1, y=0) est mappé à l'addresse 0x20001001. La mémoire est linéaire. Ceci +signifie que le caractère 0 de la ligne 1 (x=0, y=1) est le caractère numéro 80. +Ce caractère est donc mappé à l'adresse 0x20001000 + 80. +Afin d'être en mesure d'afficher un caractère à l'écran, le programmeur doit +écrire la valeur ascii d'un caractère à l'adresse appropriée. + +L'exemple suivant montre un extrait de code qui permet d'afficher "ABCD" +dans le coin suppérieur gauche de l'écran. +``` +; This will print ABCD on the screen +.global codeStart + +codeStart: + MOV #0x20001000 $A ; Display address + MOV #0x41424344 $B ; ABCD ascii code + MEMW [4] $B $A +``` +##### Terminal - clavier +Afin de fonctionner, le clavier demande que la gestion des intérruptions soit +configurée et activée. Lorsque l'utilisateur appuie sur une touche du clavier, +une intérruption de clavier est générée et le contrôle est transféré à la routine +de gestion d'intérruption du clavier. À ce moment le code de lecture (attention, code +de lecture et non pas code ascii) pour la touche activée est disponible à la lecture +à l'adresse 0x20001800. +Dans l'éventualité où une intérruption du clavier ne peut être livrée à l'unité +d'exécution, le clavier possède une mémoire tampon pouvant contenir un maximum +de 20 codes de lecture. L'intérruption sera donc livrée à un moment ultérieur. +Dans le cas où la mémoire du clavier est pleine avant que l'intérruption soit +livrée, les code les plus vieux seront éliminés de la mémoire du clavier. + +L'exemple de code suivant montre comment lire le code de lecture d'une touche +à l'intérieur d'une routine de gestion d'intérruption pour le clavier. +``` +; This will print scan code to the top left of the screen +; if a scan code does not happen to map to a printable +; character, nothing will be visible. +.global start + +start: + MOV end $S + MOV vector $A + SIVR $A + ACTI + +loop: + NOP + JMP <> loop + +clockHandler: + HIRET + +keyboardHandler: + MOV #0x20001800 $A + MEMR [1] $A $B + MOV #0x20001000 $A + MEMW [1] $B $A + HIRET + +vector: +.dataMemRef clockHandler +.dataMemRef keyboardHandler +end: +``` +## Format de fichier ".o" de Capua +Le format de fichier Capua est très simple. Pour des raisons évidents, +des "bugs" sont associés au format lui-même. Ces problèmes seront résolut +lorsque le format de fichier sera remplacé. Pour l'instant, le format +fonctionne correctement dans la majorité des cas. + +En voici la forme générale: + +``` + + + + + ... + + + + + ... + +... + +``` + +Le texte qui suit explique le détail de tous les éléments +faisant parti du format de fichier. + +* AssemblySize + * Cette balise contient la taille prévue de la version + finale liée de ce fichier. La taille est encodée en + format "big endian" à + l’intérieur de la balise. Le calcul de taille a été + intégré à l’assembleur puisqu’il était plus facile + et plus rapide de le placer à cet endroit. De plus, l’information + est accessible à l’éditeur de liens + lorsque celui-ci doit calculer l’adresse à l’échelle du + fichier parmi plusieurs fichiers qui doivent être + reliés. +* ExternalSymbols + * Cette balise contient une liste de balises (couples + refName/refAdd). Les symboles présents dans la + liste ExternalSymbols sont considérés comme + "globaux" et, par conséquent, sont à la disposition + de l’éditeur de liens lorsque celui-ci relie plusieurs + fichiers. +* InternalSymbols + * Cette balise est la même que ExternalSymbols sauf + que les symboles énumérés dans cette balise ne + sont pas accessibles à l’éditeur de lien au moment + de lier plusieurs fichiers. Ceci a + pour but de prévenir les problèmes de collision de + noms à l’échelle globale (externe) au moment de + la liaison. +* refName + * Une balise refName doit se trouver à l’intérieur + d’une balise ExternalSymbols ou d’une balise + InternalSymbols. Elle doit également être suivie + d’une balise refAdd. La balise refName contient + simplement la version texte du nom de symbole. + Le nom de symbole est déterminé par la + référence/appellation de la mémoire dans le code + assembleur écrit par le programmeur. +* refAdd + * Cette balise suit une balise refName et indique + simplement le décalage où le symbole se + trouve à partir de l’adresse 0 (par rapport au début + du fichier). Notez que le décalage est relatif à un + fichier entièrement relié, pas à un fichier objet (.o). + Cette adresse remplace éventuellement le nom de + symbole lorsque le fichier est relié. +* Text + * La balise Text contient le fichier binaire + assemblé du fichier objet. Un examen attentif + révélera la présence de noms de symboles à + l’intérieur de la balise de texte. Ceux-ci sont + remplacés au moment de l'édition des liens. Les symboles + sont présents dans la section de texte, sous la forme : + ":SymbolName:". Il ne s’agit pas d’une solution + parfaite (ni même d’une bonne solution), mais elle + est rapide. (Gardez toujours à l’esprit qu’à l’origine, + ces outils ont été écrits pour tester l’unité + d’exécution, non pas pour être utilisés par des programmeurs). + +- - - +This file is part of Spartacus project +Copyright (C) 2017 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + + + + diff --git a/GameOverlay/Arena.py b/GameOverlay/Arena.py deleted file mode 100644 index fc27787..0000000 --- a/GameOverlay/Arena.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -# -*- coding: -*- - -""" -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" - -__author__ = "CSE" -__copyright__ = "Copyright 2015, CSE" -__credits__ = ["CSE"] -__license__ = "GPL" -__version__ = "1.3" -__maintainer__ = "CSE" -__status__ = "Dev" - - -class Arena: - """ - This is nothing more than a "helper" class that aims at displaying the game state. - """ - def __init__(self): - # colorama.init() - pass - - def displayFirstInterface(self, p1, p2): - print("") - print("----------------------------------------------------------------------") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" Welcome to Spartacus Code battle! ") - print(" Virtual machine is now initializing ") - print(" Contestants are being feed... ") - print(" ") - print(" {} vs. {}".format(p1, p2,)) - print(" ") - print(" Battle is about to begin... ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print(" ") - print("----------------------------------------------------------------------") - print("") - - - def displayGameState(self, p1capua=None, p2capua=None): - - print("") - print("") - print("Current Memory Map") - print("----------------------------------------------------------------------") - - # Here we need to print memory status! - # 16 lines, 64 unit per line, 1024 bytes per unit - - p1Count = 0 - p2Count = 0 - for byte in range(1, (16 * 64 * 1024) + 1): - - if p1capua.eu.mioc._memoryArray._memoryCellArray[byte-1]._lastWriteAccessBy == p1capua.eu.name: - p1Count += 1 - elif p2capua.eu.mioc._memoryArray._memoryCellArray[byte-1]._lastWriteAccessBy == p2capua.eu.name: - p2Count += 1 - - if byte % 1024 == 0: - # We need to write a unit to the screen - char = " " - if p1Count > p2Count: - char = "@" - elif p2Count > p1Count: - char = "*" - - print(char, end="") - # Reset count for next unit - p1Count = 0 - p2Count = 0 - - if (byte / 1024) % 64 == 0 and byte != 0: - # We need to write a line - print("\n", end="") - - print("----------------------------------------------------------------------") - print("{} @".format(p1capua.eu.name,)) - print("{} *".format(p2capua.eu.name,)) - print("----------------------------------------------------------------------") - print("") - - def displayGameStop(self, message=""): - print("") - print("----------------------------------------------------------------------") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print(message) - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("----------------------------------------------------------------------") - print("") - - def displayContextCrash(self, message="", contextDump=None): - print("") - print("----------------------------------------------------------------------") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print(message) - print("Crashed thread context information dump:") - print(str(contextDump)) - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()") - print("----------------------------------------------------------------------") - print("") diff --git a/GameOverlay/GameConfiguration.py b/GameOverlay/GameConfiguration.py deleted file mode 100644 index 419be9f..0000000 --- a/GameOverlay/GameConfiguration.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: -*- - -""" -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" - -__author__ = "CSE" -__copyright__ = "Copyright 2016, CSE" -__credits__ = ["CSE"] -__license__ = "GPL" -__version__ = "1.0" -__maintainer__ = "CSE" -__status__ = "Dev" - -MAX_PROGRAM_SIZE = 0xFFF diff --git a/GameOverlay/GameManager.py b/GameOverlay/GameManager.py deleted file mode 100644 index 7a5c664..0000000 --- a/GameOverlay/GameManager.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python -# -*- coding: -*- - -""" -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" - -from CapuaEnvironment.Capua import Capua -import CapuaEnvironment.IOComponent.MemoryMappedDevices.SpartacusThreadMultiplexer.SpartacusThreadMultiplexer as STMP -from Configuration.Configuration import MEMORY_START_AT, MEMORY_END_AT -from GameOverlay.Arena import Arena -from GameOverlay.GameConfiguration import MAX_PROGRAM_SIZE -from ToolChain.Assembler.Assembler import Assembler -from ToolChain.Debugger.Debugger import Debugger # Do not delete... There is a commented out line in this file that allows to plug in the debugger -from ToolChain.Linker.StaticFlatLinker import StaticFlatLinker - -import random -import time - -__author__ = "CSE" -__copyright__ = "Copyright 2015, CSE" -__credits__ = ["CSE"] -__license__ = "GPL" -__version__ = "1.4" -__maintainer__ = "CSE" -__status__ = "Dev" - - -class GameManager: - - arena = None - - p1Capua = None - p1ContextBank = None - p1CurrentContext = 0 - - p2Capua = None - p2ContextBank = None - p2CurrentContext = 0 - - currentPlayer = None # Capua instance of the current player - currentContextBank = None # List of contexts for the current player - currentContext = 0 # index for the current context relative to currentContextBank - - mioc = None - ifu = None - - def __init__(self, p1File, p1Name, p2File, p2Name): - """ - This method setup the game environment. The first thing it will do is build the - required fighter programs into usable binaries. After that, it will build a Capua - environment and load these fighter into the environment. - :param p1File: String, path the to file to be used - :param p1Name: String, usable display name - :param p2File: String, path the to file to be used - :param p2Name: String, usable display name - :return: - """ - - # The Arena is the "graphic" (yeah right...) display for the game. - self.arena = Arena() - - # Setup core environment after this step, 2 cores will share the same memory elements - self.p1Capua = Capua(name=p1Name) - self.mioc = self.p1Capua.mioc - self.p2Capua = Capua(ma=self.p1Capua.ma, mioc=self.p1Capua.mioc, name=p2Name) - - # We need to get load address for both fighter. These are random otherwise game is boring - p1Address, p2Address = self.getRandomLoadAddresses() - - # If we are here, we have valid random load address for both fighter - # We can now build both fighters - self.buildFiles(fileName=p1File, baseAddress=p1Address) - self.buildFiles(fileName=p2File, baseAddress=p2Address) - - # If we are here, both files have been built! Yeah! - - # Following line is there for debugging purposes it will allow - # to run the debugger in the environment using p1 random load address - # Warning, Threading will not be available even if we are in game mode - # deb = Debugger(inputFile=p1File.split(".")[0] + ".bin", loadAddress=p1Address) - - # Time to load binaries in memory - p1Size = self.loadBinaryInMemory(capua=self.p1Capua, - loadAddress=p1Address, - binaryPath=p1File.split(".")[0] + ".bin") - p2Size = self.loadBinaryInMemory(capua=self.p2Capua, - loadAddress=p2Address, - binaryPath=p2File.split(".")[0] + ".bin") - - # Just to make sure no player is trying to destroy the game with a big - # empty battle program - if p1Size > MAX_PROGRAM_SIZE or p2Size > MAX_PROGRAM_SIZE: - print("{} is {} bytes".format(p1Name, hex(p1Size))) - print("{} is {} bytes".format(p2Name, hex(p2Size))) - print("Over {} = disqualified!!".format(hex(MAX_PROGRAM_SIZE))) - quit() - - # Insert info into context bank before player can play - # Could have hidden this away in a method but feels clearer this way - self.p1ContextBank = [] - self.p1ContextBank.append(self.p1Capua.eu) - - self.p2ContextBank = [] - self.p2ContextBank.append(self.p2Capua.eu) - - # The smallest binary starts first. If equal random start - self.pickFirstToPlay(p1Size=p1Size, p2Size=p2Size) - - # This creates the link with the hardware device that allows for thread support - STMP.GameEnvironment = self - - # Right here, right now, the game has been setup, we are ready to play! YAY! - self.play() - - def buildFiles(self, fileName=None, baseAddress=None): - """ - This will do a full build of the file given as input. The file will be built - so that it can be loaded at the baseAddress - :param fileName: str, the name of the file that is to be used for the build. - :param baseAddress: int, the base address that is to be used for the build load address. - :return: - """ - try: - baseFile = fileName.split(".")[0] - assembler = Assembler(fileName, baseFile + ".o") - linker = StaticFlatLinker(inputFileList=[baseFile + ".o"], - outputFile=baseFile + ".bin", - loadAddress=baseAddress) - except Exception as e: - raise ValueError("{} failed to build. Game over".format(fileName)) - - def getRandomLoadAddresses(self): - """ - This is an helper method that will return two valid random load address. Both address - have 4k of contiguous memory following the address. - :return: - """ - p1Address = random.randint(MEMORY_START_AT, MEMORY_END_AT - MAX_PROGRAM_SIZE) # See GameConfiguration - p1AddMax = p1Address + MAX_PROGRAM_SIZE - p2Address = p1Address # This forces the loop and avoid code duplication - - while p2Address in range(p1Address, p1AddMax) or (p2Address + MAX_PROGRAM_SIZE) in range(p1Address, p1AddMax): - p2Address = random.randint(MEMORY_START_AT, MEMORY_END_AT - MAX_PROGRAM_SIZE) - - return p1Address, p2Address - - def pickFirstToPlay(self, p1Size=None, p2Size=None): - """ - This method is a simple helper that will choose the first player to play based - on the size of both binaries. Is case where both binaries are of the same size, - a random pick is made. - :param p1Size: - :param p2Size: - :return: - """ - if p1Size < p2Size: - self.setCurrentPlayer(player=self.p1Capua, - contextBank=self.p1ContextBank, - currentContext=self.p1CurrentContext) - elif p2Size < p1Size: - self.setCurrentPlayer(player=self.p2Capua, - contextBank=self.p2ContextBank, - currentContext=self.p2CurrentContext) - else: - randomChoice = random.randint(0, 1) - if randomChoice == 0: - self.setCurrentPlayer(player=self.p1Capua, - contextBank=self.p1ContextBank, - currentContext=self.p1CurrentContext) - else: - self.setCurrentPlayer(player=self.p2Capua, - contextBank=self.p2ContextBank, - currentContext=self.p2CurrentContext) - - def setCurrentPlayer(self, player, contextBank, currentContext): - """ - Simply sets the current game player. - :param player: Capua, a valid capua environment. - :param contextBank: List, a list with all a player contexts. - :param currentContext: int, a int indicating the currently executing context for a player. - :return: - """ - self.currentPlayer = player - self.currentContextBank = contextBank - self.currentContext = currentContext - - def getBinary(self, inputFile=None): - """ - This method simply gets the content of an on disk binary file. - :param inputFile: string, path to the file that needs to be loaded into memory. - :return: the content of the file in the form of a byte array - """ - content = b"" - - binFile = open(inputFile, "rb") - content = binFile.read() - binFile.close() - - return content - - def loadBinaryInMemory(self, capua=None, loadAddress=None, binaryPath=None): - """ - This will load a binary into the correct memory region. It will return the - size of the loaded binary - :param capua: - :param loadAddress: - :param binaryPath: - :return: - """ - content = self.getBinary(inputFile=binaryPath) - - memoryIndex = loadAddress - for dataByte in content: - capua.mioc.memoryWriteAtAddressForLength(address=memoryIndex, - length=1, - value=dataByte, - source=capua.eu.name) - memoryIndex += 1 # Move on to the next byte to be written - - # Prepare the core for execution - capua.eu.setupCore(I=loadAddress) - - return len(content) - - def play(self): - """ - This is the game being played. This method control the execution flow and scheduling for - all players and contexts. - :return: - """ - cycles = 0 - - stime = time.time() - while cycles < 1000000: # maximum of 1 000 000 instruction in a game - for i in range(0, 10): - # Activate current context - context = self.currentContextBank[self.currentContext] - - try: - # Execute one step in this context - context.execute() - except Exception as e: - if len(self.currentContextBank) == 1: - # When exception occurs, game is over if only a single - # context is available for that player... - playerName = context.name - message = "{} caused error '{}' - no more thread, \n\nGame Over\n".format(playerName,str(e)) - self.arena.displayGameStop(message=message) - etime = time.time() - print("Duration: " + str(etime-stime)) - return - else: - # Simply remove faulty context no need to do - # anything else, context will simply get flushed away... - playerName = context.name - message = "{} caused error '{}' - one less thread, game continues".format(playerName,str(e)) - self.arena.displayContextCrash(message=message, contextDump=context) - self.currentContextBank.remove(context) - time.sleep(5) - - # Save context information before we switch - - # Context switch to next context - if self.currentContext + 1 < len(self.currentContextBank): - # More context are available increment - self.currentContext += 1 - else: - # No more context available, switch back to 0 - self.currentContext = 0 - - # After each full run, update the "interface" (yeah this is an overstatement...) - if cycles % 5000 == 0: - self.arena.displayGameState(self.p1Capua, self.p2Capua) - time.sleep(1) - pass - # Now allow the other player to play! - self.switchPlayer() - cycles += 10 - - etime = time.time() - print("Duration: " + str(etime-stime)) - # If we are here, both players are still alive... - self.arena.displayGameStop(message="The game ended as a draw") - - def switchPlayer(self): - """ - Simple helper method to make player switch less ugly in the code. - :return: - """ - if self.currentPlayer == self.p1Capua: - # Save p1 information - self.p1CurrentContext = self.currentContext - self.p1ContextBank = self.currentContextBank - - # Load p2 information - self.setCurrentPlayer(player=self.p2Capua, - contextBank=self.p2ContextBank, - currentContext=self.p2CurrentContext) - else: - # Save p2 information - self.p2CurrentContext = self.currentContext - self.p2ContextBank = self.currentContextBank - - # Load p1 information - self.setCurrentPlayer(player=self.p1Capua, - contextBank=self.p1ContextBank, - currentContext=self.p1CurrentContext) diff --git a/GameOverlay/__init__.py b/GameOverlay/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Game.py b/HardDriveCreator.py similarity index 51% rename from Game.py rename to HardDriveCreator.py index 48375a5..64150e9 100644 --- a/Game.py +++ b/HardDriveCreator.py @@ -20,80 +20,70 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """ -from GameOverlay.Arena import Arena -from GameOverlay.GameManager import GameManager +from Configuration.Configuration import HARD_DRIVE_SECTOR_SIZE, \ + HARD_DRIVE_MAX_SIZE import argparse import os __author__ = "CSE" -__copyright__ = "Copyright 2015, CSE" +__copyright__ = "Copyright 2017, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" def parseCommandLineArgs(): """ - This simply parses the command line so we can get both players file information + As implied by the name, this will parse the command line arguments so we can use them. Important note, + after this function is called, no need to use the "extension" attribute since this one is concatenated + with the "output". This results in cleaner code since those two are always(? most of the time at least) + used together. :return: A parsed object as provided by argparse.parse_args() """ - parser = argparse.ArgumentParser(prog="Game.py", - description="Capua Assembler Version {}".format(__version__,), + parser = argparse.ArgumentParser(prog="HardDriveCreator.py", + description="Capua Hard Drive Creator Version {}".format(__version__,), epilog="This tool is provided as part of Spartacus learning environment under {} " "licence. Feel free to distribute, modify, " "contribute and learn!".format(__license__,)) - parser.add_argument("-1", "--p1", - required=True, - nargs=1, - type=str, - help="Define the player 1 binary file to be loaded") - parser.add_argument("-2", "--p2", + parser.add_argument("-o", "--output", required=True, nargs=1, type=str, - help="Define the player 2 binary file to be loaded") + help="Define the output where the hard drive file is to be created") args = parser.parse_args() - args.p1 = os.path.abspath(args.p1[0]) # This originally come out as a list - args.p2 = os.path.abspath(args.p2[0]) + args.output = args.output[0] return args def validatePaths(argsWithPaths): """ - This function will simply validate that both paths exists + This function will simply validate that the input path exists :param argsWithPaths: An input parsed object as provided by argparse.parse_args() :return: This does not return. Simply raises ValueError in cases where paths are not valid. """ - if not os.path.exists(argsWithPaths.p1): - raise ValueError("ERROR: file {} does not exists.".format(argsWithPaths.p1,)) - if not os.path.exists(argsWithPaths.p2): - raise ValueError("ERROR: file {} does not exists.".format(argsWithPaths.p2,)) + if os.path.exists(argsWithPaths.output): + raise ValueError("ERROR: file {} already exists.".format(argsWithPaths.output,)) if __name__ == '__main__': usableArgs = parseCommandLineArgs() - # import pdb; pdb.set_trace() + print(usableArgs) validatePaths(usableArgs) # Make sure the parsed info is usable before using it! - print("Game about to begin, following options will be used") - print(" Player 1 file: {}".format(usableArgs.p1,)) - print(" Player 2 file: {}".format(usableArgs.p2,)) - - p1UsableName = usableArgs.p1.split("\\")[-1] - p1UsableName = p1UsableName.split(".")[0] - - p2UsableName = usableArgs.p2.split("\\")[-1] - p2UsableName = p2UsableName.split(".")[0] - - arena = Arena() - arena.displayFirstInterface(p1=p1UsableName, p2=p2UsableName) - - gm = GameManager(p1File=usableArgs.p1, p1Name=p1UsableName, p2File=usableArgs.p2, p2Name=p2UsableName) + print("Hard drive creation about to begin, following options will be used") + print(" output file: {}".format(usableArgs.output,)) + f = open(usableArgs.output, "wb") + for i in range(0, HARD_DRIVE_MAX_SIZE): + f.write(b"\x00" * HARD_DRIVE_SECTOR_SIZE) + f.close() + if os.path.exists(usableArgs.output): + # The assembler did the job correctly and the out file has been written to disk! + print("Hard drive creation done, output file has been written to {}". format(usableArgs.output,)) diff --git a/Linker.py b/Linker.py index 3745c5c..9cb50ee 100644 --- a/Linker.py +++ b/Linker.py @@ -30,7 +30,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -63,7 +63,7 @@ def parseCommandLineArgs(): required=False, nargs=1, type=int, - default=DEFAULT_LOAD_ADDRESS, + default=[DEFAULT_LOAD_ADDRESS], help="Define the address at which a binary should be loaded. Don't mess this up, " "Capua does not currently have virtual addressing mode... This means that you" "HAVE TO MAKE SURE that your binary is loaded in a free memory region otherwise" @@ -96,6 +96,7 @@ def validatePaths(argsWithPaths): if not os.path.exists(file): raise ValueError("ERROR: file {} does not exists.".format(file,)) + if __name__ == '__main__': usableArgs = parseCommandLineArgs() validatePaths(usableArgs) # Make sure the parsed info is usable before using it! @@ -108,7 +109,7 @@ def validatePaths(argsWithPaths): linker = StaticFlatLinker(inputFileList=usableArgs.input, outputFile=usableArgs.output, - loadAddress=usableArgs.address, + loadAddress=usableArgs.address[0], softwareLoader=usableArgs.software, symbolsFile=symbolsFile) if os.path.exists(usableArgs.output): diff --git a/README.md b/README.md new file mode 100644 index 0000000..20e7ec4 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +![Spartacus Logo](Documentation/Images/spartacus-logo.jpg) + +Version française plus bas. +## What is spartacus? +Spartacus is an open source learning environment that aims in helping students learn the basis of +assembly programming and operating system development. It is portable and easy to learn. + +As of the current version (2.0) the game environment that was present in the original +project is no longer available. However, the virtual machine now has everything that +is required to write a basic operating system. + +NOTE: +This project has been tested using python 3.5.2 + +All documentation is available in the "Documentation" folder. +Please note that some code examples are available in the "testFiles" +folder. Also note, for those who like code coloring, a Notepad++ +syntax file is available at "Documentation/CapuaASM-NPPLanguage.xml". +Simply import that file into your "user defined" language in NPP +in order to benefit from code coloring for the Capua assembly language. + +The Assembler.py, Linker.py, Debugger.py and HardDriveCreator.py files are meant to +be used by the user. Simply invoke these from the command line in order +to be able to see how to use them. Help option = -h + +## Where to start? +There is a quick start guide in the documentation folder. + +## Contribution +Spartacus is still in its early age and is far from being entirely done. Multiple +things are still required to make this project as useful as it can be. If you are +interested in contributing, before you start writing code, please contact us and +we will exchange on your idea and vision. This is simply to ensure that what you +have in mind can be merge into Spartacus main branch. Here is a list of areas that +still require a lot of work: + + +* Virtual memory system (no support at current time) +* Floating point arithmetic (no support at current time) +* C (or other language) compiler (no compiler at current time) +* Debugger improvement (step into, step over, ...) +* ".o" file format improvement (current format is prone to bugs) +* Assembler rewrite + + +Please note that any contribution has to follow GPLv2 licence. Also, any contribution +should be reflected in the documentation for at least 1 of the languages. For example +if someone writes the code to support virtual memory, it is expected that this person +will contribute in updating the documentation so it reflects the changes made. + +Any code contribution not respecting these requirements will be rejected. + +## Folder structure +* CapuaEnvironment: + * This folder is where the VM code sits. +* Configuration: + * This folder holds multiple configuration elements used at multiple place inside the project +* Documentation: + * All non code documentation is in there +* ToolChain: + * NOTE ABOUT THE TOOL CHAIN: + This tool chain was originally built for testing purpose not for programmer. + Therefore, bugs are present and code is NOT fully tested. We are improving this + but there is still a lot of work to be done. The assembler and the linker are + more than likely going to be re-written. + * All tool chain related code is here. For example, the code allowing to parse and link files + is in there. The debugger code is there too. +* testFiles: + * This directory holds some code example +- - - +## Qu'est-ce que Spartacus? +Spartacus est un environnement libre visant à aider les étudiants à apprendre les bases +de la programmation assembleur et du développement de systèmes d'exploitation. C'est +un environnement portable et facile à apprendre. + +La version actuelle (2.0) ne comprend plus le code de l'environnement de jeu qui +était présent dans la version original du projet. Cependant, la machine virtuelle +comprend maintenant tous les éléments requis pour l'écriture d'un système d'exploitation +minimaliste. + +NOTE: +Ce projet fut testé à l'aide de python 3.5.2 + +L'ensemble de la documentation est disponible à l'intérieur du +dossier "Documentation". Notez que des exemples de code sont disponibles +à l'intérieur du dossier "testFiles". Un fichier de syntaxe Notepas++ +est aussi diponible à l'emplacement "Documentation/CapuaASM-NPPLanguage.xml". +Vous pouvez ajouter ce fichier à vos langage personnalisés dans NPP afin +de bénéficier de la coloration du code pour le langage assembleur Capua. + +Les fichiers Assembler.py, Linker.py, Debugger.py and HardDriveCreator.py ont +pour objectif d'être utiliser par l'utilisateur. Invoquez simplement ces fichiers +à partir de la ligne de commandes. Les options d'aides pour ceux-ci sont accessibles +à l'aide de l'option "-h". + +## Où commencer? +Un guide de démarrage rapide est présent dans le dossier "Documentation". + +## Contribution +Spartacus n'est pas encore un projet mature. Plusieurs éléments sont toujours +requis afin de rendre ce projet le plus utile possible. Si vous êtes intéressé +à contribuer, avant de commencer, veuillez nous contacter. Nous échangerons sur +votre idée et votre vision. Ceci a simplement pour objectif de s'assurer que +votre idée de développement peut être intégrée à l'intérieur du projet principal. +Voici une liste d'éléments qui demandent encore beaucoup de travail: + + +* Mémoire virtuelle (aucun support actuellement) +* Opérations en virgule flotyante (aucun support actuellement) +* Compilateur C (ou autre langage) (aucun support actuellement) +* Ajout au débugueur (step into, step over, ...) +* Amélioration du format".o" (current format is prone to bugs) +* Réécriture de l'assembleur + + +Please note that any contribution has to follow GPLv2 licence. Also, any contribution +should be reflected in the documentation for at least 1 of the languages. For example +if someone writes the code to support virtual memory, it is expected that this person +will contribute in updating the documentation so it reflects the changes made. + +Any code contribution not respecting these requirements will be rejected. + +## Structure de fichier +* CapuaEnvironment: + * Ce dossier contient le code de la machine virtuelle. +* Configuration: + * Ce dossier contient les fichiers de configuration utilisés à multiples endroits dans le projet. +* Documentation: + * Ce dossier contient toute la documentation +* ToolChain: + * NOTE AU SUJET DES OUTILS: + Les outils ont, à l'origine, été développé à des fins de test et validation + et non pas pour être utilisés par le programmeur. Ainsi, des problèmes sont + présents dans le code et ce dernier n'a pas été entièrement testé. Nous + travaillons à l'amélioration des outils mais plusieurs autres éléments demandent + aussi de l'attention. L'assembleur et l'éditeur de liens seront probablement + ré-écrits. + * L'ensemble du code relié aux outils de développement est ici. Par exemple, le code permettant + l'édition des liens et l'analyseur de code est dans ce répertoire. Le code du débogueur y est + aussi présent. +* testFiles: + * Ce répertoire contient des exemples de code. +- - - +This file is part of Spartacus project +Copyright (C) 2016 CSE + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/README.txt b/README.txt deleted file mode 100644 index 15afe62..0000000 --- a/README.txt +++ /dev/null @@ -1,49 +0,0 @@ -This file is part of Spartacus project -Copyright (C) 2016 CSE - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - - -NOTE: -This project has been tested using python 3.4 and 3.5 - -All documentation is available in the "Documentation" folder. -Please note that some code examples are available in the "testFiles" -folder. Also note, for those who like code coloring, a Notepad++ -syntax file is available at "Documentation/CapuaASM-NPPLanguage.xml". -Simply import that file into your "user defined" language in NPP -in order to benefit from code coloring for the Capua assembly language. - -The Assembler.py, Linker.py, Debugger.py and Game.py files are meant to -be used by the user. Simply invoque these from the command line in order -to be able to see how to use them. Help option = -h - -About the folders: -CapuaEnvironment: - This folder is where the VM code sits. -Configuration: - This folder holds multiple configuration elements used at multiple place inside the project -Documentation: - All non code documentation is in there -GameOverlay: - This is where the game code is. Both for the interface and game management. -ToolChain: - NOTE ABOUT THE TOOL CHAIN: - This tool chain was originally built for testing purpose not for programmer. - Therefore, bugs are present and code is NOT fully tested. - All tool chain related code is here. For example, the code allowing to parse and link files - is in there. The debugger code is there too. -testFiles: - This directory holds multiple code example that were used while developing the Spartacus project. diff --git a/ToolChain/Assembler/Assembler.py b/ToolChain/Assembler/Assembler.py index 500ec9c..461c55d 100644 --- a/ToolChain/Assembler/Assembler.py +++ b/ToolChain/Assembler/Assembler.py @@ -27,7 +27,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -73,23 +73,46 @@ def buildAssembledFile(self): fileContent += struct.pack(">I", self.parser.finalSize) fileContent += b"" - # External symbols allow for a file to be linked with another file - fileContent += b"" - for reference in self.parser.referenceDict: - if EXPORTED_REFERENCE_INDICATOR in reference: - refName = reference.split()[1] - fileContent += b"" + refName.encode("utf-8") + b"" - fileContent += b"" + struct.pack(">I", self.parser.referenceDict[reference]) + b"" - fileContent += b"" + validInternalRef = [] # This will be used to validate for good external reference + # This works because all reference (internal and external) end up + # listed in the internal references anyway. # Deal with internal symbols fileContent += b"" for reference in self.parser.referenceDict: if EXPORTED_REFERENCE_INDICATOR not in reference: + validInternalRef.append(reference) fileContent += b"" + reference.encode("utf-8") + b"" fileContent += b"" + struct.pack(">I", self.parser.referenceDict[reference]) + b"" + + # Update global offsets would it be required + # This is required since all global offsets are set to 0 at the moment they are parsed + wouldBeExported = EXPORTED_REFERENCE_INDICATOR + " " + reference + if wouldBeExported in self.parser.referenceDict.keys(): + self.parser.referenceDict[wouldBeExported] = self.parser.referenceDict[reference] + fileContent += b"" + # External symbols allow for a file to be linked with another file + fileContent += b"" + for reference in self.parser.referenceDict: + if EXPORTED_REFERENCE_INDICATOR in reference: + isValid = False + shortReference = reference.split()[1] + for internalReference in validInternalRef: + if shortReference == internalReference: + isValid = True + break + + if isValid: + refName = reference.split()[1] + fileContent += b"" + refName.encode("utf-8") + b"" + fileContent += b"" + struct.pack(">I", self.parser.referenceDict[reference]) + b"" + else: + raise ValueError("Unresolved reference: {} is exported but " + "is never declared.".format(reference)) + fileContent += b"" + # Deal with the text section fileContent += b"" for instruction in self.parser.instructionList: diff --git a/ToolChain/Assembler/Constants.py b/ToolChain/Assembler/Constants.py index d1592f5..d9e1b21 100644 --- a/ToolChain/Assembler/Constants.py +++ b/ToolChain/Assembler/Constants.py @@ -24,7 +24,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -36,6 +36,7 @@ EXPORTED_REFERENCE_INDICATOR = ".GLOBAL" DATA_NUMERIC_INDICATOR = ".DATANUMERIC" DATA_ALPHA_INDICATOR = ".DATAALPHA" +DATA_MEMORY_REFERENCE = ".DATAMEMREF" COMMENT_INDICATORS = ";" NUMERIC_DATA_INDICATOR = "int" diff --git a/ToolChain/Assembler/Parser/Parser.py b/ToolChain/Assembler/Parser/Parser.py index 8b5cfd0..56a1171 100644 --- a/ToolChain/Assembler/Parser/Parser.py +++ b/ToolChain/Assembler/Parser/Parser.py @@ -26,6 +26,10 @@ from Configuration.Configuration import REGISTER_A, \ REGISTER_B, \ REGISTER_C, \ + REGISTER_D, \ + REGISTER_E, \ + REGISTER_F, \ + REGISTER_G, \ REGISTER_S from ToolChain.Assembler.Constants import REGISTER_PREFIX, \ IMMEDIATE_PREFIX, \ @@ -35,6 +39,7 @@ MEMORY_REFERENCE_INDICATORS, \ DATA_ALPHA_INDICATOR, \ DATA_NUMERIC_INDICATOR, \ + DATA_MEMORY_REFERENCE, \ EXPORTED_REFERENCE_INDICATOR import struct @@ -43,7 +48,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -182,6 +187,9 @@ def _parseCodeLine(self, line: list=None): # This is a numeric data field lineInstruction = struct.pack(">I", buildInstruction.sourceImmediate) buildInstruction.instructionCode = 0 # Calling code expect this to be non STR for non mem ref + elif DATA_MEMORY_REFERENCE in buildInstruction.instructionCode: + lineInstruction = buildInstruction.sourceImmediate + buildInstruction.instructionCode = 0 else: lineInstruction = None @@ -193,7 +201,8 @@ def translateRegisterNameToRegisterCode(self, registerName: str=""): A = 0x00 B = 0x01 C = 0x10 - S = 0x11 + ... + S = 0x1111 Throws error if register is not A, B, C or S :param registerName: str, representing the register that needs translation :return: int, the int that represents the register @@ -207,6 +216,14 @@ def translateRegisterNameToRegisterCode(self, registerName: str=""): registerCode = REGISTER_B elif registerName == "C": registerCode = REGISTER_C + elif registerName == "D": + registerCode = REGISTER_D + elif registerName == "E": + registerCode = REGISTER_E + elif registerName == "F": + registerCode = REGISTER_F + elif registerName == "G": + registerCode = REGISTER_G elif registerName == "S": registerCode = REGISTER_S else: @@ -297,7 +314,7 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): foundSource = False # Found source operand foundDestination = False # Keep us from abusive operation - if (len(line) == 1 and MEMORY_REFERENCE_INDICATORS == line[0][-1]) or COMMENT_INDICATORS == line[0][0]: + if (MEMORY_REFERENCE_INDICATORS == line[0][-1]) or COMMENT_INDICATORS == line[0][0]: # This is no code line... No need for further work, return! # We keep the comment indicator so we can later discard the instruction. # Put text into instructionCode since this is an impossible case... Easy to @@ -335,13 +352,22 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): buildInstruction.instructionLength = len(alpha) buildInstruction.instructionCode = line[0].upper() return + if DATA_MEMORY_REFERENCE in buildInstruction.operationMnemonic: + # This is a memory reference used as a constant that needs to be + # linked later in the process. + if line[1][0] == ":": + line[1] = line[1][1:] + buildInstruction.sourceImmediate = (":" + line[1] + ":").upper().encode("utf-8") + buildInstruction.instructionLength = 4 + buildInstruction.instructionCode = DATA_MEMORY_REFERENCE + return # We don't want to redo the first part. That would cause problem with in code memory references. for part in line[1:]: if part[0] is COMMENT_INDICATORS: break # We are done with this line of code, we found a comment - if part[0] is FLAGS_INDICATORS[0]: + elif part[0] is FLAGS_INDICATORS[0]: if part[-1] is FLAGS_INDICATORS[-1]: # This is FLAGS Indicator!! buildInstruction.flags = self.translateTextFlagsToCodeFlags(part[1:-1]) @@ -350,7 +376,7 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): FLAGS_INDICATORS[-1])) continue - if part[0] is WIDTH_INDICATORS[0]: + elif part[0] is WIDTH_INDICATORS[0]: if part[-1] is WIDTH_INDICATORS[-1]: # This is WIDTH Indicator!! buildInstruction.width = self.translateTextImmediateToImmediate(part[1:-1]) # Reusing immediate logic @@ -359,7 +385,7 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): WIDTH_INDICATORS[-1])) continue - if part[0] is REGISTER_PREFIX: + elif part[0] is REGISTER_PREFIX: # This is a register if not foundSource: foundSource = True @@ -371,7 +397,7 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): buildInstruction.destinationRegister = self.translateRegisterNameToRegisterCode(part[1:]) continue - if part[0] is IMMEDIATE_PREFIX: + elif part[0] is IMMEDIATE_PREFIX: # This is an immediate if not foundSource: foundSource = True @@ -383,20 +409,20 @@ def _evaluateAndExtractInfoFromLine(self, line, buildInstruction): buildInstruction.destinationImmediate = self.translateTextImmediateToImmediate(part[1:]) continue - if part[0] is MEMORY_REFERENCE_INDICATORS: + else: # This is a memory reference to be treated as an immediate + if part[0] == ":": + part = part[1:] if not foundSource: foundSource = True - buildInstruction.sourceImmediate = part.upper() + MEMORY_REFERENCE_INDICATORS + buildInstruction.sourceImmediate = MEMORY_REFERENCE_INDICATORS+ part.upper() + MEMORY_REFERENCE_INDICATORS else: if foundDestination: raise ValueError("Invalid operation format") foundDestination = True - buildInstruction.destinationImmediate = part.upper() + MEMORY_REFERENCE_INDICATORS + buildInstruction.destinationImmediate = MEMORY_REFERENCE_INDICATORS + part.upper() + MEMORY_REFERENCE_INDICATORS continue - raise ValueError("Unexpected code line format detected {}".format(line)) - def _findPossibleCodesForInstruction(self, partialInstruction: Instruction=None): """ This will find the possible instruction codes for a given partial instruction. diff --git a/ToolChain/Assembler/Parser/test_Parser.py b/ToolChain/Assembler/Parser/test_Parser.py index 25a5d5d..c0ee1fd 100644 --- a/ToolChain/Assembler/Parser/test_Parser.py +++ b/ToolChain/Assembler/Parser/test_Parser.py @@ -30,7 +30,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -490,7 +490,7 @@ def test_parseCodeLineMemr(self): self.assertEqual(buildInstruction.width, 0x04) self.assertEqual(buildInstruction.flags, None) self.assertEqual(buildInstruction.operationMnemonic, "MEMR") - self.assertEqual(buildInstruction.instructionLength, 2) + self.assertEqual(buildInstruction.instructionLength, 3) buildInstruction, lineInstruction = self.parser._parseCodeLine(["MEMR", "[3]", "$A", "$B"]) self.assertEqual(buildInstruction.instructionCode, 0b00010000) @@ -501,7 +501,7 @@ def test_parseCodeLineMemr(self): self.assertEqual(buildInstruction.width, 0x03) self.assertEqual(buildInstruction.flags, None) self.assertEqual(buildInstruction.operationMnemonic, "MEMR") - self.assertEqual(buildInstruction.instructionLength, 2) + self.assertEqual(buildInstruction.instructionLength, 3) buildInstruction, lineInstruction = self.parser._parseCodeLine(["MEMR", "[2]", "$A", "$B"]) self.assertEqual(buildInstruction.instructionCode, 0b00010000) @@ -512,7 +512,7 @@ def test_parseCodeLineMemr(self): self.assertEqual(buildInstruction.width, 0x02) self.assertEqual(buildInstruction.flags, None) self.assertEqual(buildInstruction.operationMnemonic, "MEMR") - self.assertEqual(buildInstruction.instructionLength, 2) + self.assertEqual(buildInstruction.instructionLength, 3) buildInstruction, lineInstruction = self.parser._parseCodeLine(["MEMR", "[1]", "$A", "$B"]) self.assertEqual(buildInstruction.instructionCode, 0b00010000) @@ -523,7 +523,7 @@ def test_parseCodeLineMemr(self): self.assertEqual(buildInstruction.width, 0x01) self.assertEqual(buildInstruction.flags, None) self.assertEqual(buildInstruction.operationMnemonic, "MEMR") - self.assertEqual(buildInstruction.instructionLength, 2) + self.assertEqual(buildInstruction.instructionLength, 3) buildInstruction, lineInstruction = self.parser._parseCodeLine(["MEMR", "[4]", "#0xFF", "$B"]) self.assertEqual(buildInstruction.instructionCode, 0b00000001) @@ -584,7 +584,7 @@ def test_parseCodeLineMemw(self): self.assertEqual(buildInstruction.width, 0x04) self.assertEqual(buildInstruction.flags, None) self.assertEqual(buildInstruction.operationMnemonic, "MEMW") - self.assertEqual(buildInstruction.instructionLength, 2) + self.assertEqual(buildInstruction.instructionLength, 3) buildInstruction, lineInstruction = self.parser._parseCodeLine(["MEMW", "[4]", "#0xFF", "$B"]) self.assertEqual(buildInstruction.instructionCode, 0b00000000) @@ -984,8 +984,12 @@ def test_translateRegisterNameToRegisterCode(self): self.assertEqual(0b00, self.parser.translateRegisterNameToRegisterCode(registerName="A")) self.assertEqual(0b01, self.parser.translateRegisterNameToRegisterCode(registerName="B")) self.assertEqual(0b10, self.parser.translateRegisterNameToRegisterCode(registerName="C")) - self.assertEqual(0b11, self.parser.translateRegisterNameToRegisterCode(registerName="S")) - self.assertRaises(ValueError, self.parser.translateRegisterNameToRegisterCode, "D") # Invalid register + self.assertEqual(0b11, self.parser.translateRegisterNameToRegisterCode(registerName="D")) + self.assertEqual(0b100, self.parser.translateRegisterNameToRegisterCode(registerName="E")) + self.assertEqual(0b101, self.parser.translateRegisterNameToRegisterCode(registerName="F")) + self.assertEqual(0b110, self.parser.translateRegisterNameToRegisterCode(registerName="G")) + self.assertEqual(0b1111, self.parser.translateRegisterNameToRegisterCode(registerName="S")) + self.assertRaises(ValueError, self.parser.translateRegisterNameToRegisterCode, "x") # Invalid register def test_translateTextImmediateToImmediate(self): """ @@ -1060,6 +1064,11 @@ def test_findPossibleCodesForInstruction(self): Test the method: _findPossibleCodesForInstruction(self, partialInstruction: Instruction=None): """ + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "ACTI" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b11110001]) + partialInstruction = Instruction(skipValidation=True) partialInstruction.operationMnemonic = "ADD" codes = self.parser._findPossibleCodesForInstruction(partialInstruction) @@ -1080,11 +1089,26 @@ def test_findPossibleCodesForInstruction(self): codes = self.parser._findPossibleCodesForInstruction(partialInstruction) self.assertEqual(codes, [0b01101000, 0b10011010]) + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "DACTI" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b11110010]) + partialInstruction = Instruction(skipValidation=True) partialInstruction.operationMnemonic = "DIV" codes = self.parser._findPossibleCodesForInstruction(partialInstruction) self.assertEqual(codes, [0b10010101]) + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "HIRET" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b11110011]) + + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "INT" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b01110110, 0b10000011]) + partialInstruction = Instruction(skipValidation=True) partialInstruction.operationMnemonic = "JMP" codes = self.parser._findPossibleCodesForInstruction(partialInstruction) @@ -1145,6 +1169,16 @@ def test_findPossibleCodesForInstruction(self): codes = self.parser._findPossibleCodesForInstruction(partialInstruction) self.assertEqual(codes, [0b11110000]) + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "SFSTOR" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b01000010, 0b01010010]) + + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "SIVR" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b01110101]) + partialInstruction = Instruction(skipValidation=True) partialInstruction.operationMnemonic = "SHL" codes = self.parser._findPossibleCodesForInstruction(partialInstruction) @@ -1170,7 +1204,34 @@ def test_findPossibleCodesForInstruction(self): codes = self.parser._findPossibleCodesForInstruction(partialInstruction) self.assertEqual(codes, [0b01100011, 0b10010000]) - self.assertEqual(22, len(operationDescription)) # Just validate that nothing was added without changing test case... + partialInstruction = Instruction(skipValidation=True) + partialInstruction.operationMnemonic = "SFSTOR" + codes = self.parser._findPossibleCodesForInstruction(partialInstruction) + self.assertEqual(codes, [0b01000010, 0b01010010]) + + self.assertEqual(28, len(operationDescription)) # Validate that nothing was added without changing test case + + def test_getInstructionCodeAndFormUsingPossibleCodesActi(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # ACTI + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "ACTI" + buildInstruction.sourceRegister = None + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = None + possibleCodes = [0b11110001] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes(buildInstruction, + possibleCodes) + self.assertEqual(0b11110001, instructionCode) + self.assertEqual("Ins", instructionForm) def test_getInstructionCodeAndFormUsingPossibleCodesAdd(self): """ @@ -1260,6 +1321,28 @@ def test_getInstructionCodeAndFormUsingPossibleCodesCmp(self): self.assertEqual(0b10011010, instructionCode) self.assertEqual("InsRegReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesDacti(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # DACTI + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "DACTI" + buildInstruction.sourceRegister = None + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = None + possibleCodes = [0b11110010] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes(buildInstruction, + possibleCodes) + self.assertEqual(0b11110010, instructionCode) + self.assertEqual("Ins", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesDiv(self): """ Test the method: @@ -1282,6 +1365,50 @@ def test_getInstructionCodeAndFormUsingPossibleCodesDiv(self): self.assertEqual(0b10010101, instructionCode) self.assertEqual("InsRegReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesHiret(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # INT + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "HIRET" + buildInstruction.sourceRegister = None + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = None + possibleCodes = [0b11110011] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes(buildInstruction, + possibleCodes) + self.assertEqual(0b11110011, instructionCode) + self.assertEqual("Ins", instructionForm) + + def test_getInstructionCodeAndFormUsingPossibleCodesInt(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # INT + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "INT" + buildInstruction.sourceRegister = 0b111 + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = None + possibleCodes = [0b01110110, 0b10000011] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes(buildInstruction, + possibleCodes) + self.assertEqual(0b01110110, instructionCode) + self.assertEqual("InsReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesJmp(self): """ Test the method: @@ -1333,7 +1460,7 @@ def test_getInstructionCodeAndFormUsingPossibleCodesMemr(self): partialInstruction: Instruction=None, possibleCodes: list=None): """ - # Memr + # MEMR buildInstruction = Instruction(skipValidation=True) buildInstruction.operationMnemonic = "MEMR" buildInstruction.sourceRegister = 0 @@ -1546,6 +1673,28 @@ def test_getInstructionCodeAndFormUsingPossibleCodesRet(self): self.assertEqual(0b11110000, instructionCode) self.assertEqual("Ins", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesSfstor(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # SFSTOR + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "SFSTOR" + buildInstruction.sourceRegister = 0b10 + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = 0b100 + possibleCodes = [0b01000010, 0b01010010] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes(buildInstruction, + possibleCodes) + self.assertEqual(0b01010010, instructionCode) + self.assertEqual("InsFlagReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesShl(self): """ Test the method: @@ -1590,6 +1739,29 @@ def test_getInstructionCodeAndFormUsingPossibleCodesShr(self): self.assertEqual(0b10011001, instructionCode) self.assertEqual("InsRegReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesSivr(self): + """ + Test the method: + _getInstructionCodeAndFormUsingPossibleCodes(self, + partialInstruction: Instruction=None, + possibleCodes: list=None): + """ + # SIVR + buildInstruction = Instruction(skipValidation=True) + buildInstruction.operationMnemonic = "SIVR" + buildInstruction.sourceRegister = 0 + buildInstruction.sourceImmediate = None + buildInstruction.destinationRegister = None + buildInstruction.destinationImmediate = None + buildInstruction.width = None + buildInstruction.flags = None + possibleCodes = [0b01110101] + instructionCode, instructionForm = self.parser._getInstructionCodeAndFormUsingPossibleCodes( + buildInstruction, + possibleCodes) + self.assertEqual(0b01110101, instructionCode) + self.assertEqual("InsReg", instructionForm) + def test_getInstructionCodeAndFormUsingPossibleCodesSnt(self): """ Test the method: diff --git a/ToolChain/Debugger/Debugger.py b/ToolChain/Debugger/Debugger.py index 62b9456..b3f08f9 100644 --- a/ToolChain/Debugger/Debugger.py +++ b/ToolChain/Debugger/Debugger.py @@ -24,7 +24,12 @@ from Configuration.Configuration import REGISTER_A, \ REGISTER_B, \ REGISTER_C, \ - REGISTER_S + REGISTER_D, \ + REGISTER_E, \ + REGISTER_F, \ + REGISTER_G, \ + REGISTER_S, \ + DEBUGGER_WAKEUP_TICK_COUNT from ToolChain.Linker.Constants import DEFAULT_LOAD_ADDRESS @@ -34,7 +39,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -164,7 +169,7 @@ def setupLoggingFacilities(self, outputFile=None): :return: """ if outputFile is not None: - self.outputFile = open(outputFile, "w") + self.outputFile = open(outputFile[0], "w") def tearDownLoggingFacilities(self): """ @@ -234,6 +239,7 @@ def debug(self, inputFile=None): userCommand = input("{}: ".format((inputFile,))) self.debugLog(userCommand) if userCommand == "quit" or userCommand == "exit": + self.capua.eu.halt() break self.runUserCommand(command=userCommand) @@ -314,6 +320,14 @@ def convertNumericRegisterToRegisterName(self, numericRegister: int=None): result = "B" elif numericRegister == REGISTER_C: result = "C" + elif numericRegister == REGISTER_D: + result = "D" + elif numericRegister == REGISTER_E: + result = "E" + elif numericRegister == REGISTER_F: + result = "F" + elif numericRegister == REGISTER_G: + result = "G" elif numericRegister == REGISTER_S: result = "S" @@ -330,6 +344,10 @@ def displayCPUInformation(self, register: str=None): self.debugLog("{} = {} {}".format("A ", self.capua.eu.A, hex(self.capua.eu.A),)) self.debugLog("{} = {} {}".format("B ", self.capua.eu.B, hex(self.capua.eu.B),)) self.debugLog("{} = {} {}".format("C ", self.capua.eu.C, hex(self.capua.eu.C),)) + self.debugLog("{} = {} {}".format("D ", self.capua.eu.D, hex(self.capua.eu.D),)) + self.debugLog("{} = {} {}".format("E ", self.capua.eu.E, hex(self.capua.eu.E),)) + self.debugLog("{} = {} {}".format("F ", self.capua.eu.F, hex(self.capua.eu.F),)) + self.debugLog("{} = {} {}".format("G ", self.capua.eu.G, hex(self.capua.eu.G),)) self.debugLog("{} = {} {}".format("S ", self.capua.eu.S, hex(self.capua.eu.S),)) self.debugLog("{} = {} {}".format("I ", self.capua.eu.I, hex(self.capua.eu.I),)) self.debugLog("{} = {} {}".format("FLAGS", self.capua.eu.FLAGS, bin(self.capua.eu.FLAGS),)) @@ -478,7 +496,7 @@ def showSymbols(self): def runToBreakPoint(self): """ - This will run until a breakpoint is reached. It will also break every 500 tick + This will run until a breakpoint is reached. It will also break every DEBUGGER_WAKEUP_TICK_COUNT tick :return: """ tickCounter = 0 @@ -488,9 +506,9 @@ def runToBreakPoint(self): # Break point reached break first = False - if tickCounter == 500: + if 0 < DEBUGGER_WAKEUP_TICK_COUNT == tickCounter: # Just check if user want to go back to single step mode - self.debugLog("500 instructions executed since last break, " + self.debugLog(str(DEBUGGER_WAKEUP_TICK_COUNT) + " instructions executed since last break, " "do you want to go back to single step (y/n): ") answer = input("") if answer == "y": diff --git a/ToolChain/Linker/AssembledParsedFile.py b/ToolChain/Linker/AssembledParsedFile.py index 6815479..798144b 100644 --- a/ToolChain/Linker/AssembledParsedFile.py +++ b/ToolChain/Linker/AssembledParsedFile.py @@ -28,7 +28,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -119,7 +119,7 @@ def _buildDictFromInnerReferenceData(self, data=b""): result = {} # Regular expression used to do the extraction - reRefName = b"[A-Z_0-9]{1,15}" + reRefName = b"[A-Z_0-9]{1,}" reRefAdd = b"[\x00-\xFF]{4}" # Extract the info into two lists diff --git a/ToolChain/Linker/Constants.py b/ToolChain/Linker/Constants.py index 1839b6b..d5ae190 100644 --- a/ToolChain/Linker/Constants.py +++ b/ToolChain/Linker/Constants.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" diff --git a/ToolChain/Linker/StaticFlatLinker.py b/ToolChain/Linker/StaticFlatLinker.py index 6a462a1..2aa1fc0 100644 --- a/ToolChain/Linker/StaticFlatLinker.py +++ b/ToolChain/Linker/StaticFlatLinker.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright 2015, CSE" __credits__ = ["CSE"] __license__ = "GPL" -__version__ = "1.3" +__version__ = "2.0" __maintainer__ = "CSE" __status__ = "Dev" @@ -191,7 +191,7 @@ def _isThereSomeUnresolvedReferences(self): foundUnresolved = False reference = b"" - referenceRe = b":[A-Z_0-9]{1,15}:" + referenceRe = b":[A-Z_0-9]{1,}:" result = re.search(referenceRe, self.finalFileContent) if result is not None: diff --git a/testFiles/Hello.casm b/testFiles/Hello.casm new file mode 100644 index 0000000..fc09b09 --- /dev/null +++ b/testFiles/Hello.casm @@ -0,0 +1,39 @@ +; This is a hello world file for Capua +.global start + +start: + MOV end $S + MOV #0 $A + MOV hello $C + MOV #12 $d + CALL display_Line + +endLessLoop: + JMP <> endLessLoop + +hello: +.dataAlpha Hello World! + +; display_Line($A=line, $C=text, $D=len) +display_Line: + PUSH $E + PUSH $F + PUSH $G + XOR $E $E + MOV #80 $F + MUL $F $A + ADD #0x20001000 $A +dl_print_loop: + MEMR [1] $C $G + MEMW [1] $G $A + ADD #1 $A + ADD #1 $C + SUB #1 $D + CMP #0 $D + JMP dl_print_loop + POP $G + POP $F + POP $E + RET + +end: \ No newline at end of file diff --git a/testFiles/Length.casm b/testFiles/Length.casm new file mode 100644 index 0000000..9130bce --- /dev/null +++ b/testFiles/Length.casm @@ -0,0 +1,40 @@ +; File strlen.casm +; This will calculate the length of testString +.global start +start: + JMP <> stackSetup ;Jump over the string + +testString: +.dataAlpha This is a test string + +stackSetup: + MOV stack $S ;Stack is now usable +codeStart: + PUSH testString + CALL strlen + SUB #0x4 $S ;stack reset +loop: + JMP <> loop + +end: + +;Following is the length calculation +;strlen(stringPointer) +strlen: + MOV $S $A + SUB #0x4 $A ;Calculate parameter offset + MEMR [4] $A $A ;Get parameter in register A + MOV $A $C ;Keep pointer to string start +lenover: + MEMR [1] $A $B + CMP #0x00 $B ;are we at the end of the string? + JMP gotlen + ADD #0x1 $A + JMP <> lenover ;not at the end, jump back +gotlen: + SUB $C $A ;A will hold the len of the string at this point. + RET ;return value in register A + +; Stack is at the end of the program. +; No risk of overwriting the program +stack: diff --git a/testFiles/LoneBall/build.bashScript b/testFiles/LoneBall/build.bashScript new file mode 100755 index 0000000..02555e3 --- /dev/null +++ b/testFiles/LoneBall/build.bashScript @@ -0,0 +1,8 @@ +#!/bin/bash + +python3 ../../Assembler.py -i LoneBall.casm -o LoneBall.o +python3 ../../Assembler.py -i keyboard.casm -o keyboard.o +python3 ../../Assembler.py -i helper.casm -o helper.o +python3 ../../Assembler.py -i display.casm -o display.o + +python3 ../../Linker.py -i LoneBall.o keyboard.o helper.o display.o -o LoneBall.bin diff --git a/testFiles/LoneBall/display.casm b/testFiles/LoneBall/display.casm new file mode 100644 index 0000000..68c9aac --- /dev/null +++ b/testFiles/LoneBall/display.casm @@ -0,0 +1,68 @@ +; This file contains code required to manage the display + +.global display_Line +.global display_Char +.global display_Clear +.global display_Clear_Col + +; display_Line($A=line, $C=text, $D=len) +display_Line: + PUSH $E + PUSH $F + PUSH $G + XOR $E $E + MOV #80 $F + MUL $F $A + ADD #0x20001000 $A +dl_print_loop: + MEMR [1] $C $G + MEMW [1] $G $A + ADD #1 $A + ADD #1 $C + SUB #1 $D + CMP #0 $D + JMP dl_print_loop + POP $G + POP $F + POP $E + RET + +; display_Char($A=xpos, $C=ypos, $D=char) +display_Char: + push $E + push $F + MOV $A $E + MOV #80 $F + MUL $F $C + ADD $A $E ; Here, $E contains the y offset in screen display buffer + ADD #0x20001000 $E ; Address in display buffer is in $E + MEMW [1] $D $E + pop $F + pop $E + RET + +; display_Clear() +display_Clear: + MOV #0x20001000 $A + MOV #2000 $B + MOV #0 $C +dc_loop: + MEMW [4] #0x20202020 $A + ADD #4 $C + ADD #4 $A + CMP $C $B + JMP dc_loop + RET + +; display_Clear_Col($A = colx) +display_Clear_Col: + MOV #25 $C + MOV #0x20001000 $B + ADD $A $B +dcc_loop: + MEMW [1] #0x20 $B + ADD #80 $B + SUB #1 $C + CMP #0 $C + JMP dcc_loop + RET \ No newline at end of file diff --git a/testFiles/LoneBall/helper.casm b/testFiles/LoneBall/helper.casm new file mode 100644 index 0000000..d033033 --- /dev/null +++ b/testFiles/LoneBall/helper.casm @@ -0,0 +1,80 @@ +; This file contains various functions made to ease the programming + +.global helper_saveRegs +.global helper_restoreRegs +.global helper_integerToAlpha +.global helper_stringLength + +; helper_saveRegs() +; Use of this function to save all registers (but not $S-$A-$B-$C at the same time +; use of this function NEEDS to be perfectly balanced with helper_restoreRegs +helper_saveRegs: + PUSH $D + PUSH $E + PUSH $F + PUSH $G + SUB #16 $S + MEMR [4] $S $D + ADD #20 $S + MEMW [4] $D $S + RET + +; helper_restoreRegs($A=xpos, $C=ypos, $D=char) +; Use this function to restore all registers save by the helper_saveRegs function +; use of this function NEEDS to be perfectly balanced with helper_saveRegs +helper_restoreRegs: + MEMR [4] $S $D + SUB #20 $S + MEMW [4] $D $S + ADD #16 $S + POP $G + POP $F + POP $E + POP $D + RET + +; helper_integerToAlpha($A=intvalue, $C=destBuffer) +; Use this function to translate a numeric value to text to be used for display +helper_integerToAlpha: + PUSH $E + PUSH $D + PUSH $F + XOR $F $F + MOV #10 $D ; Conversion do decimal +itoa_loop: + DIV $A $D + MOV #48 $E ; Code for char '0' + ADD $B $E + ADD #1 $F + PUSH $E + CMP #0 $A + JMP itoa_loop +itoa_ordering_loop: + POP $E ; Just a quick trick to get the string in the correct order + MEMW [1] $E $C + ADD #1 $C + SUB #1 $F + CMP #0 $F + JMP itoa_ordering_loop + MEMW [1] #0 $C ; String termination + POP $F + POP $D + POP $E + RET + +; helper_stringLength($A=PointerToString) return string length in $A +helper_stringLength: + PUSH $D ; Save $D since only A B C are volatile + XOR $D $D + MOV $A $B +sl_oneMoreChar: + MEMR [1] $B $C + CMP #0 $C ; Check for string termination + JMP sl_GotLength ; Found end of string + ADD #1 $D ; Increment length + ADD #1 $B ; Increment buffer position + JMP <> sl_oneMoreChar +sl_GotLength: + MOV $D $A + POP $D + RET diff --git a/testFiles/LoneBall/keyboard.casm b/testFiles/LoneBall/keyboard.casm new file mode 100644 index 0000000..338151b --- /dev/null +++ b/testFiles/LoneBall/keyboard.casm @@ -0,0 +1,125 @@ +; This file contains tables that allow for scan code to char matching. +; Scan codes are used as offset into the scanCodeOffsetTable. The data +; found at that offset is then used as an offset into charTable where +; code for a specific character can be found. + +.global key_scanCodeOffsetTable +.global key_scanCodeEnter +.global key_charTable +.global key_getCharFromCode + +; getCharFromCode($A=Code), return done in A +key_getCharFromCode: + SHL #0x2 $A ; Equivalent to $A * 4 - Required since the table is 32 fields + MOV key_scanCodeOffsetTable $B + ADD $A $B ; This give the correct offset in scanCodeOffsetTable + MEMR [4] $B $A ; Offset into charTable is now in $A + SHL #0x2 $A ; Equivalent to $A * 4 - Required since the table is 32 fields + MOV key_charTable $B + ADD $A $B + MEMR [4] $B $A + RET + + +key_scanCodeOffsetTable: +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0x0 ; 1 +.dataNumeric 0x1 ; 2 +.dataNumeric 0x2 ; 3 +.dataNumeric 0x3 ; 4 +.dataNumeric 0x4 ; 5 +.dataNumeric 0x5 ; 6 +.dataNumeric 0x6 ; 7 +.dataNumeric 0x7 ; 8 +.dataNumeric 0x8 ; 9 +.dataNumeric 0x9 ; 0 +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xa ; q +.dataNumeric 0xb ; w +.dataNumeric 0xc ; e +.dataNumeric 0xd ; r +.dataNumeric 0xe ; t +.dataNumeric 0xf ; y +.dataNumeric 0x10 ; u +.dataNumeric 0x11 ; i +.dataNumeric 0x12 ; o +.dataNumeric 0x13 ; p +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0x24 ; \n +.dataNumeric 0x14 ; a +.dataNumeric 0x15 ; s +.dataNumeric 0x16 ; d +.dataNumeric 0x17 ; f +.dataNumeric 0x18 ; g +.dataNumeric 0x19 ; h +.dataNumeric 0x1a ; j +.dataNumeric 0x1b ; k +.dataNumeric 0x1c ; l +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0xff +.dataNumeric 0x1d ; z +.dataNumeric 0x1e ; x +.dataNumeric 0x1f ; c +.dataNumeric 0x20 ; v +.dataNumeric 0x21 ; b +.dataNumeric 0x22 ; n +.dataNumeric 0x23 ; m + +key_scanCodeEnter: +.dataNumeric 0x24 + +key_charTable: +.dataNumeric 0x31 ; 1 +.dataNumeric 0x32 ; 2 +.dataNumeric 0x33 ; 3 +.dataNumeric 0x34 ; 4 +.dataNumeric 0x35 ; 5 +.dataNumeric 0x36 ; 6 +.dataNumeric 0x37 ; 7 +.dataNumeric 0x38 ; 8 +.dataNumeric 0x39 ; 9 +.dataNumeric 0x30 ; 0 +.dataNumeric 0x71 ; q +.dataNumeric 0x77 ; w +.dataNumeric 0x65 ; e +.dataNumeric 0x72 ; r +.dataNumeric 0x74 ; t +.dataNumeric 0x79 ; y +.dataNumeric 0x75 ; u +.dataNumeric 0x69 ; i +.dataNumeric 0x6f ; o +.dataNumeric 0x70 ; p +.dataNumeric 0x61 ; a +.dataNumeric 0x73 ; s +.dataNumeric 0x64 ; d +.dataNumeric 0x66 ; f +.dataNumeric 0x67 ; g +.dataNumeric 0x68 ; h +.dataNumeric 0x6a ; j +.dataNumeric 0x6b ; k +.dataNumeric 0x6c ; l +.dataNumeric 0x7a ; z +.dataNumeric 0x78 ; x +.dataNumeric 0x63 ; c +.dataNumeric 0x76 ; v +.dataNumeric 0x62 ; b +.dataNumeric 0x6e ; n +.dataNumeric 0x6d ; m +.dataNumeric 0x10 ; \n \ No newline at end of file diff --git a/testFiles/clock.casm b/testFiles/clock.casm deleted file mode 100644 index 2f8de3a..0000000 --- a/testFiles/clock.casm +++ /dev/null @@ -1,8 +0,0 @@ -; This is a simple Clock access test -.global start: - -start: - MOV #0x20000100 $A ; This is the address of the memory mapped clock - MEMR [4] $A $B ; At this point, the time will be in register B - NOP -end: \ No newline at end of file diff --git a/testFiles/fibonaccie.casm b/testFiles/fibonaccie.casm deleted file mode 100644 index 5914ab1..0000000 --- a/testFiles/fibonaccie.casm +++ /dev/null @@ -1,18 +0,0 @@ -; This will calculate Fibonaccie number -.global start: - -codeStart: -MOV #0 $A -MOV #1 $B - - -fiboStart: -MOV $A $C -ADD $B $C -MOV $B $A -MOV $C $B -MOV $A $S ;Current fibo number will be in $S after this instruction -JMP <> :fiboStart -MOV #1 $A -MOV #2 $B -end: \ No newline at end of file diff --git a/testFiles/instructionRound.casm b/testFiles/instructionRound.casm deleted file mode 100644 index b129573..0000000 --- a/testFiles/instructionRound.casm +++ /dev/null @@ -1,193 +0,0 @@ -; This is an attempt at using as many instructions as possible - -.global start: - -start: -MOV #0x40010000 $B - -;MemRW -MEMW [1] #0xFF $B -MEMR [1] #0x40010000 $A -MEMR [1] $B $C - -MOV #0xAA $A -MEMW [1] $A $B -MEMW [1] #0xBB #0x40020000 -MOV #6 $A - -;JumpR -JMPR <> #2 ;Jump over the next line -JMPR <> $A ;Jump over the next line -JMPR <> #-8 ;Jump back one line - -MOV #6 $A -MOV #6 $B -CMP $A $B - -JMPR #2 ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #6 $A -MOV #5 $B -CMP $A $B - -JMPR #2 ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #4 $A -MOV #5 $B -CMP $A $B - -JMPR #2 ;Jump the next line -JMPR <> $A ;Jump over the next line - -XOR $B $B - -;JumpRR -MOV #2 $C -MOV #6 $A -MOV #6 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #6 $A -MOV #5 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #4 $A -MOV #5 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -XOR $B $B - -;JumpRRR -MOV #2 $C -MOV #6 $A -MOV #6 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #6 $A -MOV #5 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -MOV #4 $A -MOV #5 $B -CMP $A $B - -JMPR $C ;Jump the next line -JMPR <> $A ;Jump over the next line - -XOR $B $B - -immreg: -MOV #0xFFFF0000 $A -AND #0x0000FFFF $A -OR #0xFFFFFFFF $A -XOR #0xFFFFFFEF $A -SHR #0x1 $A -SHR #0x3 $A -SHR #0x1 $A -MOV #0x1 $A -SHL #0x1 $A -SHL #30 $A -SHL #1 $A -XOR $A $A -ADD #0xFF $A -ADD #0x1 $A -SUB #0x1 $A -SUB #0xFF $A -SUB #0x1 $A -SUB #0xFFFFFFFF $A -MOV #0x05 $A -CMP #0x05 $A -CMP #0x04 $A -CMP #0x06 $A - -;reg -MOV #0xFFFFFFFE $A -NOT $A -MOV #0x40000000 $S -MOV :function $A -CALL $A -PUSH $A -JMP <> :pastFunction - -function: -MOV #1 $A -RET - -pastFunction: -;regreg - -MOV #0xFFFF0000 $A -MOV #0x0000FFFF $B -XOR $A $B -MOV #0xFFFF0000 $A -MOV #0x0000FFFF $B -ADD $A $B -MOV #1 $A -MOV #2 $B -SUB $B $A - -MOV #4 $A -MOV #2 $B -MUL $A $B -MOV #2 $A -DIV $B $A - -MOV #1 $A -MOV #1 $B -SHL $B $A -SHR $B $A -MOV #2 $B -AND $B $A -MOV #2 $A -AND $B $A - -MOV #1 $A -MOV #2 $B -OR $B $A -OR $A $A - -MOV #5 $A -MOV #5 $B -CMP $B $A - -MOV #4 $B -CMP $B $A - -MOV #6 $B -CMP $B $A - -MOV $B $A - - -div: -mov #6 $a -mov #3 $b -div $a $b - -mov #7 $a -mov #3 $b -div $a $b - -MEMTEST: -MOV #0xAABBCCDD $A -MOV #0x40000000 $B -MEMW [1] $A $B - -end: \ No newline at end of file diff --git a/testFiles/mmu.casm b/testFiles/mmu.casm deleted file mode 100644 index bedeba5..0000000 --- a/testFiles/mmu.casm +++ /dev/null @@ -1,17 +0,0 @@ -; This will test access to mmu mapped device -.global start: - -start: - - MOV #0x40000000 $B -memFun: - MOV #0x20000000 $A - MEMW [4] $B $A - MOV #0x20000004 $A - MEMW [1] #0b11111111 $A - MOV #0x20000005 $A - MEMW [1] #0b10000000 $A - ADD #0xFF $B - - JMP <> :memFun - diff --git a/testFiles/strlen.casm b/testFiles/strlen.casm deleted file mode 100644 index 9ee8bfb..0000000 --- a/testFiles/strlen.casm +++ /dev/null @@ -1,34 +0,0 @@ -.global start: - -start: -JMP <> :stackSetup - -string: -.dataAlpha cdab - -stackSetup: -MOV :stack $S ;Stack is now usable - -codeStart: - PUSH :string - CALL :strlen - SUB #0x4 $S ;stack reset - JMP <> :codeStart -end: - -;strlen(stringpointer) -strlen: - MOV $S $A - SUB #0x4 $A ;Calculate parameter offset, cant do pop, that would destroy the stack - MEMR [4] $A $A ;Get parameter in register C - MOV $A $C ;Keep pointer to string start -lenover: - MEMR [1] $A $B - CMP #0x00 $B ;are we at the end of the string - JMP :gotlen - ADD #0x1 $A - JMP <> :lenover ;not at the end, jump back -gotlen: - SUB $C $A ;A will hold the len of the string at this point. - RET ;return value in register A -stack: \ No newline at end of file diff --git a/testFiles/thread.casm b/testFiles/thread.casm deleted file mode 100644 index e817780..0000000 --- a/testFiles/thread.casm +++ /dev/null @@ -1,18 +0,0 @@ -; This is used to test the multithreading provided by the game environment -.global start: - -start: - MOV #0x20000200 $A - MEMW [4] :thread $A - - MOV #0x40000100 $S -loop: - PUSH #0x00 - JMP <> :loop - -thread: - MOV #0x400F0000 $S -part: - PUSH #0x00 - JMP <> :part -end: \ No newline at end of file