summaryrefslogtreecommitdiff
path: root/Assembler
diff options
context:
space:
mode:
Diffstat (limited to 'Assembler')
-rw-r--r--Assembler/Assembler.cs221
-rw-r--r--Assembler/Disassembler.cs87
2 files changed, 286 insertions, 22 deletions
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<string, u16> _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<u8, string[]> 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<u8, string> 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