chipd8/source/assembler.d

223 lines
6.4 KiB
D

module assembler;
import std.stdio : writefln;
struct Assembler {
/*
Instruction Glossary:
0x0nnn - SYS addr
0x00E0 - CLS
0x00EE - RET
0x1nnn - JP addr
0x2nnn - CALL addr
0x3xkk - SE Vx, byte
0x4xkk - SNE Vx, byte
0x5xy0 - SE Vx, Vy
0x6xkk - LD Vx, byte
0x7xkk - ADD Vx, byte
0x8xy0 - LD Vx, Vy
0x8xy1 - OR Vx, Vy
0x8xy2 - AND Vx, Vy
0x8xy3 - XOR Vx, Vy
0x8xy4 - ADD Vx, Vy
0x8xy5 - SUB Vx, Vy
0x8xy6 - SHR Vx {, Vy}
0x8xy7 - SUBN Vx, Vy
0x8xyE - SHL Vx {, Vy}
0x9xy0 - SNE Vx, Vy
0xAnnn - LD I, addr
0xBnnn - JP V0, addr
0xCxkk - RND Vx, byte
0xDxyn - DRW Vx, Vy, nibble
0xEx9E - SKP Vx
0xExA1 - SKNP Vx
0xFx07 - LD Vx, DT
0xFx0A - LD Vx, K
0xFx15 - LD DT, Vx
0xFx18 - LD ST, Vx
0xFx1E - ADD I, Vx
0xFx29 - LD F, Vx
0xFx33 - LD B, Vx
0xFx55 - LD [I], Vx
0xFx65 - LD Vx, [I]
*/
enum OpCode {
UNK, // UNKNOWN
CLS, // CLS
RET, // RET
CALL, // CALL addr
ADD, // ADD Vx, Vy
// ADD Vx, byte
// ADD I, Vx
SUB, // SUB Vx, Vy
// SUB Vx, byte
SUBN, // SUBN Vx, Vy
SE, // SE Vx, Vy
// SE Vx, byte
SNE, // SNE Vx, byte
SKP, // SKP Vx
SKNP, // SKNP Vx
SHL, // SHL Vx {, Vy}
SHR, // SHR Vx {, Vy}
XOR, // XOR Vx, Vy
AND, // AND Vx, Vy
OR, // OR Vx, Vy
LD, // LD Vx, DT
// LD DT, Vx
// LD ST, Vx
// LD Vx, K
// LD [I], Vx
// LD Vx, [I]
RND // RND Vx, byte
}
enum Argument {
Nil,
Vx,
Vy,
DT,
ST,
K,
addr,
val
}
struct Instruction {
OpCode op;
Argument a1;
Argument a2;
}
import std.array;
private {
Appender!(ubyte[]) assembled_data;
Appender!string dissassembled_data;
}
@disable this(this);
ubyte[] assemble(const char* input) {
return [];
} // assemble
ubyte[] assemble(const char[] input) {
return [];
} // assemble
const (char)[] disassemble(ubyte[] instructions) {
import std.range : chunks;
foreach (ref ubyte[] i; instructions.chunks(2)) {
ushort opcode = (*(cast(ushort*)(i.ptr)));
switch (opcode & 0xF000) {
case 0x0000:
switch (opcode & 0x0FFF) {
case 0x00E0: // 0x00E0 Clears the screen. | CLS
case 0x00EE: // 0x00EE Returns from a subroutine. | RET
default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs. | SYS addr
//assert(0, "0x0NNN RCA 1802 program opcode not implemented!");
break;
}
break;
case 0x1000: // 0x1NNN Jumps to address NNN. | JP addr
case 0x2000: // 0x2NNN Calls subroutine at NNN. | CALL addr
case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN. | SE Vx, byte
case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN. | SNE Vx, byte
case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY. | SE Vx, Vy
case 0x6000: // 0x6XNN Sets VX to NN. | LD Vx, byte
case 0x7000: // 0x7XNN Adds NN to VX. | AD Vx, byte
case 0x8000:
switch (opcode) {
case 0x0000: // 0x8XY0 Sets VX to the value of VY. | LD Vx, Vy
case 0x0001: // 0x8XY1 Sets VX to VX or VY. | OR Vx, Vy
case 0x0002: // 0x8XY2 Sets VX to VX and VY. | AND Vx, Vy
case 0x0003: // 0x8XY3 Sets VX to VX xor VY. | XOR Vx, Vy
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't. | ADD Vx, Vy
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't. | SUB Vx, Vy
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift. | SHR Vx {, Vy}
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't. | SUBN Vx, Vy
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift. | SHL Vx {, Vy}
default: // unhandled for some reason
writefln("unknown opcode: 0x%x", opcode);
break;
}
break;
case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY. | SNE Vx, Vy
case 0xA000: // 0xANNN Sets I to the address NNN. | LD I, addr
case 0xB000: // 0xBNNN Jumps to the address NNN plus V0. | JP V0, addr
case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN. | RND Vx, byte
// 0xDXYN | DRW Vx, Vy, nibble
// Sprites stored in memory at location in index register (I), 8bits wide.
// Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero.
// All drawing is XOR drawing (i.e. it toggles the screen pixels).
// Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn.
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
case 0xD000:
case 0xE000:
switch (opcode & 0x000F) {
case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed. | SKP Vx
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed. | SKNP Vx
default: // unhandled for some reason
writefln("unknown opcode: 0x%x", opcode);
break;
}
break;
case 0xF000:
switch (opcode & 0x00FF) {
case 0x0007: // 0xFX07 Sets VX to the value of the delay timer. | LD Vx, DT
case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX. | LD Vx, K
case 0x0015: // 0xFX15 Sets the delay timer to VX. | LD DT, Vx
case 0x0018: // 0xFX18 Sets the sound timer to VX. | LD ST, Vx
case 0x001E: // 0xFX1E Adds VX to I. | ADD I, Vx
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX. | LD F, Vx
// 0xFX33 Stores the Binary-coded decimal representation of VX,
// with the most significant of three digits at the address in I,
// the middle digit at I plus 1, and the least significant digit at I plus 2.
case 0x0033: // 0xFX33 ??? FIXME? | LD B, Vx
case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I. | LD [I], Vx
case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I. | LD Vx, [I]
default: // unhandled for some reason
writefln("unknown opcode: 0x%x", opcode);
break;
}
break;
default:
writefln("unknown opcode: 0x%x", opcode);
}
}
return "";
} // dissassemble
} // Assembler