diff --git a/README.md b/README.md index 400703e..c9d60b0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # SVM -Sam Virtual Machine +Sams Virtual Machine diff --git a/SVM.sln b/SVM.sln new file mode 100644 index 0000000..96d596b --- /dev/null +++ b/SVM.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SVM", "SVM\SVM.csproj", "{C8C1E9F3-DAC3-4779-AF30-352DBE4DAB4C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8C1E9F3-DAC3-4779-AF30-352DBE4DAB4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8C1E9F3-DAC3-4779-AF30-352DBE4DAB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8C1E9F3-DAC3-4779-AF30-352DBE4DAB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8C1E9F3-DAC3-4779-AF30-352DBE4DAB4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0357165B-31C2-41A9-8767-32104597595E} + EndGlobalSection +EndGlobal diff --git a/SVM/ArrayExtensions.cs b/SVM/ArrayExtensions.cs new file mode 100644 index 0000000..22df782 --- /dev/null +++ b/SVM/ArrayExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using System.Linq; + +namespace SVM +{ + static class ArrayExtensions + { + public static byte[] Subset(this byte[] array, int start, int length) + { + Debug.Assert(array.Length >= start + length); + byte[] subset = new byte[length]; + Array.Copy(array, start, subset, 0, length); + return subset; + } + public static byte[] Subset(this byte[] array, int start) + { + Debug.Assert(array.Length >= start); + byte[] subset = new byte[array.Length - start]; + Array.Copy(array, start, subset, 0, array.Length - start); + return subset; + } + public static byte[] Concat(this byte[] array, params byte[][] others) + { + var size = array.Length + others.Sum(o => o.Length); + var result = new byte[size]; + + var i = array.Length; + Array.Copy(array, 0, result, 0, array.Length); + foreach(var o in others) + { + Array.Copy(o, 0, result, i, o.Length); + i += o.Length; + } + return result; + } + public static byte[] Prepend(this byte[] array, byte val) + { + var newArr = new byte[array.Length + 1]; + var len = array.Length; + Array.Copy(array, 0, newArr, 1, array.Length); + newArr[0] = val; + return newArr; + } + } +} diff --git a/SVM/Assembler.cs b/SVM/Assembler.cs new file mode 100644 index 0000000..0124696 --- /dev/null +++ b/SVM/Assembler.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Diagnostics; + +namespace SVM +{ + class Assembler + { + Instruction[] instructions; + + public Assembler() + { + instructions = Instruction.GetAllInstructions(); + } + + public byte[] Compile(string program) + { + byte[] mem = new byte[VM.MEMSIZE]; + + var lines = (from l in program.Replace("\t", " ").Split(Environment.NewLine) + where !l.StartsWith('#') && !String.IsNullOrWhiteSpace(l) + select l.Split('#').First().Trim().ToUpper()).ToArray(); + + int i = 0; + ushort mempos = 0; + ushort lastpos = 0; + string line; + string[] parts; + Dictionary markers = new Dictionary(); + Dictionary> markerUses = new Dictionary>(); + //Encode ops until memory section + for (; i < lines.Length; i++) + { + line = lines[i]; + if (line == "MEMORY") + { + i++; + break; + } + + //Remove multiple spaces + while (line.IndexOf(" ") != -1) + { + line = line.Replace(" ", " "); + } + + //Records marker locations + if (line.StartsWith(':')) + { + markers.Add(line.Substring(1), mempos); + continue; + } + + parts = line.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries); + var op = parts[0]; + if (op == "ORIGIN") + { + Debug.Assert(parts.Length == 2); + if (parts[1].StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + mempos = ushort.Parse(parts[1].Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + mempos = ushort.Parse(parts[1]); + } + if (mempos > lastpos) lastpos = mempos; + continue; + } + + var instr = instructions.First(x => x.ASM == op); + + Dictionary markerRefs = new Dictionary(); + var bytecode = instr.Encode(parts.Length > 1 ? parts[1] : string.Empty, markerRefs); + if (markerRefs != null && markerRefs.Count > 0) + { + foreach (var mRef in markerRefs) + { + if (markerUses.ContainsKey(mRef.Key)) + { + markerUses[mRef.Key].Add((ushort)(mempos + mRef.Value)); + } + else + { + markerUses.Add(mRef.Key, new List() { (ushort)(mempos + mRef.Value) }); + } + } + } + Array.Copy(bytecode, 0, mem, mempos, bytecode.Length); + mempos += (ushort)bytecode.Length; + if (mempos > lastpos) lastpos = mempos; + } + + foreach(var mUse in markerUses) + { + if (!markers.ContainsKey(mUse.Key)) + { + throw new Exception(string.Format("Use of undefined marker {0}", mUse.Key)); + } + foreach(var loc in mUse.Value) + { + mem[loc] = markers[mUse.Key].HiByte(); + mem[loc+1] = markers[mUse.Key].LoByte(); + } + } + + for (; i < lines.Length; i++) + { + line = lines[i]; + parts = line.Split(" ", 2); + + ushort dataOrigin = 0; + if (parts[0].StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + dataOrigin = ushort.Parse(parts[0].Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + dataOrigin = ushort.Parse(parts[0]); + } + + byte[] lineData = new byte[0]; + if (parts[1].StartsWith('\'') || parts[1].StartsWith('"')) + { + string asciiContent = string.Empty; + if (parts[1].StartsWith('\'')) + { + asciiContent = parts[1].Trim('\''); + } + else if (parts[1].StartsWith('"')) + { + asciiContent = parts[1].Trim('"'); + } + lineData = Encoding.ASCII.GetBytes(asciiContent); + //Zero terminate + Array.Resize(ref lineData, lineData.Length + 1); + lineData[lineData.Length - 1] = 0; + } + else if (parts[1].StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + var hex = parts[1].Substring(2).Replace(" ", ""); + if (hex.Length % 2 != 0) + { + hex = "0" + hex; + } + lineData = new byte[hex.Length / 2]; + for (int di = 0, ldi = 0; di < hex.Length; di += 2, ldi++) + { + lineData[ldi] = byte.Parse(hex.Substring(di, 2), System.Globalization.NumberStyles.HexNumber); + } + } + Array.Copy(lineData, 0, mem, dataOrigin, lineData.Length); + if (lastpos < dataOrigin + lineData.Length) + { + lastpos = (ushort)(dataOrigin + lineData.Length); + } + + } + + Array.Resize(ref mem, lastpos); + return mem; + } + } +} diff --git a/SVM/Instruction.cs b/SVM/Instruction.cs new file mode 100644 index 0000000..f6eca5e --- /dev/null +++ b/SVM/Instruction.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace SVM +{ + abstract class Instruction + { + public static Instruction[] GetAllInstructions() + { + var types = from t in Assembly.GetAssembly(typeof(Instruction)).GetTypes() + where t.IsSubclassOf(typeof(Instruction)) + select t; + + var instr = new List(); + + foreach(var t in types) + { + instr.Add((Instruction)Activator.CreateInstance(t)); + } + + return instr.ToArray(); + } + + public abstract string ASM { get; } + public abstract byte OP { get; } + + public virtual byte[] Encode(string asm) + { + return Encode(asm, null); + } + public abstract byte[] Encode(string asm, Dictionary markerRefs); + public abstract string ToASM(byte[] vars); + public abstract void Exec(VM vm, byte[] vars); + public virtual byte[] Decode(VM vm) + { + return new byte[0]; + } + } +} diff --git a/SVM/Instructions/ADD.cs b/SVM/Instructions/ADD.cs new file mode 100644 index 0000000..12342c7 --- /dev/null +++ b/SVM/Instructions/ADD.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Linq; + +namespace SVM.Instructions +{ + class ADD : Instruction + { + public override string ASM => "ADD"; + + public override byte OP => 0x21; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + var parts = asm.Split(" ", 2); + Debug.Assert(parts.Length == 2); + var reg = Register.FromASM(parts[0]); + + var bc = new byte[] { OP, reg }; + + return bc.Concat(Location.FromASM(parts[1]).Encode()); + } + + public override byte[] Decode(VM vm) + { + var pc = vm.PC; + vm.PC += 1 + Location.SIZE; + return vm.MEM.Subset(pc, 1 + Location.SIZE); + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length > 1); + var reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + var loc = Location.FromByteCode(vars, 1); + + Run(vm, reg, loc); + } + + protected virtual void Run(VM vm, byte reg, Location loc) + { + vm.R[reg] += loc.Read(vm); + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 1 + Location.SIZE); + var reg = vars[0]; + var loc = Location.FromByteCode(vars, 1); + return string.Format("{0} {1} {2}", ASM, Register.ToASM(reg), loc.ToASM()); + } + } +} diff --git a/SVM/Instructions/CALL.cs b/SVM/Instructions/CALL.cs new file mode 100644 index 0000000..af0dd03 --- /dev/null +++ b/SVM/Instructions/CALL.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class CALL : Instruction + { + public override string ASM => "CALL"; + + public override byte OP => 0x40; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + ushort jmp = 0; + if (asm.StartsWith(':')) + { + markerRefs.Add(asm.Substring(1), 1); + } + else if (asm.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + jmp = ushort.Parse(asm.Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + jmp = ushort.Parse(asm); + } + + return new byte[] { OP, jmp.HiByte(), jmp.LoByte() }; + } + + public override byte[] Decode(VM vm) + { + var bc = vm.MEM.Subset(vm.PC, 2); + vm.PC += 2; + return bc; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 2); + + Debug.Assert(vm.SP < VM.STACKDEPTH); + + vm.STACK[vm.SP++] = vm.PC; + vm.PC = (ushort)((vars[0] << 8) + vars[1]); + } + + public override string ToASM(byte[] vars) + { + var jmp = (ushort)((vars[0] << 8) + vars[1]); + return string.Format("{0} 0x{1:X}", ASM, jmp); + } + } +} diff --git a/SVM/Instructions/CLR.cs b/SVM/Instructions/CLR.cs new file mode 100644 index 0000000..8830ae5 --- /dev/null +++ b/SVM/Instructions/CLR.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class CLR : Instruction + { + public override string ASM => "CLR"; + + public override byte OP => 0x20; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + byte reg = Register.FromASM(asm); + return new byte[] { OP, reg }; + } + + public override byte[] Decode(VM vm) + { + return new byte[] { vm.MEM[vm.PC++] }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 1); + byte reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + Run(vm, reg); + } + + protected virtual void Run(VM vm, byte reg) + { + vm.R[reg] = 0; + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 1); + return string.Format("{0} {1}", ASM, Register.ToASM(vars[0])); + } + } +} diff --git a/SVM/Instructions/CLRI.cs b/SVM/Instructions/CLRI.cs new file mode 100644 index 0000000..e46519d --- /dev/null +++ b/SVM/Instructions/CLRI.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class CLRI : Instruction + { + public override string ASM => "CLRI"; + + public override byte OP => 0x62; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + vm.RI = 0; + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 0); + return ASM; + } + } +} diff --git a/SVM/Instructions/DEC.cs b/SVM/Instructions/DEC.cs new file mode 100644 index 0000000..9e176cd --- /dev/null +++ b/SVM/Instructions/DEC.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class DEC : CLR + { + public override string ASM => "DEC"; + + public override byte OP => 0x26; + + protected override void Run(VM vm, byte reg) + { + vm.R[reg]--; + } + } +} diff --git a/SVM/Instructions/DECI.cs b/SVM/Instructions/DECI.cs new file mode 100644 index 0000000..b67c3c7 --- /dev/null +++ b/SVM/Instructions/DECI.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class DECI : Instruction + { + public override string ASM => "DECI"; + + public override byte OP => 0x64; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + vm.RI--; + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 0); + return ASM; + } + } +} diff --git a/SVM/Instructions/DIV.cs b/SVM/Instructions/DIV.cs new file mode 100644 index 0000000..10299cb --- /dev/null +++ b/SVM/Instructions/DIV.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class DIV : ADD + { + public override string ASM => "DIV"; + + public override byte OP => 0x23; + + protected override void Run(VM vm, byte reg, Location loc) + { + vm.R[reg] /= loc.Read(vm); + } + } +} diff --git a/SVM/Instructions/HALT.cs b/SVM/Instructions/HALT.cs new file mode 100644 index 0000000..e842bd1 --- /dev/null +++ b/SVM/Instructions/HALT.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class HALT : Instruction + { + public override string ASM => "HALT"; + + public override byte OP => 0x50; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + vm.RUN = false; + vm.Ports[0].Write(Encoding.ASCII.GetBytes("\r\nSYSTEM HALTED")); + } + + public override string ToASM(byte[] vars) + { + return ASM; + } + } +} diff --git a/SVM/Instructions/INC.cs b/SVM/Instructions/INC.cs new file mode 100644 index 0000000..2c393af --- /dev/null +++ b/SVM/Instructions/INC.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class INC : CLR + { + public override string ASM => "INC"; + + public override byte OP => 0x25; + + protected override void Run(VM vm, byte reg) + { + vm.R[reg]++; + } + } +} diff --git a/SVM/Instructions/INCI.cs b/SVM/Instructions/INCI.cs new file mode 100644 index 0000000..a5cd6ba --- /dev/null +++ b/SVM/Instructions/INCI.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class INCI : Instruction + { + public override string ASM => "INCI"; + + public override byte OP => 0x63; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + vm.RI++; + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 0); + return ASM; + } + } +} diff --git a/SVM/Instructions/JMP.cs b/SVM/Instructions/JMP.cs new file mode 100644 index 0000000..ace6f08 --- /dev/null +++ b/SVM/Instructions/JMP.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class JMP : Instruction + { + public override string ASM => "JMP"; + + public override byte OP => 0x51; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + ushort loc = 0; + + if (asm.StartsWith(':') && markerRefs != null) + { + markerRefs.Add(asm.Substring(1), 1); + } + else if (asm.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + loc = ushort.Parse(asm.Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + loc = ushort.Parse(asm); + } + + return new byte[] { OP, loc.HiByte(), loc.LoByte() }; + } + + public override byte[] Decode(VM vm) + { + var code = vm.MEM.Subset(vm.PC, 2); + vm.PC += 2; + return code; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 2); + ushort loc = (ushort)((vars[0] << 8) + vars[1]); + vm.PC = loc; + } + + public override string ToASM(byte[] vars) + { + ushort loc = (ushort)((vars[0] << 8) + vars[1]); + return string.Format("{0} 0x{1:x}", ASM, loc); + } + } +} diff --git a/SVM/Instructions/JNZ.cs b/SVM/Instructions/JNZ.cs new file mode 100644 index 0000000..98291d1 --- /dev/null +++ b/SVM/Instructions/JNZ.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class JNZ : JZ + { + public override string ASM => "JNZ"; + + public override byte OP => 0x53; + + public override void CheckJump(VM vm, byte reg, ushort loc) + { + if (vm.R[reg] != 0) + { + vm.PC = loc; + } + } + } +} diff --git a/SVM/Instructions/JZ.cs b/SVM/Instructions/JZ.cs new file mode 100644 index 0000000..576cd73 --- /dev/null +++ b/SVM/Instructions/JZ.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class JZ : Instruction + { + public override string ASM => "JZ"; + + public override byte OP => 0x52; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + var parts = asm.Split(" ", 2); + Debug.Assert(parts.Length == 2); + + var reg = Register.FromASM(parts[0]); + + ushort loc = 0; + + if (parts[1].StartsWith(':') && markerRefs != null) + { + markerRefs.Add(parts[1].Substring(1), 2); + } + else if (parts[1].StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + loc = ushort.Parse(parts[1].Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + loc = ushort.Parse(parts[1]); + } + + return new byte[] { OP, reg, loc.HiByte(), loc.LoByte() }; + } + + public override byte[] Decode(VM vm) + { + var code = vm.MEM.Subset(vm.PC, 3); + vm.PC += 3; + return code; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 3); + byte reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + ushort loc = (ushort)((vars[1] << 8) + vars[2]); + + CheckJump(vm, reg, loc); + } + + public virtual void CheckJump(VM vm, byte reg, ushort loc) + { + if (vm.R[reg] == 0) + { + vm.PC = loc; + } + } + + public override string ToASM(byte[] vars) + { + var reg = vars[0]; + ushort loc = (ushort)((vars[1] << 8) + vars[2]); + return string.Format("{0} {1} 0x{2:x}", ASM, Register.ToASM(reg), loc); + } + } +} diff --git a/SVM/Instructions/LOAD.cs b/SVM/Instructions/LOAD.cs new file mode 100644 index 0000000..8592867 --- /dev/null +++ b/SVM/Instructions/LOAD.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class LOAD : Instruction + { + public override string ASM => "LOAD"; + + public override byte OP => 0x10; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + var parts = asm.Split(" "); + Debug.Assert(parts.Length > 1); + var reg = Register.FromASM(parts[0]); + var loc = Location.FromASM(String.Join(" ", parts.Skip(1))); + return (new byte[] { OP, reg }).Concat(loc.Encode()); + } + + public override byte[] Decode(VM vm) + { + var pc = vm.PC; + ushort size = 1 + Location.SIZE; + vm.PC += size; + return vm.MEM.Subset(pc, size); + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length > 1); + var reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + var loc = Location.FromByteCode(vars, 1); + Run(vm, reg, loc); + } + + protected virtual void Run(VM vm, byte reg, Location loc) + { + vm.R[reg] = loc.Read(vm); + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length > 1); + var reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + var loc = Location.FromByteCode(vars, 1); + + return string.Format("{0} {1} {2}", ASM, Register.ToASM(reg), loc.ToASM()); + } + } +} diff --git a/SVM/Instructions/LOADH.cs b/SVM/Instructions/LOADH.cs new file mode 100644 index 0000000..dca5acf --- /dev/null +++ b/SVM/Instructions/LOADH.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class LOADH : LOAD + { + public override string ASM => "LOADH"; + public override byte OP => 0x12; + + protected override void Run(VM vm, byte reg, Location loc) + { + var data = loc.Read(vm); + vm.R[reg] = (ushort)((data << 8) + (vm.R[reg] & 0xFF)); + } + } +} diff --git a/SVM/Instructions/LOADI.cs b/SVM/Instructions/LOADI.cs new file mode 100644 index 0000000..90ad27a --- /dev/null +++ b/SVM/Instructions/LOADI.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class LOADI : Instruction + { + public override string ASM => "LOADI"; + + public override byte OP => 0x60; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + var reg = Register.FromASM(asm); + return new byte[] { OP, reg }; + } + + public override byte[] Decode(VM vm) + { + var reg = vm.MEM[vm.PC]; + vm.PC++; + return new byte[] { reg }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 1); + var reg = vars[0]; + Debug.Assert(reg <= VM.REGISTERS); + Run(vm, reg); + } + + protected virtual void Run(VM vm, byte reg) + { + vm.R[reg] = vm.MEM[vm.RI]; + } + + public override string ToASM(byte[] vars) + { + return string.Format("{0} {1}", ASM, Register.ToASM(vars[0])); + } + } +} diff --git a/SVM/Instructions/LOADL.cs b/SVM/Instructions/LOADL.cs new file mode 100644 index 0000000..9ed9c28 --- /dev/null +++ b/SVM/Instructions/LOADL.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class LOADL : LOAD + { + public override string ASM => "LOADL"; + public override byte OP => 0x13; + + protected override void Run(VM vm, byte reg, Location loc) + { + var data = loc.Read(vm); + vm.R[reg] = (ushort)((vm.R[reg] & 0xFF00) + data); + } + } +} diff --git a/SVM/Instructions/MUL.cs b/SVM/Instructions/MUL.cs new file mode 100644 index 0000000..a6f18e5 --- /dev/null +++ b/SVM/Instructions/MUL.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class MUL : ADD + { + public override string ASM => "SUB"; + + public override byte OP => 0x24; + + protected override void Run(VM vm, byte reg, Location loc) + { + vm.R[reg] *= loc.Read(vm); + } + } +} diff --git a/SVM/Instructions/NOP.cs b/SVM/Instructions/NOP.cs new file mode 100644 index 0000000..04b8ef7 --- /dev/null +++ b/SVM/Instructions/NOP.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class NOP : Instruction + { + public override string ASM => "NOOP"; + + public override byte OP => 0x00; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + return; + } + + public override string ToASM(byte[] vars) + { + Debug.Assert(vars.Length == 0); + return ASM; + } + } +} diff --git a/SVM/Instructions/RET.cs b/SVM/Instructions/RET.cs new file mode 100644 index 0000000..a632f02 --- /dev/null +++ b/SVM/Instructions/RET.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class RET : Instruction + { + public override string ASM => "RET"; + + public override byte OP => 0x41; + + public override byte[] Encode(string asm, Dictionary markerRefs) + { + return new byte[] { OP }; + } + + public override void Exec(VM vm, byte[] vars) + { + Debug.Assert(vars.Length == 0); + Debug.Assert(vm.SP >= 0); + + vm.PC = vm.STACK[vm.SP]; + vm.STACK[vm.SP--] = 0; + } + + public override string ToASM(byte[] vars) + { + throw new NotImplementedException(); + } + } +} diff --git a/SVM/Instructions/SAVE.cs b/SVM/Instructions/SAVE.cs new file mode 100644 index 0000000..22eed22 --- /dev/null +++ b/SVM/Instructions/SAVE.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using System.Diagnostics; + +namespace SVM.Instructions +{ + class SAVE : LOAD + { + public override string ASM => "SAVE"; + + public override byte OP => 0x11; + + protected override void Run(VM vm, byte reg, Location loc) + { + loc.Write(vm, vm.R[reg]); + } + } +} diff --git a/SVM/Instructions/SAVEH.cs b/SVM/Instructions/SAVEH.cs new file mode 100644 index 0000000..3496265 --- /dev/null +++ b/SVM/Instructions/SAVEH.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class SAVEH : LOAD + { + public override string ASM => "SAVEH"; + public override byte OP => 0x14; + + protected override void Run(VM vm, byte reg, Location loc) + { + byte data = (byte)((vm.R[reg] & 0xFF00) >> 8); + loc.Write(vm, data); + } + } +} diff --git a/SVM/Instructions/SAVEI.cs b/SVM/Instructions/SAVEI.cs new file mode 100644 index 0000000..08a4f51 --- /dev/null +++ b/SVM/Instructions/SAVEI.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class SAVEI : LOADI + { + public override string ASM => "SAVEI"; + + public override byte OP => 0x61; + + protected override void Run(VM vm, byte reg) + { + vm.MEM[vm.RI] = (byte)vm.R[reg]; + } + } +} diff --git a/SVM/Instructions/SAVEL.cs b/SVM/Instructions/SAVEL.cs new file mode 100644 index 0000000..2e2ce4a --- /dev/null +++ b/SVM/Instructions/SAVEL.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class SAVEL : LOAD + { + public override string ASM => "SAVEL"; + public override byte OP => 0x15; + + protected override void Run(VM vm, byte reg, Location loc) + { + byte data = (byte)(vm.R[reg] & 0xFF); + loc.Write(vm, data); + } + } +} diff --git a/SVM/Instructions/SETI.cs b/SVM/Instructions/SETI.cs new file mode 100644 index 0000000..24835f5 --- /dev/null +++ b/SVM/Instructions/SETI.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM.Instructions +{ + class SETI : LOADI + { + public override string ASM => "SETI"; + + public override byte OP => 0x65; + + protected override void Run(VM vm, byte reg) + { + vm.RI = vm.R[reg]; + } + } +} diff --git a/SVM/Instructions/SUB.cs b/SVM/Instructions/SUB.cs new file mode 100644 index 0000000..5308103 --- /dev/null +++ b/SVM/Instructions/SUB.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Instructions +{ + class SUB : ADD + { + public override string ASM => "SUB"; + + public override byte OP => 0x22; + + protected override void Run(VM vm, byte reg, Location loc) + { + vm.R[reg] -= loc.Read(vm); + } + } +} diff --git a/SVM/Location.cs b/SVM/Location.cs new file mode 100644 index 0000000..c1327d0 --- /dev/null +++ b/SVM/Location.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace SVM +{ + class Location + { + public const byte PORT_CODE = 0x0; + public const string PORT_ASM = "P"; + public const byte REG_CODE = 0x1; + public const string REG_ASM = "R"; + public const byte MEM_CODE = 0x2; + public const string MEM_ASM = "M"; + public const byte LITERAL_CODE = 0x3; + public const string LITERAL_ASM = "L"; + + public const int SIZE = 3; + + public static byte EncodeType(string type) + { + switch (type) + { + case PORT_ASM: return PORT_CODE; + case REG_ASM: return REG_CODE; + case MEM_ASM: return MEM_CODE; + case LITERAL_ASM: return LITERAL_CODE; + default: throw new Exception("Invalid location type"); + } + } + public static string DecodeType(byte type) + { + switch (type) + { + case PORT_CODE: return PORT_ASM; + case REG_CODE: return REG_ASM; + case MEM_CODE: return MEM_ASM; + case LITERAL_CODE: return LITERAL_ASM; + default: throw new Exception("Invalid location type"); + } + } + + public static Location FromASM(string asm) + { + var parts = asm.Split(" ", 2); + Debug.Assert(parts.Length > 0); + byte type = EncodeType(parts[0]); + ushort loc = 0; + Debug.Assert(parts.Length == 2); + string locVal = parts[1]; + if (locVal.StartsWith('\'') || locVal.StartsWith('"')) + { + Debug.Assert(locVal.Length > 1); + loc = (byte)locVal[1]; + } + else if (locVal.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + { + Debug.Assert(locVal.Length > 2); + loc = ushort.Parse(locVal.Substring(2), System.Globalization.NumberStyles.HexNumber); + } + else + { + loc = ushort.Parse(locVal); + } + + return new Location(type, loc); + } + public static Location FromByteCode(byte[] data, int start) + { + Debug.Assert(data.Length > start); + var type = data[start]; + ushort loc = 0; + Debug.Assert(data.Length > start + 1); + loc = (ushort)((data[start + 1] << 8) + data[start + 2]); + return new Location(type, loc); + } + + private byte type; + private ushort loc; + + public Location(byte type, ushort loc) + { + this.type = type; + this.loc = loc; + } + + public ushort Read(VM vm) + { + switch (type) + { + case PORT_CODE: + return vm.Ports[loc].Read(); + case REG_CODE: + return vm.R[loc]; + case MEM_CODE: + return vm.MEM[loc]; + case LITERAL_CODE: + return loc; + default: throw new Exception("Invalid location type"); + } + } + public void Write(VM vm, ushort val) + { + switch (type) + { + case PORT_CODE: + vm.Ports[loc].Write((byte)val); + break; + case REG_CODE: + vm.R[loc] = val; + break; + case MEM_CODE: + vm.MEM[loc] = (byte)val; + break; + case LITERAL_CODE: + throw new Exception("Invalid operation"); + default: + throw new Exception("Invalid location type"); + } + } + + public byte[] Encode() + { + byte ub = (byte)((loc & 0xFF00) >> 8); + byte lb = (byte)(loc & 0xFF); + return new byte[] { type, ub, lb }; + } + + public string ToASM() + { + return string.Format("{0} 0x{1:X}", DecodeType(type), loc); + } + } +} diff --git a/SVM/NumberExtensions.cs b/SVM/NumberExtensions.cs new file mode 100644 index 0000000..442e09b --- /dev/null +++ b/SVM/NumberExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM +{ + static class NumberExtensions + { + public static byte HiByte(this ushort num) + { + return (byte)((num & 0xFF00) >> 8); + } + + public static byte LoByte(this ushort num) + { + return (byte)(num & 0xFF); + } + } +} diff --git a/SVM/PGM/A.txt b/SVM/PGM/A.txt new file mode 100644 index 0000000..010b992 --- /dev/null +++ b/SVM/PGM/A.txt @@ -0,0 +1,41 @@ +ORIGIN 0 +#Print string 5 times + +CLR B +ADD B L 5 +ADD B L 48 +SAVE B P 0 +SUB B L 48 + +LOAD A L 0x300 #Set string start +SETI A +JMP :PRINTA + +:PRINTA #Print until zero +LOADI A +JZ A :NEWLINE +SAVE A P 0 +INCI +JMP :PRINTA + +:NEWLINE #New Line +LOAD A L 13 +SAVE A P 0 +LOAD A L 10 +SAVE A P 0 + +LOAD A L 0x300 #Set string start +SETI A + +DEC B #Check Counter and stop if 5 printed +JZ B :STOP +ADD B L 48 +SAVE B P 0 +SUB B L 48 +JMP :PRINTA + +:STOP +HALT + +MEMORY +0x300 " HELLO WORLD!" \ No newline at end of file diff --git a/SVM/Port.cs b/SVM/Port.cs new file mode 100644 index 0000000..0dadc67 --- /dev/null +++ b/SVM/Port.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM +{ + abstract class Port + { + private VM vm; + + public Port(VM vm) + { + this.vm = vm; + } + + public abstract ushort Read(); + public abstract void Write(byte val); + public virtual void Write(byte[] array) + { + foreach(var x in array) + { + Write(x); + } + } + } +} diff --git a/SVM/Ports/ConsolePort.cs b/SVM/Ports/ConsolePort.cs new file mode 100644 index 0000000..884eca0 --- /dev/null +++ b/SVM/Ports/ConsolePort.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM.Ports +{ + class ConsolePort : Port + { + public ConsolePort(VM vm) + : base(vm) + { } + + public override ushort Read() + { + var result = Console.ReadKey(true); + return (byte)result.KeyChar; + } + + public override void Write(byte val) + { + Console.Write((char)val); + } + } +} diff --git a/SVM/Program.cs b/SVM/Program.cs new file mode 100644 index 0000000..00a412b --- /dev/null +++ b/SVM/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; + +namespace SVM +{ + class Program + { + static void Main(string[] args) + { + var file = Path.Combine(Environment.CurrentDirectory, args[0]); + + var content = File.ReadAllText(file); + + Console.WriteLine("Assembling {0}", file); + + var asm = new Assembler(); + + var mem = asm.Compile(content); + + var vm = new VM(); + vm.CycleDelay = 25; + vm.Load(mem, 0); + + vm.Run(); + + Console.ReadKey(true); + } + } +} diff --git a/SVM/Register.cs b/SVM/Register.cs new file mode 100644 index 0000000..fb5b7d2 --- /dev/null +++ b/SVM/Register.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SVM +{ + static class Register + { + public static byte FromASM(string name) + { + switch(name.ToUpper()) + { + case "A": return 0; + case "B": return 1; + case "C": return 2; + case "D": return 3; + default: throw new Exception("Unknown Register: "+name); + } + } + + public static string ToASM(byte code) + { + switch(code) + { + case 0: return "A"; + case 1: return "B"; + case 2: return "C"; + case 3: return "D"; + default: throw new Exception(string.Format("Unknown Register Code: {0}", code)); + } + } + } +} diff --git a/SVM/SVM.csproj b/SVM/SVM.csproj new file mode 100644 index 0000000..96924e4 --- /dev/null +++ b/SVM/SVM.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + PreserveNewest + + + + diff --git a/SVM/VM.cs b/SVM/VM.cs new file mode 100644 index 0000000..93e1d07 --- /dev/null +++ b/SVM/VM.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace SVM +{ + class VM + { + public const int REGISTERS = 4; + public const int PORTS = 8; + public const int STACKDEPTH = 16; + public const int MEMSIZE = 0xFFFF; + + public bool RUN; + public ushort PC; + public ushort[] R = new ushort[REGISTERS]; + public ushort RI; + public byte SP; + public ushort[] STACK = new ushort[STACKDEPTH]; + public byte[] MEM = new byte[MEMSIZE]; + public Port[] Ports = new Port[PORTS]; + + public int CycleDelay = 0; + + private Dictionary instructions = new Dictionary(); + + public VM() + { + var instrs = Instruction.GetAllInstructions(); + foreach(var instr in instrs) + { + instructions.Add(instr.OP, instr); + } + + Ports[0] = new Ports.ConsolePort(this); + Reset(); + } + + public void Reset() + { + PC = 0; + SP = 0; + RI = 0; + Array.Fill(R, 0); + Array.Fill(STACK, 0); + Array.Fill(MEM, 0); + } + + public void Load(byte[] data, byte origin) + { + Array.Copy(data, 0, MEM, origin, data.Length); + } + + public void Run() + { + RUN = true; + while(RUN) + { + Step(); + if (CycleDelay > 0) + { + Thread.Sleep(CycleDelay); + } + } + } + + public void Step() + { + if (RUN) + { + //Get op + var pc = PC; + var instr = instructions[MEM[PC++]]; + byte[] decoded = instr.Decode(this); + Console.WriteLine("A{0} B{1} C{2} D{3}", R[0], R[1], R[2], R[3]); + Console.WriteLine("0x{0:X4} {1}", pc, instr.ToASM(decoded)); + instr.Exec(this, decoded); + Console.ReadKey(true); + } + } + } +}