summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Q1.core/Arch/Constants/ChipRegisters.cs36
-rw-r--r--Q1.core/Arch/Lookups/InstructionSet.cs25
-rw-r--r--Q1.core/Pipeline/Assembler.cs320
-rw-r--r--Q1.core/Pipeline/AssemblerBlock.cs60
-rw-r--r--Q1.core/Pipeline/Disassembler.cs36
-rw-r--r--Q1.tests/Core/Instructions/LogicInstructionTests.cs151
-rw-r--r--Q1.tests/Pipeline/AssemblerTests.cs59
7 files changed, 687 insertions, 0 deletions
diff --git a/Q1.core/Arch/Constants/ChipRegisters.cs b/Q1.core/Arch/Constants/ChipRegisters.cs
index 042ce32..40caf6a 100644
--- a/Q1.core/Arch/Constants/ChipRegisters.cs
+++ b/Q1.core/Arch/Constants/ChipRegisters.cs
@@ -16,4 +16,40 @@ public static class ChipRegisters
public const u8 PC = 0x4C;
public const u8 SP = 0x4D;
+
+ public static u8? GetRegisterIndex(string name)
+ {
+ return name switch
+ {
+ "AX" => AX,
+ "AH" => AH,
+ "AL" => AL,
+ "DX" => DX,
+ "DH" => DH,
+ "DL" => DL,
+ "LX" => LX,
+ "PC" => PC,
+ "SP" => SP,
+ _ when name.StartsWith("V") && byte.TryParse(name[1..], out u8 index) && index < GENERIC_REGISTER_COUNT => index,
+ _ => null
+ };
+ }
+
+ public static string? GetRegisterName(u8 index)
+ {
+ return index switch
+ {
+ AX => "AX",
+ AH => "AH",
+ AL => "AL",
+ DX => "DX",
+ DH => "DH",
+ DL => "DL",
+ LX => "LX",
+ PC => "PC",
+ SP => "SP",
+ _ when index < GENERIC_REGISTER_COUNT => $"V{index}",
+ _ => null
+ };
+ }
} \ No newline at end of file
diff --git a/Q1.core/Arch/Lookups/InstructionSet.cs b/Q1.core/Arch/Lookups/InstructionSet.cs
index 2ea20fc..ad81f2e 100644
--- a/Q1.core/Arch/Lookups/InstructionSet.cs
+++ b/Q1.core/Arch/Lookups/InstructionSet.cs
@@ -20,6 +20,31 @@ public static class InstructionSet
throw new ArgumentException($"Instruction '{instructionName}' not found in the instruction set.");
}
+ public static u8 GetOpcodeWithIndex(string instructionName, out u8 index)
+ {
+ for (u8 opcode = 0; opcode < InstructionSet.Lookup.Length; opcode++)
+ {
+ var group = InstructionSet.Lookup[opcode];
+ if (group.Implicit.Name == instructionName ||
+ group.SimpleAddressing.Name == instructionName ||
+ group.ExtendedImplicit.Name == instructionName ||
+ group.ExtendedAddressing.Name == instructionName)
+ {
+ if (group.Implicit.Name == instructionName)
+ index = 0;
+ else if (group.SimpleAddressing.Name == instructionName)
+ index = 1;
+ else if (group.ExtendedImplicit.Name == instructionName)
+ index = 2;
+ else
+ index = 3;
+
+ return opcode;
+ }
+ }
+ throw new ArgumentException($"Instruction '{instructionName}' not found in the instruction set.");
+ }
+
static InstructionSet()
{
var nop = new ImplicitInstruction { Name = "NOP", Execute = InstructionSet.Nop };
diff --git a/Q1.core/Pipeline/Assembler.cs b/Q1.core/Pipeline/Assembler.cs
new file mode 100644
index 0000000..2a11bbb
--- /dev/null
+++ b/Q1.core/Pipeline/Assembler.cs
@@ -0,0 +1,320 @@
+namespace Q1.core.Pipeline;
+
+using Q1.core.Arch;
+using Q1.core.Arch.Constants;
+using Q1.core.Arch.Lookups;
+
+public class Assembler
+{
+ private Dictionary<string, AssemblerBlock> _blocks = new();
+ private string _currentBlock = "";
+ private u16 _addressOffset = 0;
+
+ public MemoryStream Assemble(TextReader source)
+ {
+ this._blocks[this._currentBlock] = new AssemblerBlock();
+
+ while (source.ReadLine() is { } line)
+ {
+ line = this.StripComments(line);
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ if (line.StartsWith('.'))
+ this.HandleDirective(line[1..]);
+ else if (line.EndsWith(':'))
+ this.HandleLabel(line[..^1]);
+ else
+ this.HandleInstruction(line);
+ }
+
+ return this.CreateFinalBinary();
+ }
+
+ private MemoryStream CreateFinalBinary()
+ {
+ Dictionary<string, u16> labelAddresses = new();
+ u16 currentAddress = this._addressOffset;
+ foreach ((string key, AssemblerBlock value) in this._blocks)
+ {
+ labelAddresses[key] = currentAddress;
+ currentAddress += (u16) value.Size;
+ }
+
+ MemoryStream finalBinary = new();
+ foreach ((_, AssemblerBlock value) in this._blocks)
+ {
+ foreach ((i64 offset, (bool word, string constant)) in value.DeferredConstants)
+ {
+ if (!labelAddresses.TryGetValue(constant, out u16 address))
+ throw new Exception($"Undefined label: {constant}");
+
+ if (word)
+ value.ResolveWordConstant((i32) offset, address);
+ else
+ value.ResolveByteConstant((i32) offset, (u8) address);
+ }
+
+ value.WriteToStream(finalBinary);
+ }
+
+ return finalBinary;
+ }
+
+ private void HandleDirective(string directive)
+ {
+ string[] parts = directive
+ .Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.ToLower())
+ .ToArray();
+
+ string directiveName = parts[0];
+ string[] args = parts[1..];
+
+ if (directiveName == "org")
+ {
+ if (args.Length != 1)
+ throw new Exception($"Expected 1 argument for .org directive, got {args.Length}");
+
+ if (args[0].StartsWith("0x"))
+ this._addressOffset = Convert.ToUInt16(args[0][2..], 16);
+ else if (args[0].StartsWith("0b"))
+ this._addressOffset = Convert.ToUInt16(args[0][2..], 2);
+ else if (i32.TryParse(args[0], out i32 decimalValue))
+ this._addressOffset = (u16) decimalValue;
+ else
+ throw new Exception($"Invalid argument for .org directive: {args[0]}");
+ }
+ else
+ throw new Exception($"Unknown directive: {directiveName}");
+ }
+
+ private void HandleLabel(string label)
+ {
+ this._currentBlock = label;
+ if (this._blocks.ContainsKey(label))
+ throw new Exception($"Duplicate label: {label}");
+ this._blocks[label] = new AssemblerBlock();
+ }
+
+ private void HandleInstruction(string instruction)
+ {
+ string[] parts = instruction
+ .Split([' ', '\t', ','], StringSplitOptions.RemoveEmptyEntries);
+
+ string opcodeName = parts[0].ToUpper();
+ bool isWordInstruction = !(parts.Length > 1 && parts[1].Equals("byte", StringComparison.InvariantCultureIgnoreCase));
+
+ string[] args = isWordInstruction ? parts[1..] : parts[2..];
+
+ var opcode = InstructionSet.GetOpcodeWithIndex(opcodeName, out u8 opcodeIndex);
+ switch (opcodeIndex)
+ {
+ case 0: // Implicit
+ {
+ if (args.Length != 0)
+ throw new Exception($"Expected 0 arguments for opcode {opcodeName}, got {args.Length}");
+
+ this.WriteInstruction(opcode, 0, 0, isWordInstruction);
+
+ break;
+ }
+
+ case 1: // Simple Addressing
+ {
+ if (args.Length != 1)
+ throw new Exception($"Expected 1 argument for opcode {opcodeName}, got {args.Length}");
+
+ u8 m1 = this.GetArgumentType(args[0]);
+ this.WriteInstruction(opcode, m1, 0, isWordInstruction);
+ this.WriteArgument(args[0], isWordInstruction);
+
+ break;
+ }
+
+ case 2: // Extended Implicit
+ {
+ if (args.Length != 0)
+ throw new Exception($"Expected 0 arguments for opcode {opcodeName}, got {args.Length}");
+
+ this.WriteInstruction(opcode, 0, 1, isWordInstruction);
+
+ break;
+ }
+
+ case 3: // Extended Addressing
+ {
+ if (args.Length != 2)
+ throw new Exception($"Expected 2 arguments for opcode {opcodeName}, got {args.Length}");
+
+ u8 m1 = this.GetArgumentType(args[0]);
+ u8 m2 = this.GetArgumentType(args[1]);
+
+ this.WriteInstruction(opcode, m1, m2, isWordInstruction);
+ this.WriteArgument(args[0], isWordInstruction);
+ this.WriteArgument(args[1], isWordInstruction);
+
+ break;
+ }
+ }
+ }
+
+ private void WriteInstruction(u8 opcode, u8 m1, u8 m2, bool word)
+ {
+ u16 instruction = InstructionEncoding.Encode(opcode, m1, m2, word);
+ this.WriteWordToBlock(instruction);
+ }
+
+ private u8 GetArgumentType(string arg)
+ {
+ if (arg.StartsWith('[') && arg.EndsWith(']'))
+ {
+ string inner = arg[1..^1];
+
+ if (ChipRegisters.GetRegisterIndex(inner.ToUpper()) != null)
+ return AddressingModes.GetAddressingMode("Register Indirect");
+
+ if (inner.Contains('+'))
+ return AddressingModes.GetAddressingMode("Register Indirect with Offset");
+
+ return AddressingModes.GetAddressingMode("Direct");
+ }
+
+ if (arg.StartsWith('@'))
+ {
+ if (arg.StartsWith("@[") && arg.EndsWith(']'))
+ return AddressingModes.GetAddressingMode("Indirect");
+ else
+ throw new Exception($"Invalid indirect argument format: {arg}");
+ }
+
+ if (ChipRegisters.GetRegisterIndex(arg.ToUpper()) != null)
+ return AddressingModes.GetAddressingMode("Register");
+
+ return AddressingModes.GetAddressingMode("Immediate");
+ }
+
+ private void WriteArgument(string arg, bool isWord)
+ {
+ if (arg.StartsWith('[') && arg.EndsWith(']'))
+ {
+ string inner = arg[1..^1];
+
+ if (ChipRegisters.GetRegisterIndex(inner.ToUpper()) is { } registerIndex)
+ this.WriteByteToBlock(registerIndex);
+
+ else if (inner.Contains('+'))
+ {
+ string[] parts = inner.Split('+', StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length != 2)
+ throw new Exception($"Invalid register indirect with offset format: {arg}");
+
+ string registerPart = parts[0].Trim().ToUpper();
+ string offsetPart = parts[1].Trim();
+
+ if (ChipRegisters.GetRegisterIndex(registerPart) is not { } regIndex)
+ throw new Exception($"Invalid register in register indirect with offset: {registerPart}");
+
+ this.WriteByteToBlock(regIndex);
+ this.WriteConstant(offsetPart, isWord);
+ }
+ else
+ this.WriteConstant(inner, isWord);
+ }
+
+ else if (arg.StartsWith('@'))
+ {
+ if (arg.StartsWith("@[") && arg.EndsWith(']'))
+ {
+ string inner = arg[2..^1];
+
+ this.WriteConstant(inner, isWord);
+ }
+ else
+ throw new Exception($"Invalid indirect argument format: {arg}");
+ }
+
+ else if (ChipRegisters.GetRegisterIndex(arg.ToUpper()) is { } registerIndex)
+ this.WriteByteToBlock(registerIndex);
+ else
+ this.WriteConstant(arg, isWord);
+ }
+
+ private void WriteConstant(string constant, bool isWord)
+ {
+ // hex 0x0-9a-fA-F
+ if (constant.StartsWith("0X", StringComparison.InvariantCultureIgnoreCase))
+ {
+ string hexPart = constant[2..];
+ if (isWord)
+ this.WriteWordToBlock(Convert.ToUInt16(hexPart, 16));
+ else
+ this.WriteByteToBlock(Convert.ToByte(hexPart, 16));
+ }
+ // binary 0b0-1
+ else if (constant.StartsWith("0B", StringComparison.InvariantCultureIgnoreCase))
+ {
+ string binaryPart = constant[2..];
+ if (isWord)
+ this.WriteWordToBlock(Convert.ToUInt16(binaryPart, 2));
+ else
+ this.WriteByteToBlock(Convert.ToByte(binaryPart, 2));
+ }
+ // char `a, etc.
+ else if (constant.StartsWith('`') && constant.Length == 2)
+ {
+ char charValue = constant[1];
+ if (isWord)
+ this.WriteWordToBlock(charValue);
+ else
+ this.WriteByteToBlock((u8) charValue);
+ }
+
+ // decimal 0-9+
+ else if (i32.TryParse(constant, out i32 decimalValue))
+ {
+ if (isWord)
+ this.WriteWordToBlock((u16) decimalValue);
+ else
+ this.WriteByteToBlock((u8) decimalValue);
+ }
+
+ // label
+ else
+ {
+ if (isWord)
+ this.DeferWriteWordToBlock(constant);
+ else
+ this.DeferWriteByteToBlock(constant);
+ }
+ }
+
+ private void WriteByteToBlock(u8 data)
+ {
+ this._blocks[this._currentBlock].WriteByte(data);
+ }
+
+ private void WriteWordToBlock(u16 data)
+ {
+ this._blocks[this._currentBlock].WriteWord(data);
+ }
+
+ private void DeferWriteByteToBlock(string constant)
+ {
+ this._blocks[this._currentBlock].DeferByteConstant(constant);
+ }
+
+ private void DeferWriteWordToBlock(string constant)
+ {
+ this._blocks[this._currentBlock].DeferWordConstant(constant);
+ }
+
+ private string StripComments(string line)
+ {
+ int commentIndex = line.IndexOf(';');
+ if (commentIndex >= 0)
+ return line[..commentIndex].Trim();
+
+ return line.Trim();
+ }
+} \ No newline at end of file
diff --git a/Q1.core/Pipeline/AssemblerBlock.cs b/Q1.core/Pipeline/AssemblerBlock.cs
new file mode 100644
index 0000000..e40cbec
--- /dev/null
+++ b/Q1.core/Pipeline/AssemblerBlock.cs
@@ -0,0 +1,60 @@
+namespace Q1.core.Pipeline;
+
+public class AssemblerBlock
+{
+ private readonly MemoryStream _stream = new();
+ public readonly Dictionary<i64, (bool word, string constant)> DeferredConstants = new();
+
+ public long Size => this._stream.Length;
+
+ public void WriteByte(u8 data)
+ {
+ this._stream.WriteByte(data);
+ }
+
+ public void WriteWord(u16 data)
+ {
+ u8 high = (u8) (data & 0xFF);
+ u8 low = (u8) ((data >> 8) & 0xFF);
+ this._stream.WriteByte(low);
+ this._stream.WriteByte(high);
+ }
+
+ public void DeferWordConstant(string constant)
+ {
+ this.DeferredConstants.Add(this._stream.Length - 1, (true, constant));
+ this.WriteWord(0);
+ }
+
+ public void DeferByteConstant(string constant)
+ {
+ this.DeferredConstants.Add(this._stream.Length - 1, (false, constant));
+ this.WriteByte(0);
+ }
+
+ public void ResolveWordConstant(i32 offset, u16 value)
+ {
+ long currentPos = this._stream.Position;
+ this._stream.Position = offset;
+
+ this.WriteWord(value);
+
+ this._stream.Position = currentPos;
+ }
+
+ public void ResolveByteConstant(i32 offset, u8 value)
+ {
+ long currentPos = this._stream.Position;
+ this._stream.Position = offset;
+
+ this.WriteByte(value);
+
+ this._stream.Position = currentPos;
+ }
+
+ public void WriteToStream(MemoryStream target)
+ {
+ this._stream.Position = 0;
+ this._stream.CopyTo(target);
+ }
+} \ No newline at end of file
diff --git a/Q1.core/Pipeline/Disassembler.cs b/Q1.core/Pipeline/Disassembler.cs
new file mode 100644
index 0000000..ae93e12
--- /dev/null
+++ b/Q1.core/Pipeline/Disassembler.cs
@@ -0,0 +1,36 @@
+namespace Q1.core.Pipeline;
+
+using Arch;
+using Arch.Lookups;
+
+public class Disassembler
+{
+ public StringWriter Disassemble(MemoryStream stream)
+ {
+ var reader = new BinaryReader(stream);
+ var writer = new StringWriter();
+
+ while (reader.BaseStream.Position < reader.BaseStream.Length)
+ {
+ u16 instruction = reader.ReadUInt16();
+ InstructionEncoding.Decode(instruction, out u8 opcode, out u8 m1, out u8 m2, out bool word);
+
+ var group = InstructionSet.Lookup[opcode];
+ (string name, int param) = (m1, m2) switch
+ {
+ (0x00, 0x00) => (group.Implicit.Name, 0),
+ (_, 0x00) => (group.SimpleAddressing.Name, 1),
+ (0x00, 0x01) => (group.ExtendedImplicit.Name, 0),
+ (_, _) => (group.ExtendedAddressing.Name, 2)
+ };
+
+ writer.Write(name);
+ if (param == 1)
+ {
+
+ }
+ }
+
+ return writer;
+ }
+} \ No newline at end of file
diff --git a/Q1.tests/Core/Instructions/LogicInstructionTests.cs b/Q1.tests/Core/Instructions/LogicInstructionTests.cs
new file mode 100644
index 0000000..d0c0558
--- /dev/null
+++ b/Q1.tests/Core/Instructions/LogicInstructionTests.cs
@@ -0,0 +1,151 @@
+namespace Q1.tests.Core.Instructions;
+
+using core.Arch;
+using core.Arch.Lookups;
+using core.Components;
+
+[TestFixture]
+public class LogicInstructionTests : InstructionTestBase
+{
+ [TestCase(0x0000, 0x01)]
+ [TestCase(0x0001, 0x00)]
+ [TestCase(0x0005, 0x00)]
+ public void NotLxInstruction_PerformsNot(i32 input, i32 output)
+ {
+ // Arrange
+ var chip = this.CreateChip();
+ chip.Lx = (u16) input;
+
+ var instruction = InstructionEncoding
+ .Encode(
+ InstructionSet.GetOpcode("NOT(LX)"),
+ 0,
+ 0,
+ true);
+
+ chip.Bus.WriteWord(chip.Pc, instruction);
+
+ // Act
+ chip.Clock();
+
+ // Assert
+ u16 expected = (u16) output;
+ Assert.That(chip.Lx, Is.EqualTo(expected));
+ }
+
+ [TestCase(0x0000, 0x01)]
+ [TestCase(0x0001, 0x00)]
+ [TestCase(0x0005, 0x00)]
+ public void NotInstruction_PerformsNot(i32 input, i32 output)
+ {
+ // Arrange
+ var chip = this.CreateChip();
+
+ var instruction = InstructionEncoding
+ .Encode(
+ InstructionSet.GetOpcode("NOT"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ 0,
+ true);
+
+ chip.Bus.WriteWord(chip.Pc, instruction);
+ chip.Bus.WriteWord((u16) (chip.Pc + 2), (u16) input);
+
+ // Act
+ chip.Clock();
+
+ // Assert
+ u16 expected = (u16) output;
+ Assert.That(chip.Lx, Is.EqualTo(expected));
+ }
+
+ [TestCase(0x0000, 0x0000, 0x01)]
+ [TestCase(0x0000, 0x0001, 0x00)]
+ [TestCase(0x0001, 0x0000, 0x00)]
+ [TestCase(0x0001, 0x0001, 0x01)]
+ [TestCase(0x1234, 0x5678, 0x00)]
+ [TestCase(0x8342, 0x8342, 0x01)]
+ public void CompareInstruction_ComparesCorrectly(i32 a, i32 b, i32 expected)
+ {
+ // Arrange
+ var chip = this.CreateChip();
+
+ var instruction = InstructionEncoding
+ .Encode(
+ InstructionSet.GetOpcode("CMP"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ true);
+
+ chip.Bus.WriteWord(chip.Pc, instruction);
+ chip.Bus.WriteWord((u16) (chip.Pc + 2), (u16) a);
+ chip.Bus.WriteWord((u16) (chip.Pc + 4), (u16) b);
+
+ // Act
+ chip.Clock();
+
+ // Assert
+ u16 expected16 = (u16) expected;
+ Assert.That(chip.Lx, Is.EqualTo(expected16));
+ }
+
+ [TestCase(0x0000, 0x0000, 0x00)]
+ [TestCase(0x0000, 0x0001, 0x01)]
+ [TestCase(0x0001, 0x0000, 0x00)]
+ [TestCase(0x0001, 0x0001, 0x00)]
+ [TestCase(0x1234, 0x5678, 0x01)]
+ [TestCase(0x8342, 0x1234, 0x00)]
+ public void LessThanInstruction_ComparesCorrectly(i32 a, i32 b, i32 expected)
+ {
+ // Arrange
+ var chip = this.CreateChip();
+
+ var instruction = InstructionEncoding
+ .Encode(
+ InstructionSet.GetOpcode("LT"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ true);
+
+ chip.Bus.WriteWord(chip.Pc, instruction);
+ chip.Bus.WriteWord((u16) (chip.Pc + 2), (u16) a);
+ chip.Bus.WriteWord((u16) (chip.Pc + 4), (u16) b);
+
+ // Act
+ chip.Clock();
+
+ // Assert
+ u16 expected16 = (u16) expected;
+ Assert.That(chip.Lx, Is.EqualTo(expected16));
+ }
+
+ [TestCase(0x0000, 0x0000, 0x00)]
+ [TestCase(0x0000, 0x0001, 0x00)]
+ [TestCase(0x0001, 0x0000, 0x01)]
+ [TestCase(0x0001, 0x0001, 0x00)]
+ [TestCase(0x5678, 0x1234, 0x01)]
+ [TestCase(0x1234, 0x8342, 0x00)]
+ public void GreaterThanInstruction_ComparesCorrectly(i32 a, i32 b, i32 expected)
+ {
+ // Arrange
+ var chip = this.CreateChip();
+
+ var instruction = InstructionEncoding
+ .Encode(
+ InstructionSet.GetOpcode("GT"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ true);
+
+ chip.Bus.WriteWord(chip.Pc, instruction);
+ chip.Bus.WriteWord((u16) (chip.Pc + 2), (u16) a);
+ chip.Bus.WriteWord((u16) (chip.Pc + 4), (u16) b);
+
+ // Act
+ chip.Clock();
+
+ // Assert
+ u16 expected16 = (u16) expected;
+ Assert.That(chip.Lx, Is.EqualTo(expected16));
+ }
+} \ No newline at end of file
diff --git a/Q1.tests/Pipeline/AssemblerTests.cs b/Q1.tests/Pipeline/AssemblerTests.cs
new file mode 100644
index 0000000..4eb36bb
--- /dev/null
+++ b/Q1.tests/Pipeline/AssemblerTests.cs
@@ -0,0 +1,59 @@
+namespace Q1.tests.Pipeline;
+
+using core.Arch;
+using core.Arch.Constants;
+using core.Arch.Lookups;
+using core.Pipeline;
+
+[TestFixture]
+public class AssemblerTests
+{
+ private Assembler CreateAssembler()
+ {
+ return new Assembler();
+ }
+
+ private byte[] Assemble(string source)
+ {
+ var assembler = this.CreateAssembler();
+ var reader = new StringReader(source);
+ return assembler.Assemble(reader).ToArray();
+ }
+
+ [Test]
+ public void Assembler_ShouldAssembleSimpleProgram()
+ {
+ // Arrange
+ string source = """
+ mov 0x1234, ax
+ """;
+
+ // Act
+ var result = Assemble(source);
+
+ // Assert
+ MemoryStream expected = new MemoryStream();
+ WriteWord(expected, InstructionEncoding.Encode(
+ InstructionSet.GetOpcode("MOV"),
+ AddressingModes.GetAddressingMode("Immediate"),
+ AddressingModes.GetAddressingMode("Register"),
+ true));
+ WriteWord(expected, 0x1234);
+ WriteByte(expected, ChipRegisters.AX);
+
+ Assert.That(result, Is.EqualTo(expected.ToArray()));
+ }
+
+ private void WriteByte(MemoryStream stream, byte value)
+ {
+ stream.WriteByte(value);
+ }
+
+ private void WriteWord(MemoryStream stream, ushort value)
+ {
+ u8 high = (u8) (value & 0xFF);
+ u8 low = (u8) ((value >> 8) & 0xFF);
+ stream.WriteByte(low);
+ stream.WriteByte(high);
+ }
+} \ No newline at end of file