// assembly.c // These are functions useful for translating assembly mnemonics to hex and vice-versa for the SplitBit CPU. // Written by Anachronaut // 10/18/2024 #include "assembly.h" #include typedef struct { uint8_t opcode; const char* mnemonic; } Instruction; Instruction instruction_set[] = { // ---- Nothing at all in 0x00 to 0x0F ---- // // Kept empty on purpose. Program Memory that has never been written, or a load that // stopped part way and left zeroes in its tail, used to read as a long run of // additions and then do something unpredictable a long way from the cause. An // unassigned byte faults where it is met, with the address, which is the difference // between a diagnosis and a search. // // Arithmetic and Logic Operations: {0x10, "ADD"}, {0x11, "SUB"}, {0x12, "AND"}, {0x13, "OR"}, {0x14, "XOR"}, {0x15, "NOTA"}, {0x16, "NOTB"}, {0x17, "SHL"}, {0x18, "SHR"}, // Branch Operations: {0x60, "BRI"}, {0x61, "BRQ"}, {0x62, "BRA"}, {0x63, "BRB"}, {0x64, "BRC"}, {0x65, "BRD"}, // The same four conditions the other way round. A quarter of the conditional // branches in the corpus were a branch over an unconditional one before these // existed, each of them needing a label invented only to be jumped past. {0x66, "BNQ"}, {0x67, "BNA"}, {0x68, "BNB"}, {0x69, "BNC"}, // Subroutine Operations: // // A block of their own since the branches and these outgrew one nibble between them. // Each raw form sits immediately below the ordinary one it cannot be mixed with: RCAL // under CALL, RRET under RET, because the frames differ and returning through the // wrong one takes the machine somewhere nobody named. {0x70, "RCAL"}, {0x71, "CALL"}, {0x72, "SWI"}, {0x73, "RETI"}, {0x74, "RRET"}, {0x75, "RET"}, // A handler with an answer. RETI restores everything and is how a hardware handler // says it was never here; this restores what a RET restores, and is how a service // says it has replied. See the note in cpu.c. {0x76, "SRET"}, // Register Operations: {0x20, "RSTA"}, {0x21, "RSTB"}, {0x22, "INCA"}, {0x23, "INCB"}, {0x24, "DECA"}, {0x25, "DECB"}, {0x26, "INIA"}, {0x27, "INIB"}, {0x28, "CCF"}, {0x29, "MVQA"}, {0x2A, "MVQB"}, {0x2B, "SIF"}, {0x2C, "CIF"}, // Stack Operations: {0x30, "PSHQ"}, {0x31, "PSHA"}, {0x32, "PSHB"}, {0x33, "PSHD"}, {0x34, "POPA"}, {0x35, "POPB"}, {0x36, "POPD"}, // Data Operations: {0x40, "INCD"}, {0x41, "DECD"}, {0x42, "LDA"}, {0x43, "LDB"}, {0x44, "STQ"}, {0x45, "STA"}, {0x46, "STB"}, {0x47, "SETD"}, {0x48, "DPUP"}, {0x49, "DPDN"}, {0x4A, "LDD"}, {0x4B, "STD"}, {0x4C, "MVSD"}, {0x4D, "MVDS"}, {0x4E, "DPUA"}, {0x4F, "DPDA"}, {0x50, "DPUW"}, {0x51, "DPDW"}, // Output Operations: {0xD0, "OUTQ"}, {0xD1, "OUTA"}, {0xD2, "OUTB"}, // Input Operations: {0xE0, "INA"}, {0xE1, "INB"}, // Special Operations: {0xF0, "NOP"}, {0xFE, "WAIT"}, {0xFF, "HALT"} }; int num_instructions = sizeof(instruction_set) / sizeof(Instruction); const char* getMnemonic(uint8_t opcode) { for (int i = 0; i < num_instructions; i++) { if (instruction_set[i].opcode == opcode) { return instruction_set[i].mnemonic; } } return "---"; } int dataPointerOperands(uint8_t opcode) { // How many Data Pointer selector bytes follow this opcode. Most instructions // have none. The ones that work through a pointer have one naming which pointer. // LDD and STD move a pointer through a pointer, so they name two. switch (opcode) { case 0x4A: // LDD case 0x4B: // STD return 2; case 0x65: // BRD case 0x33: // PSHD case 0x36: // POPD case 0x40: // INCD case 0x41: // DECD case 0x42: // LDA case 0x43: // LDB case 0x44: // STQ case 0x45: // STA case 0x46: // STB case 0x47: // SETD case 0x48: // DPUP case 0x49: // DPDN case 0x4C: // MVSD case 0x4D: // MVDS case 0x4E: // DPUA case 0x4F: // DPDA case 0x50: // DPUW case 0x51: // DPDW return 1; default: return 0; } } int getOpcode(char* mnemonic) { for (int i = 0; i < num_instructions; i++) { if (strcmp(instruction_set[i].mnemonic, mnemonic) == 0) { return instruction_set[i].opcode; } } // NOT_AN_OPCODE, and it is negative on purpose. This used to answer 0xFE on the // grounds that 0xFE was unused - which was true until WAIT was given that opcode, at // which point the assembler would have read WAIT as a word it did not recognise. A // sentinel picked from the unused half of a range stops being a sentinel the moment // somebody uses the range, so this one is outside the range altogether. return NOT_AN_OPCODE; }