From 72f297a479b6749674ec80e91be53b8c482123d2 Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:07:39 +1000 Subject: crowbar --- Assembler/Assembler.cs | 221 +++++++++++++++++++++++++++++++++++++++++----- Assembler/Disassembler.cs | 87 ++++++++++++++++++ 2 files changed, 286 insertions(+), 22 deletions(-) create mode 100644 Assembler/Disassembler.cs (limited to 'Assembler') diff --git a/Assembler/Assembler.cs b/Assembler/Assembler.cs index 14a33fd..5df068d 100644 --- a/Assembler/Assembler.cs +++ b/Assembler/Assembler.cs @@ -10,6 +10,9 @@ public class Assembler public readonly BinaryWriter Writer; public readonly StringReader Reader; + private Dictionary _labels = new(); + private u16 _addressOffset = 0; + public Assembler(StringReader reader, BinaryWriter writer) { this.Reader = reader; @@ -20,11 +23,23 @@ public class Assembler { while (this.Reader.ReadLine() is { } line) { - string trimmedLine = line.Trim(); + string trimmedLine = line.Split(";")[0].Trim(); // Remove comments if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith(';')) continue; - this.ParseInstruction(trimmedLine); + if (trimmedLine.StartsWith(".org")) + { + // Handle .org directive + string orgValue = trimmedLine[5..].Trim(); + this._addressOffset = this.GetConstant(orgValue); + Console.WriteLine($"Setting address offset to {this._addressOffset:X4}"); + } + + else if (trimmedLine.EndsWith(":")) + this._labels[trimmedLine[..^1]] = (u16) (this.Writer.BaseStream.Position + this._addressOffset); + + else + this.ParseInstruction(trimmedLine); } } @@ -37,28 +52,40 @@ public class Assembler if (parts.Length > 2) throw new ArgumentException($"Invalid instruction format: {instruction}"); + u8 opcodeValue = this.ParseOpcode(opcode); + u8 m1 = 0, m2 = 0; if (parts.Length > 0) - { - m1 = this.ParseOperand(parts[0]); - if (parts.Length > 1) - m2 = this.ParseOperand(parts[1]); - } + m1 = this.ParseMode(parts[0]); + if (parts.Length > 1) + m2 = this.ParseMode(parts[1]); - switch (opcode) - { - case "NOP": - this.Writer.Write((u8) 0x00); - break; + Console.WriteLine($"Parsed instruction: {opcode} with modes {m1:X2}, {m2:X2} (at address {this.Writer.BaseStream.Position + this._addressOffset:X4})"); + this.WriteInstruction(opcodeValue, m1, m2); + if (parts.Length > 0) + this.WriteMode(m1, parts[0]); + if (parts.Length > 1) + this.WriteMode(m2, parts[1]); + } - default: - throw new NotSupportedException($"Unsupported opcode: {opcode}"); - } + private u8 ParseOpcode(string opcode) + { + return opcode switch + { + "NOP" => 0x00, "JMP" => 0x00, "MOV" => 0x00, + "RET" => 0x01, "CALL" => 0x01, "CMP" => 0x02, + "BR" => 0x02, "BZ" => 0x02, "LT" => 0x02, + "GT" => 0x03, + "INC" => 0x08, "ADD" => 0x08, + "DEC" => 0x09, + "MUL" => 0x0B, + _ => throw new ArgumentException($"Unknown opcode: {opcode}") + }; } - private u8 ParseOperand(string operand) + private u8 ParseMode(string mode) { - operand = operand.Trim(); + mode = mode.Trim(); bool IsRegister(string text) { @@ -81,26 +108,176 @@ public class Assembler return hardcodedRegisters.Contains(text); } - if (operand.StartsWith("@")) + if (this._labels.ContainsKey(mode)) + { + return 0x01; // Immediate + } + + if (mode.StartsWith("@")) { return 0x03; // Indirect } - if (operand.StartsWith("[") && operand.EndsWith("]")) + if (mode.StartsWith("[") && mode.EndsWith("]")) { - string inner = operand[1..^1].Trim(); + string inner = mode[1..^1].Trim(); if (inner.Contains("+")) return 0x07; // Register indirect with offset - + if (IsRegister(inner)) return 0x06; // Register indirect return 0x02; // Direct } - if (IsRegister(operand)) + if (IsRegister(mode)) return 0x05; // Register return 0x01; // Immediate } + + private void WriteInstruction(u8 opcode, u8 m1, u8 m2) + { + Write(opcode); + Write((u8) (m1 << 4 | m2)); // Combine m1 and m2 into a single byte + } + + private void WriteMode(u8 mode, string operand) + { + operand = operand.Trim(); + + switch (mode) + { + case 0x00: // Implicit + // No additional data needed + break; + + case 0x01: // Immediate + u16 immediateValue = this.GetConstant(operand); + this.Write(immediateValue); + break; + + case 0x02: // Direct + u16 directAddress = this.GetConstant(operand); // Remove brackets + this.Write(directAddress); + break; + + case 0x03: // Indirect + u16 indirectAddress = this.GetConstant(operand); // Remove brackets and @ + this.Write(indirectAddress); + break; + + case 0x04: // Relative + i16 relativeOffset = this.GetSignedConstant(operand); + Write(relativeOffset); + break; + + case 0x05: // Register + u8 regIndex = this.GetRegisterIndex(operand); + Write(regIndex); + break; + + case 0x06: // Register Indirect + u8 indirectRegIndex = this.GetRegisterIndex(operand); // Remove brackets + Write(indirectRegIndex); + break; + + case 0x07: // Register Indirect with Offset + u8 offsetRegIndex = this.GetRegisterIndex(operand[1..^1].Split('+')[0].Trim()); + i16 offsetValue = this.GetSignedConstant(operand[1..^1].Split('+')[1].Trim()); + Write(offsetRegIndex); + Write(offsetValue); + break; + } + } + private i16 GetSignedConstant(string value) + { + value = this.Clean(value); + + if (value.StartsWith("$")) + { + // Hexadecimal offset + return Convert.ToInt16(value[1..], 16); + } + else if (i16.TryParse(value, out i16 result)) + // Decimal offset + return result; + else + { + throw new ArgumentException($"Invalid offset value: {value}"); + } + } + private u8 GetRegisterIndex(string reg) + { + reg = this.Clean(reg); + + if (reg.StartsWith("V")) + if (u8.TryParse(reg[1..], out u8 index) && index < 0x3F) + return index; + + return reg switch + { + "AX" => 0x40, + "AH" => 0x41, + "AL" => 0x42, + "DX" => 0x43, + "DH" => 0x44, + "DL" => 0x45, + "LX" => 0x46, + "PC" => 0x50, + "SP" => 0x51, + _ => throw new ArgumentException($"Invalid register index: {reg}") + }; + } + private u16 GetConstant(string value) + { + value = this.Clean(value); + + if (this._labels.ContainsKey(value)) + { + // Label reference + return this._labels[value]; + } + if (value.StartsWith("$")) + { + // Hexadecimal constant (backwards) $1FFF + u16 x = Convert.ToUInt16(value[1..], 16); + return x; + } + else if (u16.TryParse(value, out u16 result)) + // Decimal constant + return result; + else + { + throw new ArgumentException($"Invalid constant value: {value}"); + } + } + + private string Clean(string value) + { + if (value.StartsWith("@")) + value = value[1..]; // Remove @ for indirect addressing + + if (value.StartsWith("[") && value.EndsWith("]")) + value = value[1..^1].Trim(); // Remove brackets for direct addressing + + return value.Trim(); + } + + private void Write(u16 value) + { + u16 swappedValue = (u16) ((value >> 8) | (value << 8)); + this.Writer.Write(swappedValue); + } + + private void Write(u8 value) + { + this.Writer.Write(value); + } + + private void Write(i16 value) + { + u16 swappedValue = (u16) ((value >> 8) | (value << 8)); + this.Writer.Write(swappedValue); + } } \ No newline at end of file diff --git a/Assembler/Disassembler.cs b/Assembler/Disassembler.cs new file mode 100644 index 0000000..9b1c44d --- /dev/null +++ b/Assembler/Disassembler.cs @@ -0,0 +1,87 @@ +namespace Q1Emu.Assembler; + +using System; +using System.Collections.Generic; + +public class Disassembler +{ + public static Dictionary InstructionLookup = new() + { + { 0x00, ["NOP", "JMP", "HALT", "MOV"] }, + { 0x01, ["RET", "CALL", "SUS", "CMP"] }, + { 0x02, ["BR", "BZ", "BZ_LX", "LT"] }, + { 0x03, ["NOP", "NOP", "NOP", "GT"] }, + + { 0x04, ["NOT_LX", "NOT", "NOP", "AND"] }, + { 0x05, ["INV_DX", "INV", "NOP", "OR"] }, + { 0x06, ["SHL_DX", "SHL", "NOP", "XOR"] }, + { 0x07, ["SHR_DX", "SHR", "NOP", "NOP"] }, + + { 0x08, ["INC_AX", "INC", "NOP", "ADD"] }, + { 0x09, ["DEC_AX", "DEC", "NOP", "SUB"] }, + { 0x0A, ["NOP", "NOP", "NOP", "DIV"] }, + { 0x0B, ["NOP", "NOP", "NOP", "MUL"] }, + { 0x0C, ["NOP", "NOP", "NOP", "MOD"] }, + }; + + public static Dictionary ModeLookup = new() + { + { 0x00, "" }, + { 0x01, "c" }, + { 0x02, "[a]" }, + { 0x03, "@[a]" }, + { 0x04, "pc + c" }, + { 0x05, "r" }, + { 0x06, "[r]" }, + { 0x07, "[r + c]" }, + }; + + public static string ParseInstruction(u16 instruction) + { + u8 op = (u8) ((instruction >> 8) & 0xFF); // 256 opcodes + u8 m1 = (u8) ((instruction >> 4) & 0x0F); // 16 modes + u8 m2 = (u8) (instruction & 0x0F); // 16 modes + + u8 i = (m1, m2) switch + { + (0x00, 0x00) => 0, // Implicit + (0x00, _) => 2, // Extended implicit + (_, 0x00) => 1, // Simple addressing mode + _ => 3 // Extended addressing mode + }; + + string opcodeName = GetOpcodeName(op, i); + string mode1 = GetModeName(m1); + string mode2 = GetModeName(m2); + + if (i == 0) + return opcodeName; + + if (i == 2) + return $"{opcodeName}"; + + if (i == 1) + return $"{opcodeName} {mode1}"; + + if (i == 3) + return $"{opcodeName} {mode1}, {mode2}"; + + throw new InvalidOperationException($"Unknown instruction format for opcode {op:X2} with modes {m1:X2}, {m2:X2}"); + } + + private static string GetOpcodeName(u8 opcode, u8 i) + { + if (!InstructionLookup.ContainsKey(opcode) || i >= InstructionLookup[opcode].Length) + return $"{opcode:X2}"; + + return InstructionLookup[opcode][i]; + } + + private static string GetModeName(u8 mode) + { + if (ModeLookup.TryGetValue(mode, out string modeName)) + return modeName; + + return $"[{mode:X2}]"; // Fallback for unknown modes + } +} \ No newline at end of file -- cgit v1.3.1