From 16f3cb74564cf7c19da6e13666c98aca36094716 Mon Sep 17 00:00:00 2001 From: Melodi <62381002+Melodi17@users.noreply.github.com> Date: Tue, 5 Aug 2025 21:24:16 +1000 Subject: updates --- Assembler/Assembler.cs | 297 +++++++++++++++++++++++++++++++++------------- Assembler/Disassembler.cs | 67 +++++------ 2 files changed, 240 insertions(+), 124 deletions(-) (limited to 'Assembler') diff --git a/Assembler/Assembler.cs b/Assembler/Assembler.cs index 5df068d..09ac30e 100644 --- a/Assembler/Assembler.cs +++ b/Assembler/Assembler.cs @@ -2,16 +2,32 @@ namespace Q1Emu.Assembler; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using Chip; public class Assembler { public readonly BinaryWriter Writer; public readonly StringReader Reader; - private Dictionary _labels = new(); - private u16 _addressOffset = 0; + private Dictionary _labels = new(); + private Dictionary _unloadedLabels = new(); + private u16 _addressOffset = 0; + + public static Dictionary RegisterLookup = new() + { + { 0x40, "AX" }, + { 0x41, "AH" }, + { 0x42, "AL" }, + { 0x43, "DX" }, + { 0x44, "DH" }, + { 0x45, "DL" }, + { 0x46, "LX" }, + { 0x50, "PC" }, + { 0x51, "SP" }, + }; public Assembler(StringReader reader, BinaryWriter writer) { @@ -21,8 +37,10 @@ public class Assembler public void Assemble() { + int lineNumber = 0; while (this.Reader.ReadLine() is { } line) { + lineNumber++; string trimmedLine = line.Split(";")[0].Trim(); // Remove comments if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith(';')) continue; @@ -32,17 +50,91 @@ public class Assembler // 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.StartsWith(".ascii")) + { + string asciiValue = trimmedLine[7..].Trim() + [1..^1]; // Remove quotes + + for (int i = 0; i < asciiValue.Length; i++) + { + char c = asciiValue[i]; + if (c == '\\') + c = asciiValue[++i] switch + { + 'n' => '\n', + 't' => '\t', + '\\' => '\\', + '\'' => '\'', + '"' => '"', + _ => throw new AssemblerException($"Unknown escape sequence: \\{asciiValue[i]}"), + }; + + this.Write((u8) c); + } + } + + else if (trimmedLine.StartsWith(".raw")) + { + string[] values = trimmedLine[5..].Split(','); + foreach (string value in values) + this.Write(GetConstant(value.Trim())); + } + + else if (trimmedLine.StartsWith(".include")) + { + string includeFile = trimmedLine[9..].Trim()[1..^1]; + if (!File.Exists(includeFile)) + { + WriteError(line, lineNumber, $"Include file '{includeFile}' not found"); + return; + } + + foreach (byte b in File.ReadAllBytes(includeFile)) + this.Writer.Write(b); } else if (trimmedLine.EndsWith(":")) this._labels[trimmedLine[..^1]] = (u16) (this.Writer.BaseStream.Position + this._addressOffset); - + + else + { + try + { + this.ParseInstruction(trimmedLine); + } + catch (AssemblerException ex) + { + WriteError(line, lineNumber, ex.Message); + return; + } + } + } + + foreach ((u16 key, string value) in this._unloadedLabels) + { + if (this._labels.TryGetValue(value, out u16 address)) + { + Console.WriteLine($"Resolving label '{value}' at address {address:X4} for key {key:X4}"); + this.Writer.BaseStream.Seek(key, SeekOrigin.Begin); + this.Write(address); + } else - this.ParseInstruction(trimmedLine); + { + WriteError($"", 0, "Label not defined"); + } } } + private void WriteError(string line, int lineNumber, string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Error on line {lineNumber}: {message}"); + Console.WriteLine($" --> {line}"); + Console.ResetColor(); + } + private void ParseInstruction(string instruction) { string[] parts = instruction.Split(' ', 2); @@ -50,37 +142,93 @@ public class Assembler parts = parts.Length > 1 ? parts[1].Split(',') : []; if (parts.Length > 2) - throw new ArgumentException($"Invalid instruction format: {instruction}"); + throw new AssemblerException($"Invalid instruction format"); - u8 opcodeValue = this.ParseOpcode(opcode); + u8 opcodeValue = this.ParseOpcode(opcode, parts.Length, out u8 addressingIndex); - u8 m1 = 0, m2 = 0; - if (parts.Length > 0) - m1 = this.ParseMode(parts[0]); - if (parts.Length > 1) - m2 = this.ParseMode(parts[1]); + u8 m1, m2; + switch (addressingIndex) + { + case 0: // Implicit + if (parts.Length > 0) + throw new AssemblerException($"Implicit instruction should not have operands"); + + m1 = 0x00; + m2 = 0x00; + break; + + case 1: // Simple addressing mode + if (parts.Length > 2) + throw new AssemblerException($"Simple addressing mode should have at most one operand"); + if (parts.Length == 0) + throw new AssemblerException($"Simple addressing mode requires an operand"); + + m1 = this.ParseMode(parts[0]); + m2 = 0x00; + break; + + case 2: // Extended implicit + if (parts.Length > 0) + throw new AssemblerException($"Extended implicit instruction should not have operands"); + + m1 = 0x00; + m2 = 0x01; + break; + + case 3: // Dual addressing mode + if (parts.Length != 2) + throw new AssemblerException($"Dual addressing mode requires exactly two operands"); + + m1 = this.ParseMode(parts[0]); + m2 = this.ParseMode(parts[1]); + break; + + default: + throw new UnreachableException("Invalid addressing index: " + addressingIndex); + } - 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]); } - private u8 ParseOpcode(string opcode) + private u8 ParseOpcode(string opcode, int parts, out u8 addressingIndex) { - return opcode switch + List addressingIndices = new(); + u8? opCode = null; + foreach ((u8 key, string[] value) in Q1Cpu.InstructionLookupStrings) { - "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}") - }; + if (value.Contains(opcode)) + { + addressingIndices.AddRange(value.AllIndexesOf(opcode)); + if (opCode is not null && opCode != key) + throw new AssemblerException($"Ambiguous opcode '{opcode}' matches multiple instructions: {opCode:X4} and {key:X4}"); + + opCode = key; + } + } + + if (opCode is null) + throw new AssemblerException($"Unknown opcode '{opcode}'"); + + int Parts(int index) + => index switch + { + 0 => 0, // Implicit + 1 => 1, // Simple addressing mode + 2 => 0, // Extended implicit + 3 => 2, // Dual addressing mode + _ => throw new UnreachableException("Invalid addressing index: " + index) + }; + + if (addressingIndices.All(x => Parts(x) != parts)) + throw new AssemblerException($"Opcode '{opcode}' does not match the expected number of operands: {parts}"); + + addressingIndex = (u8) addressingIndices.First(x => Parts(x) == parts); + return opCode.Value; } private u8 ParseMode(string mode) @@ -92,20 +240,7 @@ public class Assembler if (text.Length > 1 && text[0] == 'V') return true; - string[] hardcodedRegisters = - [ - "AX", - "AH", - "AL", - "DX", - "DH", - "DL", - "LX", - "PC", - "SP", - ]; - - return hardcodedRegisters.Contains(text); + return Assembler.RegisterLookup.ContainsValue(text); } if (this._labels.ContainsKey(mode)) @@ -157,12 +292,12 @@ public class Assembler this.Write(immediateValue); break; - case 0x02: // Direct + case 0x02: // Direct u16 directAddress = this.GetConstant(operand); // Remove brackets this.Write(directAddress); break; - case 0x03: // Indirect + case 0x03: // Indirect u16 indirectAddress = this.GetConstant(operand); // Remove brackets and @ this.Write(indirectAddress); break; @@ -177,7 +312,7 @@ public class Assembler Write(regIndex); break; - case 0x06: // Register Indirect + case 0x06: // Register Indirect u8 indirectRegIndex = this.GetRegisterIndex(operand); // Remove brackets Write(indirectRegIndex); break; @@ -190,54 +325,26 @@ public class Assembler 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 + if (Assembler.RegisterLookup.ContainsValue(reg)) { - "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}") - }; + // Find the register index by value + return (u8) Assembler.RegisterLookup.FirstOrDefault(x => x.Value == reg).Key; + } + + throw new AssemblerException($"Invalid register: {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 @@ -249,7 +356,26 @@ public class Assembler return result; else { - throw new ArgumentException($"Invalid constant value: {value}"); + this._unloadedLabels[(u16) this.Writer.BaseStream.Position] = value; + return 0; + } + } + 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 + { + this._unloadedLabels[(u16) this.Writer.BaseStream.Position] = value; + return 0; } } @@ -257,27 +383,30 @@ public class Assembler { 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 +} + +public class AssemblerException(string message) + : Exception(message); \ No newline at end of file diff --git a/Assembler/Disassembler.cs b/Assembler/Disassembler.cs index 9b1c44d..816ddbc 100644 --- a/Assembler/Disassembler.cs +++ b/Assembler/Disassembler.cs @@ -2,29 +2,21 @@ namespace Q1Emu.Assembler; using System; using System.Collections.Generic; +using System.IO; +using Chip; public class Disassembler { - public static Dictionary InstructionLookup = new() + public readonly StringWriter Writer; + public readonly BinaryReader Reader; + + public Disassembler(StringWriter writer, BinaryReader reader) { - { 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"] }, - }; + this.Writer = writer; + this.Reader = reader; + } - public static Dictionary ModeLookup = new() + private static Dictionary ModeLookup = new() { { 0x00, "" }, { 0x01, "c" }, @@ -35,7 +27,7 @@ public class Disassembler { 0x06, "[r]" }, { 0x07, "[r + c]" }, }; - + public static string ParseInstruction(u16 instruction) { u8 op = (u8) ((instruction >> 8) & 0xFF); // 256 opcodes @@ -45,43 +37,38 @@ public class Disassembler u8 i = (m1, m2) switch { (0x00, 0x00) => 0, // Implicit - (0x00, _) => 2, // Extended implicit + (0x00, 0x01) => 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) + if (i == 0 || i == 2) return opcodeName; - - if (i == 2) - return $"{opcodeName}"; - + if (i == 1) - return $"{opcodeName} {mode1}"; - + return $"{opcodeName} {GetModeName(m1)}"; + if (i == 3) - return $"{opcodeName} {mode1}, {mode2}"; - + return $"{opcodeName} {GetModeName(m1)}, {GetModeName(m2)}"; + 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]; + if (!Q1Cpu.InstructionLookupStrings.TryGetValue(opcode, out string[]? opNames)) + return $"[{opcode:X2}]"; // Fallback for unknown opcodes + + return opNames[i]; } - + private static string GetModeName(u8 mode) { - if (ModeLookup.TryGetValue(mode, out string modeName)) + 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