/// <summary>Assembles the given instruction line, writing the opcode to the right memory location</summary> /// <param name="instruction">The whole instruction line</param> public void assemble(string instruction) { ushort addr; string opcode; string operand; #if MONO || !DOTNET2 string[] instr = split(instruction, ' ', 3); #else string[] instr = instruction.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries); #endif addr = ushort.Parse(instr[0]); opcode = instr[1]; operand = (instr.Length >= 3) ? instr[2] : null; CpuInstruction operation = assembleInstruction(addr, opcode, operand); if (echoAssembledInstructions) { Console.WriteLine("{0}:\t{1}", addr, operation.ToString()); } // Write the instruction to the assembly core's RAM: memory[addr] = operation[0]; memory[addr + 1] = operation[1]; }
public unsafe CpuInstruction getNextInstruction() { #if OPCACHE // If this instruction is at a cacheable memory location... if (PC < OPCACHE_SIZE) { // Cache Miss - generate the object and insert it into the cache if (opCache[PC] == null) { CpuInstruction obj = new CpuInstruction(); obj.vm = this; obj[0] = memory[PC++]; obj[1] = memory[PC++]; opCache[PC - 2] = obj; return(obj); } else // Cache Hit - return the cached instruction { CpuInstruction tmp = opCache[PC]; PC += 2; return(tmp); } } else { CpuInstruction obj = new CpuInstruction(); obj.vm = this; obj[0] = memory[PC++]; obj[1] = memory[PC++]; return(obj); } #else // Uncached operation cache obj.vm = this; obj[0] = memory[PC++]; obj[1] = memory[PC++]; return(obj); #endif }
/// <summary>Produces an assembled instruction</summary> /// <param name="addr">The start address of this instruction</param> /// <param name="instruction">The opcode string</param> /// <param name="operands">The operands to the opcode</param> /// <returns>An assembled CpuInstruction</returns> public CpuInstruction assembleInstruction(ushort addr, String instruction, String operands) { CpuInstruction operation = new CpuInstruction(); // Now encode the instruction: OpCode op = (OpCode)Enum.Parse(typeof(OpCode), instruction.Trim(), true); operation.opcode = (byte)op; // If we have any operands, pack them into the instruction: { ushort[] operandVals = parseOperands(operands); operation.operand = (ushort)operandVals[0]; operation.register = (byte)operandVals[1]; operation.indirections = (byte)operandVals[2]; } return(operation); }
/// <summary>Decodes and executes instructions</summary> public unsafe bool execute() { while (!vm.psrH) { op = vm.getNextInstruction(); // Fetch & decode the next instruction to execute // Display the monitor if requested if (debug) { monitor(); } // Increment our instruction counter ++ops; #region Write trace data if necessary if (saveTrace) { traceFile.WriteLine("\t\tPSR={0}, FP={1}, SP={2}, MP={3}", vm.PSR, vm.FP, vm.SP, vm.MP); traceFile.Write("{0}\t{1}", (vm.PC - 2), op.ToString()); } #endregion #region Instruction Execution switch ((OpCode)op.opcode) { case OpCode.NOOP: // "Do Nothing" // Allow the monitor to reassert itself after the next noop if (noopBreak) { debug = true; } break; // MATHS OPERATIONS // case OpCode.ADD: vm.add(); break; case OpCode.SUB: vm.sub(); break; case OpCode.DVD: vm.div(); break; case OpCode.MUL: vm.mul(); break; case OpCode.DREM: vm.mod(); break; case OpCode.INCR: // Increment the top of the stack by operand vm.incr((short)op.operand); break; // LOGICAL OPERATIONS // case OpCode.LOR: vm.lor(); break; case OpCode.INV: vm.lnot(); break; case OpCode.NEG: vm.neg(); break; case OpCode.LAND: vm.land(); break; case OpCode.SLL: vm.lsl(op.operand); break; case OpCode.SRL: vm.lsr(op.operand); break; // COMPARISONS // case OpCode.CLT: // if (pop() < pop()) push(1) else push(0); cmps++; short clt2 = (short)vm.pop(); short clt1 = (short)vm.pop(); if (clt1 < clt2) { vm.push(1); } else { vm.push(0); } break; case OpCode.CLE: // if (pop() <= pop()) push(1) else push(0); cmps++; short cle2 = (short)vm.pop(); short cle1 = (short)vm.pop(); if (cle1 <= cle2) { vm.push(1); } else { vm.push(0); } break; case OpCode.CEQ: // if (pop() == pop()) push(1) else push(0); cmps++; if (vm.pop() == vm.pop()) { vm.push(1); } else { vm.push(0); } break; case OpCode.CNE: // if (pop() != pop()) push(1) else push(0); cmps++; if (vm.pop() != vm.pop()) { vm.push(1); } else { vm.push(0); } break; // BRANCHING // case OpCode.BRN: vm.PC = op.getOffsetOperand(); break; case OpCode.BIDX: ushort jumpIncrement = vm.pop(); ushort increments = op.operand; ushort jumpWords = (ushort)(jumpIncrement * increments); vm.PC += jumpWords; break; case OpCode.BZE: // Branch to m if pop() == 0 if (vm.pop() == 0) { vm.PC = op.getOffsetOperand(); } break; case OpCode.BNZ: // Branch to m if pop() != 0 if (vm.pop() != 0) { vm.PC = op.getOffsetOperand(); } break; case OpCode.BNG: // Branch to m if pop() < 0 if ((short)vm.pop() < 0) { vm.PC = op.getOffsetOperand(); } break; case OpCode.BPZ: // Branch to m if pop() >= 0 if ((short)vm.pop() >= 0) { vm.PC = op.getOffsetOperand(); } break; case OpCode.BVS: // unknown if (vm.psrV) { vm.psrV = false; // Clear the flag vm.PC = op.getOffsetOperand(); } break; case OpCode.BES: // unknown if (vm.psrE) { vm.psrE = false; // Clear the flag vm.PC = op.getOffsetOperand(); } break; // SUBROUTINES // case OpCode.MARK: // Set MP to SP and increment SP by m vm.MP = vm.SP; vm.SP += op.operand; break; case OpCode.CALL: // Store FP and PC to the current frame; FP=MP; PC=m #if SYSROUTINE_INT_HACK // Hack around a bug in the spec: don't CALL the readInt and writeInt system routines (CALLing would corrupt the current frame) ushort jumpTo = op.getOffsetOperand(); if (jumpTo == 50 || jumpTo == 100) { if (jumpTo == 50) // readInt { ioSinceMonitor = true; bool valid = false; while (!valid) { try { string intinLine = Console.ReadLine(); short intin = (short)int.Parse(intinLine); vm.push((ushort)intin); if (ioLog) { if (ioLog) { ioFile.WriteLine(intin); } } valid = true; } catch (FormatException) { Console.WriteLine("(Invalid Number. Try again)"); } } } else // writeInt { ioSinceMonitor = true; short intout = (short)vm.pop(); Console.Write(intout); if (ioLog) { ioFile.Write(intout); } break; } } else // Normal CALL implementation { vm.memory[vm.MP + 1] = vm.FP; vm.memory[vm.MP + 2] = vm.PC; vm.FP = vm.MP; vm.PC = jumpTo; } #else vm.memory[vm.MP + 1] = vm.FP; vm.memory[vm.MP + 2] = vm.PC; vm.FP = vm.MP; vm.PC = op.getOffsetOperand(); #endif break; case OpCode.EXIT: // Restore FP and PC from the MP stack vm.SP = vm.FP; vm.FP = vm.memory[vm.SP + 1]; vm.PC = vm.memory[vm.SP + 2]; // jump back to the caller break; // LOADING // case OpCode.LOADL: // Load a value (operand) vm.push(op.operand); break; case OpCode.LOADR: // Load the value in a register vm.push(vm.getRegister(op.register)); break; case OpCode.LOAD: // Load the value of the offset operand vm.push(vm.memory[op.getOffsetOperand()]); break; case OpCode.LOADA: // Load the address of the offset operand vm.push(op.getOffsetOperand()); break; case OpCode.LOADI: { // Load (operand) words onto the stack, source address on the top of the stack ushort src = vm.pop(); // get the src address ushort srcMax = (ushort)(src + (op.operand)); for (; src < srcMax; src += 2) { vm.push(vm.memory[src]); } break; } // STORING // case OpCode.STORER: // Pop to a register vm.setRegister(op.register, vm.pop()); break; case OpCode.STORE: // Pop, using m as the destination address vm.memory[op.getOffsetOperand()] = vm.pop(); //vm.memSetWord(op.getOffsetOperand(), vm.pop()); break; case OpCode.STOREI: { // Pop (operand) words from the stack ushort dest = vm.pop(); for (int i = op.operand; i != 0; --i) { vm.memory[dest++] = vm.pop(); } break; } case OpCode.STZ: // Store 0 to memory location m vm.memory[op.getOffsetOperand()] = 0; break; case OpCode.INCREG: // Increment register by n vm.incRegister(op.register, op.operand); break; case OpCode.MOVE: // Copy n words from (SP-2) to (SP-1) ushort mdest = vm.pop(); ushort msrc = vm.pop(); vm.memCopyWord(msrc, mdest, op.operand); break; case OpCode.SETSP: vm.SP = op.getOffsetOperand(); break; case OpCode.SETPSR: vm.PSR = op.operand; break; case OpCode.HALT: // Sets the HALT bit of the PSR vm.psrH = true; break; // Check instruction: case OpCode.CHECK: vm.SP -= 2; short check1 = (short)vm.memory[vm.SP]; short check2 = (short)vm.memory[vm.SP - 1]; short check3 = (short)vm.memory[vm.SP + 1]; if (check1 <= check2 && check2 <= check3) { vm.psrE = false; } else // If the PSR[C] bit is set, halt the CPU { vm.psrE = true; vm.psrH = vm.psrC; } throw new NotImplementedException("CHECK instruction is not yet implemented in this virtual machine"); // Character reading and writing: case OpCode.CHIN: ioSinceMonitor = true; int chinChar = Console.Read(); vm.push((ushort)chinChar); // Technically allows unicode if (ioLog) { ioFile.Write(chinChar); } break; case OpCode.CHOUT: ioSinceMonitor = true; char choutChar = (char)vm.pop(); Console.Write(choutChar); // Technically allows unicode if (ioLog) { ioFile.Write(choutChar); } break; ////-------------- NON-STANDARD OPCODES FOLLOW! --------------//// #if !NOEXTENDEDINSTRUCTIONS case OpCode.BLANK: // Do absolutely nothing (intended as an alternative to NOOP for VM debugging) break; #endif default: throw new ArgumentOutOfRangeException("Encountered invalid opcode: " + op.opcode); } // end switch #endregion // Instruction Execution // If the CPU has been halted: if (vm.psrH) { closeFiles(); Console.WriteLine("\n>VM: CPU HALTED"); // Run the monitor if (haltBreak) { Console.WriteLine("<ENTERING MONITOR>"); monitor(); } } } // while(true) return(!vm.psrH); } // end tick()
/// <summary>A simple debugging console</summary> public unsafe void monitor() { #region Monitor disappearance conditions if (addrBreak != -1) // If we have been instructed to break at a specific address: { if (vm.PC - 2 != addrBreak) { return; // Do not display the monitor yet } else { addrBreak = -1; } } else if (monitorSkipInstructions != 0) { if (--monitorSkipInstructions > 0) { return; // Do not display the monitor yet } } #endregion #region Handle coexistance with the UI nicely // If the CPU has performed IO since the last execution, ensure we're on a new line if (ioSinceMonitor) { Console.WriteLine(); ioSinceMonitor = false; } #endregion // If the user requested it, automatically display the decode of the instruction if (autoDecode) { Console.WriteLine("\t{1}", (vm.PC - 2), op.ToString()); } // Keep accepting arguments until we receive a terminal command #region Monitor switch while (true) { Console.Write("[{0}] Monitor> ", (vm.PC - 2)); string[] cmds = TargetVM.std.ArgParser.parse(Console.ReadLine()); if (cmds.Length == 0) { return; } switch (cmds[0].ToLower()) { case null: case "": case "c": case "continue": return; case "next": case "n": debug = true; return; case "r": case "run": debug = false; return; case "k": case "skip": // Skip n executions: if (cmds.Length == 2) { monitorSkipInstructions = parseAddr(cmds[1]); Console.WriteLine("Skipping monitor for next {0} instruction(s).", monitorSkipInstructions); return; } else { Console.WriteLine("Target Monitor: skip requires an argument. Example: skip 5"); break; } case "b": case "break": // Break at a specific address if (cmds.Length == 2) { addrBreak = parseAddr(cmds[1]); Console.WriteLine("Setting breakpoint at address {0}.", addrBreak); return; } else { Console.WriteLine("Target Monitor: break requires an argument. Example: break 210"); break; } case "s": case "halt": case "stop": case "exit": case "quit": case "q": // halt execution and terminate closeFiles(); Console.WriteLine("Target Monitor: goodbye."); System.Environment.Exit(0); return; case "search": // Search for the occurrances of n in memory if (cmds.Length == 2) { ushort val = parseValue(cmds[1]); Console.WriteLine("Searching memory..."); for (int i = vm.memory.Length - 1; i != 0; --i) { if (vm.memory[i] == val) { Console.WriteLine("{0}: {1}\t('{2}')", i, val, (char)val); } } Console.WriteLine("Complete."); } else { Console.WriteLine("Target Monitor: search requires one argument."); } break; case "hb": case "haltbreak": // examines / sets haltbreak Console.WriteLine("haltBreak={0}", haltBreak); if (cmds.Length == 2) { haltBreak = parseBool(cmds[1]); Console.WriteLine("haltBreak={0}\tCHANGED", haltBreak); } break; case "nb": case "noopbreak": // examines / sets noopBreak Console.WriteLine("noopBreak={0}", noopBreak); if (cmds.Length == 2) { noopBreak = parseBool(cmds[1]); Console.WriteLine("noopBreak={0}\tCHANGED", noopBreak); } break; case "setsp": vm.SP = parseAddr(cmds[1]); Console.WriteLine("SP={0}\tCHANGED", vm.SP); break; case "setfp": vm.FP = parseAddr(cmds[1]); Console.WriteLine("FP={0}\tCHANGED", vm.FP); break; case "setmp": vm.MP = parseAddr(cmds[1]); Console.WriteLine("MP={0}\tCHANGED", vm.MP); break; case "store": if (cmds.Length == 3) { ushort storeAddr = parseAddr(cmds[1]); ushort storeVal = parseValue(cmds[2]); vm.memory[storeAddr] = storeVal; Console.WriteLine("{0}:\t{1}", storeAddr, storeVal); } else { Console.WriteLine("Target Monitor: store takes 2 arguments (address, value)"); } break; case "push": ushort data = parseAddr(cmds[1]);; vm.push(data); Console.WriteLine("Pushed {0} onto the stack.", data); break; case "pop": if (vm.SP > 0) { Console.WriteLine("Popped {0} from the stack.", vm.pop()); } else { Console.WriteLine("Stack at top of address space. Cannot pop."); } break; case "j": case "jump": if (cmds.Length == 2) { vm.PC = parseAddr(cmds[1]); Console.WriteLine("Jumping to {0}", vm.PC); this.op = vm.getNextInstruction(); // Decode the instruction and execute it instead } else { Console.WriteLine("Target Monitor: jump requires an argument. Example: jump 50"); } break; case "d": case "decode": for (int i = 1; i < cmds.Length; i++) { ushort addr = parseAddr(cmds[i]); CpuInstruction memop = new CpuInstruction(); memop[0] = vm.memory[addr]; memop[1] = vm.memory[addr + 1]; Console.WriteLine("{0}:\t{1}", addr, memop.ToString()); } if (cmds.Length == 1) { Console.WriteLine("{0}\t{1}", (vm.PC - 2), op.ToString()); } break; case "i": case "inspect": for (int i = 1; i < cmds.Length; i++) { ushort addr = parseAddr(cmds[i]); if (signedAccess) { if (addr < vm.memory.Length) { Console.WriteLine("{0}:\t0x{1:X4} == {1}s", addr, (short)vm.memory[addr]); } else { Console.WriteLine("{0}:\tOUT OF BOUNDS"); } } else { if (addr < vm.memory.Length) { Console.WriteLine("{0}:\t0x{1:X4} == {1}u", addr, vm.memory[addr]); } else { Console.WriteLine("{0}:\tOUT OF BOUNDS"); } } } break; case "p": case "peek": ushort pitems = 1; bool peekSigned = signedAccess; if (cmds.Length == 2) { pitems = parseAddr(cmds[1]); } // Check we're not about to read out of the memory bounds if (vm.SP - pitems < 0) { Console.WriteLine("Target Monitor: {0} word{1} back from SP ({2}) is an illegal address.", pitems, (pitems != 1 ? "s" : ""), vm.SP); break; } for (int offset = 1; offset <= pitems; offset++) { ushort w = vm.memory[vm.SP - offset]; // vm.memGetWord(vm.SP - offset); if (peekSigned) { Console.WriteLine("{0}:\t0x{1:X4} == {1}s", vm.SP - offset, (short)w); } else { Console.WriteLine("{0}:\t0x{1:X4} == {1}u", vm.SP - offset, w); } } break; case "g": case "reg": case "registers": case "register": case "calc": // make it easier for the user to think about using reg to calculate values if (cmds.Length == 1) { Hashtable core = vm.coreDump(); foreach (string key in core.Keys) { Console.WriteLine("{0}\t= {1}", key, core[key]); } } else { for (int i = 1; i < cmds.Length; i++) { ushort val = parseAddr(cmds[i]); Console.WriteLine("{0} = {1}", cmds[i].ToUpper(), val); } } break; case "assemble": case "asm": if (cmds.Length >= 2) { // Assemble each file passed in as an argument for (int i = 1; i < cmds.Length; i++) { if (File.Exists(cmds[i])) { Assembler a = new Assembler(vm.memory, cmds[i]); vm.memory = a.getAssembled(); // technically unnecessary // Re-decode this instruction (in case it's changed) vm.PC -= 2; this.op = vm.getNextInstruction(); } else { Console.WriteLine("Arg #{0} Non-existant file {1}", i, cmds[i]); } } } else { Console.WriteLine("Monitor: assemble requires a parameter. Please quote paths with spaces in them."); } break; case "t": case "trace": case "savetrace": if (cmds.Length == 2) { Console.WriteLine("Monitor: Tracing Enabled"); startTrace(cmds[1]); } else { Console.WriteLine("Monitor: trace requires one parameter"); } break; case "logio": case "lio": case "io": case "l": if (cmds.Length == 2) { Console.WriteLine("Monitor: IO Logging Enabled"); startIOLog(cmds[1].Replace('_', ' ')); } else { Console.WriteLine("Monitor: logio requires one parameter. Please quote paths with spaces in them."); } break; case "signed": signedAccess = true; Console.WriteLine("Monitor: memory display set to signed"); break; case "unsigned": signedAccess = false; Console.WriteLine("Monitor: memory display set to unsigned"); break; case "?": case "help": #if DOTNET2 Console.BackgroundColor = ConsoleColor.Blue; Console.ForegroundColor = ConsoleColor.White; #endif Console.WriteLine("TARGET MONITOR - QUICK HELP"); #if DOTNET2 Console.ResetColor(); #endif Console.WriteLine("Copyright (c) 2006, Peter Wright <*****@*****.**>"); Console.WriteLine("Commands that take n can understand 'sp', '0,[fp,1]', etc."); Console.WriteLine("d {n} - Displays a decode of the instruction[s] at n."); Console.WriteLine(" Current instruction displayed if none are"); Console.WriteLine(" specified (decode)"); Console.WriteLine("p [-][n] - Displays the top n items on the stack. (peek)"); Console.WriteLine("i [-]{n} - Displays values stored in memory location[s] n."); Console.WriteLine("j addr - Branches immediately to ADDR. (jump)"); Console.WriteLine("q - Terminates the VM immediately"); Console.WriteLine("c - Resumes execution; monitor state unchanged"); Console.WriteLine(" (continue, <ENTER>)"); Console.WriteLine("r - Resumes execution; monitor disabled"); Console.WriteLine("n - Resumes execution; monitor enabled"); Console.WriteLine("k n - Hides monitor for another n operations. (skip)"); Console.WriteLine("b n - Hides monitor until operation at n. (break)"); Console.WriteLine("g - Displays all registers (registers)"); Console.WriteLine("g {n} - Displays specific register values"); Console.WriteLine("calc {n} - Calculates the address n"); Console.WriteLine("asm f - Assembles file f"); Console.WriteLine("t f - Starts saving trace data to file f"); Console.WriteLine("l f - Logs all IO to file f"); Console.WriteLine("signed - Changes memory display to signed mode"); break; default: Console.WriteLine("Monitor: Unknown command"); break; } } #endregion }