diff --git a/src/a64instruction.h b/src/a64instruction.h index 463e75a..c12584c 100644 --- a/src/a64instruction.h +++ b/src/a64instruction.h @@ -1,8 +1,11 @@ #ifndef __A64INSTRUCTION__ #define __A64INSTRUCTION__ #include "a64instruction_DPImmediate.h" +#include "a64instruction_DPRegister.h" #include "a64instruction_Branch.h" #include "a64instruction_SingleTransfer.h" +#include "a64instruction_Label.h" +#include "a64instruction_Directive.h" // Define the types of instructions in subset of the AArch64 Instruction Set implemented. // Each type is defined by the format of the instruction's operand(s). @@ -12,7 +15,9 @@ typedef enum { a64inst_SINGLETRANSFER, a64inst_LOADLITERAL, a64inst_BRANCH, - a64inst_HALT + a64inst_HALT, + a64inst_LABEL, + a64inst_DIRECTIVE } a64inst_type; // Structure the holds the type and operand data of an instruction @@ -20,8 +25,11 @@ typedef struct { a64inst_type type; union { a64inst_DPImmediateData DPImmediateData; + a64inst_DPRegisterData DPRegisterData; a64inst_BranchData BranchData; a64inst_SingleTransferData SingleTransferData; + a64inst_LabelData LabelData; + a64inst_DirectiveData DirectiveData; } data; } a64inst_instruction; diff --git a/src/a64instruction_Branch.h b/src/a64instruction_Branch.h index 1681768..b732d6d 100644 --- a/src/a64instruction_Branch.h +++ b/src/a64instruction_Branch.h @@ -4,8 +4,8 @@ typedef enum { a64inst_UNCONDITIONAL = 0, - a64inst_REGISTER = 1, - a64inst_CONDITIONAL = 2 + a64inst_REGISTER = 3, + a64inst_CONDITIONAL = 1 } a64inst_BranchType; typedef struct { diff --git a/src/a64instruction_DP.h b/src/a64instruction_DP.h index 113f589..8ffb1f7 100644 --- a/src/a64instruction_DP.h +++ b/src/a64instruction_DP.h @@ -1,3 +1,6 @@ +#ifndef __A64INSTRUCTION_DP__ +#define __A64INSTRUCTION_DP__ + // Denotes the type of arithmetic operations supported by the architecture typedef enum { a64inst_ADD = 0, @@ -5,3 +8,5 @@ typedef enum { a64inst_SUB = 2, a64inst_SUBS = 3 } a64inst_arithmOp; + +#endif diff --git a/src/a64instruction_DPRegister.h b/src/a64instruction_DPRegister.h index d0252ee..ecf2f2c 100644 --- a/src/a64instruction_DPRegister.h +++ b/src/a64instruction_DPRegister.h @@ -4,8 +4,8 @@ // Denotes the type of data processing operation typedef enum { - a64inst_DPR_ARITHMLOGIC, - a64inst_DPR_MULTIPLY + a64inst_DPR_ARITHMLOGIC = 0, + a64inst_DPR_MULTIPLY = 1 } a64inst_DPROpType; // Denotes the logical operations supported by the architecture @@ -27,10 +27,11 @@ typedef enum { // Holds data specific to arithmetic/logic register data processing instructions typedef struct { enum { - a64inst_DPR_ARITHM = 0, - a64inst_DPR_LOGIC = 1 + a64inst_DPR_ARITHM = 1, + a64inst_DPR_LOGIC = 0 } type; a64inst_ShiftType shiftType; + uint8_t shiftAmount; bool negShiftedSrc2; // Guaranteed to be 0 for arithmetic instructions } a64inst_DPRegister_ArithmLogicData; @@ -44,10 +45,7 @@ typedef struct { typedef struct { a64inst_regType regType; a64inst_DPROpType DPROpType; - union { - a64inst_logicOp logicOp; - a64inst_arithmOp arithmOp; - } processOpId; + uint8_t processOp; a64inst_regSpecifier src2; union { a64inst_DPRegister_ArithmLogicData arithmLogicData; diff --git a/src/a64instruction_Directive.h b/src/a64instruction_Directive.h new file mode 100644 index 0000000..da36624 --- /dev/null +++ b/src/a64instruction_Directive.h @@ -0,0 +1,5 @@ +#include "global.h" + +typedef struct { + word value; +} a64inst_DirectiveData; diff --git a/src/a64instruction_Label.h b/src/a64instruction_Label.h new file mode 100644 index 0000000..b1d26e9 --- /dev/null +++ b/src/a64instruction_Label.h @@ -0,0 +1,3 @@ +typedef struct { + char* label; +} a64inst_LabelData; diff --git a/src/a64instruction_global.h b/src/a64instruction_global.h index 489fe06..ff748a6 100644 --- a/src/a64instruction_global.h +++ b/src/a64instruction_global.h @@ -8,7 +8,7 @@ typedef uint8_t a64inst_regSpecifier; // Denotes the type of register being referred to typedef enum { a64inst_W = 0, - a64inst_R = 1 + a64inst_X = 1 } a64inst_regType; #endif diff --git a/src/decode.c b/src/decode.c new file mode 100644 index 0000000..ce69c35 --- /dev/null +++ b/src/decode.c @@ -0,0 +1,169 @@ +#include +#include +#include +#include "decode.h" +#include "emulator.h" + +// Retrieve the bits between positions 'lsb' (inclusive) and 'msb' (exclusive) from a given word +// as a new zero-extended word. +static word getBits(word wrd, uint8_t lsb, uint8_t msb) { + + // Ensure LSB and MSB are within range of word size, and in the correct order + assert(lsb < msb && msb <= WORD_BITS); + + wrd &= ((dword) 1 << msb) - 1; + return wrd >> lsb; +} + +// Given a binary word, return its internal representation as an a64instruction struct encoding the same +// information. +a64inst_instruction *decode(word wrd) { + + a64inst_instruction *inst = malloc(sizeof(a64inst_instruction)); + if (inst == NULL) { + fprintf(stderr, "Ran out of memory while attempting to decode an instruction!\n"); + exit(1); + } + + word typeId = getBits(wrd, TYPE_ID_LSB, TYPE_ID_MSB); + // Halt interpretation + if (wrd == HALT_WORD) { + inst->type = a64inst_HALT; + + // Data Processing Immediate interpretation + } else if (typeId == DP_IMM_ID) { + inst->type = a64inst_DPIMMEDIATE; + inst->data.DPImmediateData.regType = getBits(wrd, DP_WIDTH_LSB, DP_WIDTH_MSB); + inst->data.DPImmediateData.processOp = getBits(wrd, DP_OP_LSB, DP_OP_MSB); + inst->data.DPImmediateData.dest = getBits(wrd, DP_DEST_LSB, DP_DEST_MSB); + + switch(getBits(wrd, DP_IMM_OPTYPE_LSB, DP_IMM_OPTYPE_MSB)) { + + case DP_IMM_OPTYPE_ARITHM: + inst->data.DPImmediateData.DPIOpType = a64inst_DPI_ARITHM; + inst->data.DPImmediateData.processOpData.arithmData.shiftImmediate = getBits(wrd, DP_IMM_ARITHM_SHIFTFLAG_LSB, DP_IMM_ARITHM_SHIFTFLAG_MSB); + inst->data.DPImmediateData.processOpData.arithmData.immediate = getBits(wrd, DP_IMM_ARITHM_IMMVAL_LSB, DP_IMM_ARITHM_IMMVAL_MSB); + inst->data.DPImmediateData.processOpData.arithmData.src = getBits(wrd, DP_IMM_ARITHM_DEST_LSB, DP_IMM_ARITHM_DEST_MSB); + break; + + case DP_IMM_OPTYPE_WIDEMOV: + inst->data.DPImmediateData.DPIOpType = a64inst_DPI_WIDEMOV; + inst->data.DPImmediateData.processOpData.wideMovData.shiftScalar = getBits(wrd, DP_IMM_WIDEMOV_SHIFTSCALAR_LSB, DP_IMM_WIDEMOV_SHIFTSCALAR_MSB); + inst->data.DPImmediateData.processOpData.wideMovData.immediate = getBits(wrd, DP_IMM_WIDEMOV_IMMVAL_LSB, DP_IMM_WIDEMOV_IMMVAL_MSB); + break; + + default: + fprintf(stderr, "Unknown immediate data processing operation type found!\n"); + exit(1); + break; + } + + } else if (typeId == BRANCH_ID) { + inst->type = a64inst_BRANCH; + word branchTypeFlag = getBits(wrd, BRANCH_TYPE_LSB, BRANCH_TYPE_MSB); + + inst->data.BranchData.BranchType = branchTypeFlag; + + switch (branchTypeFlag) { + case a64inst_UNCONDITIONAL: + inst->data.BranchData.processOpData.unconditionalData.unconditionalOffset = getBits(wrd, BRANCH_UNCONDITIONAL_OFFSET_LSB, BRANCH_UNCONDITIONAL_OFFSET_MSB); + break; + + case a64inst_CONDITIONAL: + inst->data.BranchData.processOpData.conditionalData.offset = getBits(wrd, BRANCH_CONDITIONAL_OFFSET_LSB, BRANCH_CONDITIONAL_OFFSET_MSB); + + word conditionFlag = getBits(wrd, BRANCH_CONDITIONAL_COND_LSB, BRANCH_CONDITIONAL_COND_MSB); + + if(conditionFlag <= 1 || (conditionFlag >= 10 && conditionFlag <= 14)) { + inst->data.BranchData.processOpData.conditionalData.cond = conditionFlag; + } else { + fprintf(stderr, "Unknown condition detected!\n"); + exit(1); + } + + break; + + case a64inst_REGISTER: + inst->data.BranchData.processOpData.registerData.src = getBits(wrd, BRANCH_REGISTER_SRC_LSB, BRANCH_REGISTER_SRC_MSB); + break; + + default: + fprintf(stderr, "Undefined branch type detected!\n"); + exit(1); + break; + } + + // TODO: Some minor code duplication between DPR and DPI data interpretation + // Data Processing Register interpretation + } else if (getBits(wrd, DP_REG_LSB, DP_REG_MSB) == 1) { + inst->type = a64inst_DPREGISTER; + inst->data.DPRegisterData.regType = getBits(wrd, DP_WIDTH_LSB, DP_WIDTH_MSB); + inst->data.DPRegisterData.processOp = getBits(wrd, DP_OP_LSB, DP_OP_MSB); + inst->data.DPRegisterData.dest = getBits(wrd, DP_DEST_LSB, DP_DEST_MSB); + inst->data.DPRegisterData.src1 = getBits(wrd, DP_REG_SRC1_LSB, DP_REG_SRC1_MSB); + inst->data.DPRegisterData.src2 = getBits(wrd, DP_REG_SRC2_LSB, DP_REG_SRC2_MSB); + inst->data.DPRegisterData.DPROpType = getBits(wrd, DP_REG_OPTYPE_LSB, DP_REG_OPTYPE_MSB); + + a64inst_DPRegister_ArithmLogicData *arithmLogicData = &inst->data.DPRegisterData.processOpData.arithmLogicData; + + arithmLogicData->type = getBits(wrd, DP_REG_ARITHMLOGIC_ARITHMFLAG_LSB, DP_REG_ARITHMLOGIC_ARITHMFLAG_MSB); + arithmLogicData->shiftType = getBits(wrd, DP_REG_ARITHMLOGIC_SHIFTTYPE_LSB, DP_REG_ARITHMLOGIC_SHIFTTYPE_MSB); + arithmLogicData->negShiftedSrc2 = getBits(wrd, DP_REG_ARITHMLOGIC_NEGSRC2FLAG_LSB, DP_REG_ARITHMLOGIC_NEGSRC2FLAG_MSB); + + switch(inst->data.DPRegisterData.DPROpType) { + + case a64inst_DPR_ARITHMLOGIC: + if (arithmLogicData->type == a64inst_DPR_ARITHM && (arithmLogicData->negShiftedSrc2 || arithmLogicData->shiftType == a64inst_ROR)) { + fprintf(stderr, "Attempting to decode arithmetic DPR instruction with invalid format!\n"); + } + arithmLogicData->shiftAmount = getBits(wrd, DP_REG_ARITHMLOGIC_SHIFTAMOUNT_LSB, DP_REG_ARITHMLOGIC_SHIFTAMOUNT_MSB); + break; + + case a64inst_DPR_MULTIPLY:; + if (!(inst->data.DPRegisterData.processOp == DP_REG_MULTIPLY_PROCESSOP && + arithmLogicData->type == DP_REG_MULTIPLY_ARITHMFLAG && + arithmLogicData->shiftType == DP_REG_MULTIPLY_SHIFTTYPE && + arithmLogicData->negShiftedSrc2 == DP_REG_MULTIPLY_NEGSRC2FLAG)) { + fprintf(stderr, "Attempting to decode multiply DPR instruction with invalid format!\n"); + } + inst->data.DPRegisterData.processOpData.multiplydata.summand = getBits(wrd, DP_REG_MULTIPLY_SUMMAND_LSB, DP_REG_MULTIPLY_SUMMAND_MSB); + inst->data.DPRegisterData.processOpData.multiplydata.negProd = getBits(wrd, DP_REG_MULTIPLY_NEGPROD_LSB, DP_REG_MULTIPLY_NEGPROD_MSB); + break; + } + + } else { + // Load and Store, or unknown + // Ignore unknown for now + inst->type = a64inst_SINGLETRANSFER; + inst->data.SingleTransferData.regType = getBits(wrd, SDT_REGTYPE_FLAG_LSB, SDT_REGTYPE_FLAG_MSB); + inst->data.SingleTransferData.target = getBits(wrd, SDT_TARGET_REG_LSB, SDT_TARGET_REG_MSB); + + // TODO: Assert that the instruction is a Single Transfer indeed. + + if(getBits(wrd, SDT_OPTYPE_FLAG_LSB, SDT_OPTYPE_FLAG_MSB) == a64inst_SINGLE_TRANSFER_SINGLE_DATA_TRANSFER) { + // Single Data Transfer + inst->data.SingleTransferData.SingleTransferOpType = a64inst_SINGLE_TRANSFER_SINGLE_DATA_TRANSFER; + inst->data.SingleTransferData.processOpData.singleDataTransferData.transferType = getBits(wrd, SDT_TRANSFER_TYPE_LSB, SDT_TRANSFER_TYPE_MSB); + inst->data.SingleTransferData.processOpData.singleDataTransferData.base = getBits(wrd, SDT_BASE_REG_LSB, SDT_BASE_REG_MSB); + if (getBits(wrd, SDT_UNSIGNED_FLAG_LSB, SDT_UNSIGNED_FLAG_MSB) == 1) { + // Unsigned offset + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = a64inst_UNSIGNED_OFFSET; + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.unsignedOffset = getBits(wrd, SDT_OFFSET_LSB, SDT_OFFSET_MSB); + } else if (getBits(wrd, SDT_REGISTER_FLAG_LSB, SDT_REGISTER_FLAG_MSB) == 1) { + // Register Offset + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = a64inst_REGISTER_OFFSET; + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.offsetReg = getBits(wrd, SDT_REGISTER_REG_LSB, SDT_REGISTER_REG_MSB); + } else { + // Pre-Indexed or Post-Indexed + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = getBits(wrd, SDT_INDEXED_ADDRMODE_LSB, SDT_INDEXED_ADDRMODE_MSB); + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset = getBits(wrd, SDT_INDEXED_OFFSET_LSB, SDT_INDEXED_OFFSET_MSB); + } + } else { + // Load Literal + inst->data.SingleTransferData.SingleTransferOpType = a64inst_SINGLE_TRANSFER_LOAD_LITERAL; + inst->data.SingleTransferData.processOpData.loadLiteralData.offset = getBits(wrd, SDT_LOAD_LITERAL_OFFSET_LSB, SDT_LOAD_LITERAL_OFFSET_MSB); + } + } + + return inst; +} diff --git a/src/decode.h b/src/decode.h new file mode 100644 index 0000000..132130e --- /dev/null +++ b/src/decode.h @@ -0,0 +1,96 @@ +#include "global.h" +#include "a64instruction.h" + +#define HALT_WORD 0x8a000000 + +#define TYPE_ID_LSB 26 +#define TYPE_ID_MSB 29 +#define DP_IMM_ID 4 +#define BRANCH_ID 5 + +#define DP_REG_LSB 25 +#define DP_REG_MSB 26 + +#define DP_WIDTH_LSB 31 +#define DP_WIDTH_MSB 32 +#define DP_OP_LSB 29 +#define DP_OP_MSB 31 +#define DP_DEST_LSB 0 +#define DP_DEST_MSB 5 + +#define DP_IMM_OPTYPE_LSB 23 +#define DP_IMM_OPTYPE_MSB 26 +#define DP_IMM_OPTYPE_ARITHM 2 +#define DP_IMM_OPTYPE_WIDEMOV 5 +#define DP_IMM_ARITHM_SHIFTFLAG_LSB 22 +#define DP_IMM_ARITHM_SHIFTFLAG_MSB 23 +#define DP_IMM_ARITHM_IMMVAL_LSB 10 +#define DP_IMM_ARITHM_IMMVAL_MSB 22 +#define DP_IMM_ARITHM_DEST_LSB 5 +#define DP_IMM_ARITHM_DEST_MSB 10 +#define DP_IMM_WIDEMOV_SHIFTSCALAR_LSB 21 +#define DP_IMM_WIDEMOV_SHIFTSCALAR_MSB 23 +#define DP_IMM_WIDEMOV_IMMVAL_LSB 5 +#define DP_IMM_WIDEMOV_IMMVAL_MSB 21 + +#define DP_REG_SRC1_LSB 5 +#define DP_REG_SRC1_MSB 10 +#define DP_REG_SRC2_LSB 16 +#define DP_REG_SRC2_MSB 21 +#define DP_REG_OPTYPE_LSB 28 +#define DP_REG_OPTYPE_MSB 29 +#define DP_REG_ARITHMLOGIC_ARITHMFLAG_LSB 24 +#define DP_REG_ARITHMLOGIC_ARITHMFLAG_MSB 25 +#define DP_REG_ARITHMLOGIC_SHIFTTYPE_LSB 22 +#define DP_REG_ARITHMLOGIC_SHIFTTYPE_MSB 24 +#define DP_REG_ARITHMLOGIC_NEGSRC2FLAG_LSB 21 +#define DP_REG_ARITHMLOGIC_NEGSRC2FLAG_MSB 22 +#define DP_REG_ARITHMLOGIC_SHIFTAMOUNT_LSB 10 +#define DP_REG_ARITHMLOGIC_SHIFTAMOUNT_MSB 16 +#define DP_REG_MULTIPLY_SUMMAND_LSB 10 +#define DP_REG_MULTIPLY_SUMMAND_MSB 15 +#define DP_REG_MULTIPLY_NEGPROD_LSB 15 +#define DP_REG_MULTIPLY_NEGPROD_MSB 16 +// Defines the values for fields used for arithmetic/logic DPR instructions +// that are necessary to indicate a multiplication instruction +#define DP_REG_MULTIPLY_PROCESSOP 0 +#define DP_REG_MULTIPLY_ARITHMFLAG 1 +#define DP_REG_MULTIPLY_SHIFTTYPE 0 +#define DP_REG_MULTIPLY_NEGSRC2FLAG 0 + +#define SDT_OPTYPE_FLAG_LSB 31 +#define SDT_OPTYPE_FLAG_MSB 32 +#define SDT_REGTYPE_FLAG_LSB 30 +#define SDT_REGTYPE_FLAG_MSB 31 +#define SDT_TARGET_REG_LSB 0 +#define SDT_TARGET_REG_MSB 5 + +#define SDT_BASE_REG_LSB 5 +#define SDT_BASE_REG_MSB 10 +#define SDT_OFFSET_LSB 10 +#define SDT_OFFSET_MSB 22 +#define SDT_TRANSFER_TYPE_LSB 22 +#define SDT_TRANSFER_TYPE_MSB 23 +#define SDT_UNSIGNED_FLAG_LSB 24 +#define SDT_UNSIGNED_FLAG_MSB 25 +#define SDT_REGISTER_FLAG_LSB 21 +#define SDT_REGISTER_FLAG_MSB 22 +#define SDT_REGISTER_REG_LSB 16 +#define SDT_REGISTER_REG_MSB 21 +#define SDT_INDEXED_ADDRMODE_LSB 11 +#define SDT_INDEXED_ADDRMODE_MSB 12 +#define SDT_INDEXED_OFFSET_LSB 12 +#define SDT_INDEXED_OFFSET_MSB 21 +#define SDT_LOAD_LITERAL_OFFSET_LSB 5 +#define SDT_LOAD_LITERAL_OFFSET_MSB 24 + +#define BRANCH_TYPE_LSB 30 +#define BRANCH_TYPE_MSB 32 +#define BRANCH_UNCONDITIONAL_OFFSET_LSB 0 +#define BRANCH_UNCONDITIONAL_OFFSET_MSB 26 +#define BRANCH_REGISTER_SRC_LSB 5 +#define BRANCH_REGISTER_SRC_MSB 10 +#define BRANCH_CONDITIONAL_COND_LSB 0 +#define BRANCH_CONDITIONAL_COND_MSB 4 +#define BRANCH_CONDITIONAL_OFFSET_LSB 5 +#define BRANCH_CONDITIONAL_OFFSET_MSB 24 diff --git a/src/emulate.c b/src/emulate.c index e2ad1c8..82245e9 100755 --- a/src/emulate.c +++ b/src/emulate.c @@ -1,5 +1,61 @@ #include +#include +#include "a64instruction.h" +#include "emulator.h" +#include "fileio.h" +#include "global.h" +#include "print.h" +#include "decode.h" +#include "execute.h" + +extern a64inst_instruction *decode(word w); int main(int argc, char **argv) { + + // Check the arguments + if (argc == 1) { + fprintf(stderr, "Error: An object file is required. Syntax: ./emulate []"); + return EXIT_FAILURE; + } + + FILE *out = stdout; + if (argc > 2) { + out = fopen(argv[2], "w"); + if (out == NULL) { + fprintf(stderr, "Error: Could not open file %s\n", argv[2]); + return EXIT_FAILURE; + } + } + + // Initialising the machine state + Machine state = {0}; + state.memory = fileio_loadBin(argv[1], MEMORY_SIZE); + state.conditionCodes = (PState){0, 1, 0, 0}; + state.pc = 0x0; + + + // Fetch-decode-execute cycle + word wrd; + a64inst_instruction *inst; + do { + + // Step 1: Fetch instruction at PC's address + wrd = readWord(state.memory, state.pc); + + // Step 2: Decode instruction to internal representation + inst = decode(wrd); + + // Step 3: Update processor state to reflect executing the instruction, and increment PC + execute(&state, inst); + + if (inst->type != a64inst_BRANCH) + state.pc += sizeof(word); + } while (inst->type != a64inst_HALT); + + state.pc -= sizeof(word); + + printState(&state, out); + free(state.memory); + return EXIT_SUCCESS; } diff --git a/src/emulator.h b/src/emulator.h new file mode 100644 index 0000000..facbfd7 --- /dev/null +++ b/src/emulator.h @@ -0,0 +1,32 @@ +#ifndef __EMULATOR__ +#define __EMULATOR__ +#include "global.h" +#include + +/************************************ + * DEFINITIONS + ************************************/ + +#define BYTE_BITS 8 +#define WORD_BITS (BYTE_BITS * sizeof(word)) +#define DWORD_BITS (BYTE_BITS * sizeof(dword)) + +/************************************ + * STRUCTS + ************************************/ + +typedef struct { + bool Negative; + bool Zero; + bool Carry; + bool Overflow; +} PState; + +typedef struct { + dword registers[REGISTER_COUNT]; + dword pc; + byte *memory; + PState conditionCodes; +} Machine; + +#endif diff --git a/src/execute.c b/src/execute.c new file mode 100644 index 0000000..fc436af --- /dev/null +++ b/src/execute.c @@ -0,0 +1,448 @@ +#include +#include +#include "execute.h" +#include "print.h" + +// Defines the maximum value that can be held in a register +#define MAX_REG_VAL ((1 << DWORD_BITS) - 1) + +// The number of bits to shift the immediate value in an arithmetic immediate data processing +// instruction if the shift flag is enabled. +#define DPI_ARITHM_SHIFT 12 + +// The number of bits to shift the immediate value in a wide move immediate data processing +// instruction if the shift flag is enabled. +#define DPI_WIDEMOV_SHIFT 16 + +// Prototypes +void execute_SDT(Machine *state, a64inst_instruction *inst); +void execute_Branch(Machine *state, a64inst_instruction *inst); +void executeMultiply(Machine *state, a64inst_instruction *inst); + +// Return maximum of two dwords +static dword max(dword a, dword b) { + return a > b ? a : b; +} + +// Truncate a given value to the size of a word or dword depending on the register type +static dword truncateValue(dword value, a64inst_regType regType) { + if (regType == a64inst_X) { + return value; + } else { + return (word)value; + //return value & (dword)(((dword)1 << WORD_BITS) - 1); + } +} + +// Sign extend a given value to a 64-bit signed integer given the number of bits +static int64_t signExtend(dword value, unsigned int n) { + if (n == 0 || n >= 64) { + // If n_bits is 0 or greater than or equal to 64, return the value as is + return (int64_t)value; + } + + uint64_t sign_bit_mask = (uint64_t)1 << (n - 1); + + // Mask to isolate the n-bit value + uint64_t n_bit_mask = (sign_bit_mask << 1) - 1; + + // Check if the sign bit is set + if (value & sign_bit_mask) { + // Sign bit is set, extend the sign + return (int64_t)(value | ~n_bit_mask); + } else { + // Sign bit is not set, return the value as is + return (int64_t)(value & n_bit_mask); + } +} + +// Read from processor register, ensuring that a valid register specifier is given +// and accounting for the case where the zero register is accessed. Truncate +// the 32 most significant bits stored in the R register when reading W register. +static dword readRegister(Machine *state, a64inst_regSpecifier reg, a64inst_regType regType) { + assert(reg <= REGISTER_COUNT); + if (reg == ZERO_REGISTER) { + return 0; + } else { + return truncateValue(state->registers[reg], regType); + } +} + +// TODO: + +// Write to a processor register, ensuring that a valid register specifier is given +// and truncating the value being written when it can't fit in the specified register +static void writeRegister(Machine *state, a64inst_regSpecifier reg, a64inst_regType regType, dword value) { + assert(reg <= REGISTER_COUNT); + if (reg != ZERO_REGISTER) { + state->registers[reg] = truncateValue(value, regType); + } +} + +// Returns the position of the MSB of the given register type +inline static dword getMSBPos(a64inst_regType regType) { + return (regType ? DWORD_BITS : WORD_BITS) - 1; +} + +// Returns the MSB of the given value assuming it's of the size stored in the given register type +inline static uint8_t getMSB(dword value, a64inst_regType regType) { + return value >> getMSBPos(regType); +} + +// Updates N and Z condition codes given the machine and a result value +static void updateCondNZ(Machine *state, dword result, a64inst_regType regType) { + state->conditionCodes.Negative = getMSB(result, regType); + state->conditionCodes.Zero = result == 0; +} + +// Execute a data processing immediate instruction +static void executeDPImmediate(Machine *state, a64inst_instruction *inst) { + assert(inst->type == a64inst_DPIMMEDIATE); + + a64inst_regType regType = inst->data.DPImmediateData.regType; + a64inst_regSpecifier dest = inst->data.DPImmediateData.dest; + switch(inst->data.DPImmediateData.DPIOpType) { + + // Execute an arithmetic immediate data processing instruction + case a64inst_DPI_ARITHM:; + + // If shift flag is enabled, logical left shift by the number of bits specified by the architecture + dword arithmImm = inst->data.DPImmediateData.processOpData.arithmData.immediate; + dword srcVal = state->registers[inst->data.DPImmediateData.processOpData.arithmData.src]; + if (inst->data.DPImmediateData.processOpData.arithmData.shiftImmediate) { + arithmImm = truncateValue(arithmImm << DPI_ARITHM_SHIFT, regType); + } + + switch(inst->data.DPImmediateData.processOp) { + + dword result; + case(a64inst_ADDS): + result = srcVal + arithmImm; + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = max(srcVal, arithmImm) > result; + state->conditionCodes.Carry = state->conditionCodes.Overflow; + break; + + case(a64inst_ADD): + writeRegister(state, dest, regType, srcVal + arithmImm); + break; + + case(a64inst_SUBS): + result = srcVal - arithmImm; + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = srcVal < result; + state->conditionCodes.Carry = state->conditionCodes.Overflow; + break; + + case(a64inst_SUB): + writeRegister(state, dest, regType, srcVal - arithmImm); + break; + + // Unknown opcode detected! + default: + fprintf(stderr, "Unknown opcode detected in a DPI arithmetic instruction!\n"); + break; + } + break; + + // Execute a wide move immediate data processing instruction + case a64inst_DPI_WIDEMOV:; + uint8_t shiftScalar = inst->data.DPImmediateData.processOpData.wideMovData.shiftScalar; + dword wideMovImm = inst->data.DPImmediateData.processOpData.wideMovData.immediate; + + // NOTE: Not checking that shiftScalar has valid value for 32bit registers. Possibly add explicit error. + //printf("%x\n", wideMovImm << (shiftScalar * DPI_WIDEMOV_SHIFT) & ); + wideMovImm = truncateValue(wideMovImm << (shiftScalar * DPI_WIDEMOV_SHIFT), regType); + switch(inst->data.DPImmediateData.processOp) { + + case(a64inst_MOVN): + writeRegister(state, dest, regType, ~wideMovImm); + break; + + case(a64inst_MOVZ): + writeRegister(state, dest, regType, wideMovImm); + break; + + case(a64inst_MOVK):; + dword result = readRegister(state, dest, regType); + result = (result & ~(((1lu << DPI_WIDEMOV_SHIFT) - 1) << shiftScalar * DPI_WIDEMOV_SHIFT)) | wideMovImm; + writeRegister(state, dest, regType, result); + break; + + default: + fprintf(stderr, "Unknown opcode detected in a DPI wide move instruction!\n"); + break; + } + break; + + // Unknown instruction detected! + default: + fprintf(stderr, "Attempting to execute instruction with unknown DPI operand type!\n"); + break; + } +} + +// Execute a data processing register instruction +static void executeDPRegister(Machine *state, a64inst_instruction *inst) { + assert(inst->type == a64inst_DPREGISTER); + + a64inst_regType regType = inst->data.DPRegisterData.regType; + a64inst_regSpecifier dest = inst->data.DPRegisterData.dest; + dword src1Val = readRegister(state, inst->data.DPRegisterData.src1, regType); + dword src2Val = readRegister(state, inst->data.DPRegisterData.src2, regType); + + switch(inst->data.DPRegisterData.DPROpType) { + + // Execute an arithmetic or logic register data processing instruction + case a64inst_DPR_ARITHMLOGIC:; + + // Apply shift to value held in second register + a64inst_DPRegister_ArithmLogicData *arithmLogicData = &inst->data.DPRegisterData.processOpData.arithmLogicData; + uint8_t shiftAmount = arithmLogicData->shiftAmount; + switch(arithmLogicData->shiftType) { + + case a64inst_LSL: + src2Val = truncateValue(src2Val << shiftAmount, regType); + break; + + case a64inst_LSR: + src2Val = truncateValue(src2Val >> shiftAmount, regType); + break; + + case a64inst_ASR: + if (regType == a64inst_X) { + src2Val = truncateValue((int64_t)src2Val >> shiftAmount, regType); + } else { + src2Val = truncateValue((int32_t)src2Val >> shiftAmount, regType); + } + break; + + case a64inst_ROR: + if (arithmLogicData->type != a64inst_DPR_LOGIC) { + fprintf(stderr, "Attempting to perform ROR shift on non-logic register data processing instruction!\n"); + } + src2Val = truncateValue(src2Val >> shiftAmount | src2Val << (getMSBPos(regType) - shiftAmount), regType); + break; + + default: + fprintf(stderr, "Attempting to execute arithmetic/logic register data processing instruction with invalid shift type!\n"); + break; + } + + // Negate second operand if negShiftedSrc2 flag is enabled + if (arithmLogicData->negShiftedSrc2) { + src2Val = truncateValue(~src2Val, regType); + } + + dword result; + switch(arithmLogicData->type) { + + case a64inst_DPR_ARITHM: + switch(inst->data.DPRegisterData.processOp) { + + case(a64inst_ADDS): + result = src1Val + src2Val; + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = max(src1Val, src2Val) > result; + state->conditionCodes.Carry = state->conditionCodes.Overflow; + break; + + case(a64inst_ADD): + writeRegister(state, dest, regType, src1Val + src2Val); + break; + + case(a64inst_SUBS): + result = src1Val - src2Val; + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = getMSB(src1Val, regType) != getMSB(src2Val, regType) && getMSB(src1Val, regType) != getMSB(result, regType); + state->conditionCodes.Carry = src1Val >= src2Val; + break; + + case(a64inst_SUB): + writeRegister(state, dest, regType, src1Val - src2Val); + break; + + // Unknown opcode detected! + default: + fprintf(stderr, "Unknown opcode detected in a DPI arithmetic instruction!\n"); + break; + } + break; + + case a64inst_DPR_LOGIC: + switch(inst->data.DPRegisterData.processOp) { + + case a64inst_AND: + writeRegister(state, dest, regType, src1Val & src2Val); + break; + + case a64inst_OR: + writeRegister(state, dest, regType, src1Val | src2Val); + break; + + case a64inst_XOR: + writeRegister(state, dest, regType, src1Val ^ src2Val); + break; + + case a64inst_AND_FLAGGED:; + result = src1Val & src2Val; + writeRegister(state, dest, regType, result); + state->conditionCodes.Overflow = 0; + state->conditionCodes.Carry = 0; + updateCondNZ(state, result, regType); + break; + } + break; + + default: + fprintf(stderr, "Attempting to execute an instruction with an unknown DPR arithmetic or logic subtype!\n"); + break; + } + break; + + // Execute a multiply register data processing instruction + case a64inst_DPR_MULTIPLY: + break; + + // Unknown instruction detected! + default: + fprintf(stderr, "Attempting to execute instruction with unknown DPR operand type!\n"); + break; + } +} + +void execute(Machine *state, a64inst_instruction *inst) { + + switch (inst->type) { + + // Halt the program + case a64inst_HALT: + break; + + // Execute a data processing immediate instruction + case a64inst_DPIMMEDIATE: + executeDPImmediate(state, inst); + break; + + // Execute a branch instruction + case a64inst_BRANCH: + execute_Branch(state, inst); + break; + + // Execute a data processing register instruction + case a64inst_DPREGISTER: + if (inst->data.DPRegisterData.DPROpType == a64inst_DPR_MULTIPLY) + executeMultiply(state, inst); + else + executeDPRegister(state, inst); + break; + + case a64inst_SINGLETRANSFER: + execute_SDT(state, inst); + break; + + // Unknown instruction + default: + break; + } + +} + +void execute_SDT(Machine *state, a64inst_instruction *inst) { + word address; + bool isLoad; + if (inst->data.SingleTransferData.SingleTransferOpType == a64inst_SINGLE_TRANSFER_LOAD_LITERAL) { + // Load Literal + isLoad = true; + address = state->pc + inst->data.SingleTransferData.processOpData.loadLiteralData.offset * 4; + } else { + address = state->registers[inst->data.SingleTransferData.processOpData.singleDataTransferData.base]; + isLoad = inst->data.SingleTransferData.processOpData.singleDataTransferData.transferType == a64inst_LOAD; + switch (inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode) { + case a64inst_UNSIGNED_OFFSET: + address += inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.unsignedOffset * (inst->data.SingleTransferData.regType == a64inst_W ? 4 : 8); + break; + case a64inst_REGISTER_OFFSET: + address += state->registers[inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.offsetReg]; + break; + case a64inst_PRE_INDEXED: + address += inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset; + state->registers[inst->data.SingleTransferData.processOpData.singleDataTransferData.base] = address; + break; + case a64inst_POST_INDEXED: + state->registers[inst->data.SingleTransferData.processOpData.singleDataTransferData.base] = address + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset; + break; + } + } + + if (isLoad) { + if (inst->data.SingleTransferData.regType == a64inst_W) { + // 32 bit access + state->registers[inst->data.SingleTransferData.target] = readWord(state->memory, address); + } else { + state->registers[inst->data.SingleTransferData.target] = readDoubleWord(state->memory, address); + } + } else { + *(word *)(state->memory + address) = state->registers[inst->data.SingleTransferData.target]; + + // Update base register if post indexed + if (inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode == a64inst_POST_INDEXED) { + writeRegister(state, inst->data.SingleTransferData.processOpData.singleDataTransferData.base, inst->data.SingleTransferData.regType == a64inst_W, address + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset); + } + } + +} + +static bool isConditionMet(Machine* state, a64inst_ConditionType cond) { + switch(cond) { + case EQ: + return state->conditionCodes.Zero; + case NE: + return !state->conditionCodes.Zero; + case GE: + return state->conditionCodes.Negative == state->conditionCodes.Overflow; + case LT: + return state->conditionCodes.Negative != state->conditionCodes.Overflow; + case GT: + return !state->conditionCodes.Zero && (state->conditionCodes.Negative == state->conditionCodes.Overflow); + case LE: + return state->conditionCodes.Zero || (state->conditionCodes.Negative != state->conditionCodes.Overflow); + case AL: + return true; + default: + fprintf(stderr, "Unknown condition specified!\n"); + exit(1); + } +} + +void execute_Branch(Machine *state, a64inst_instruction *inst) { + switch (inst->data.BranchData.BranchType) { + case a64inst_UNCONDITIONAL: + state->pc += signExtend(inst->data.BranchData.processOpData.unconditionalData.unconditionalOffset * 4, 26); + break; + + case a64inst_REGISTER: + state->pc = state->registers[inst->data.BranchData.processOpData.registerData.src]; + break; + + case a64inst_CONDITIONAL: + if (isConditionMet(state, inst->data.BranchData.processOpData.conditionalData.cond)) { + state->pc += signExtend(inst->data.BranchData.processOpData.conditionalData.offset * 4, 19); + } + break; + } +} + +void executeMultiply(Machine *state, a64inst_instruction *inst) { + dword product = state->registers[inst->data.DPRegisterData.src1] * state->registers[inst->data.DPRegisterData.src2]; + dword sum = readRegister(state, inst->data.DPRegisterData.processOpData.multiplydata.summand, inst->data.DPRegisterData.regType) + (inst->data.DPRegisterData.processOpData.multiplydata.negProd ? -product : product); + writeRegister(state, inst->data.DPRegisterData.dest, inst->data.DPRegisterData.regType, sum); +} diff --git a/src/execute.h b/src/execute.h new file mode 100644 index 0000000..fcf39ec --- /dev/null +++ b/src/execute.h @@ -0,0 +1,7 @@ +#ifndef __EXECUTE__ +#define __EXECUTE__ +#include "a64instruction.h" +#include "emulator.h" + +void execute(Machine *state, a64inst_instruction *inst); +#endif diff --git a/src/fileio.c b/src/fileio.c index 45b25e8..1dcdd77 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -1,62 +1,48 @@ +#include #include #include +#include "fileio.h" +#include "global.h" -//validates inputted charlist as valid filename against expected extension -int isValidFileFormat(char filename[], char expectedExtension[]){ - int *pointLoc = strrchr(filename, '.'); +/* Loads a binary file located at filePath to memory, taking up a block of exactly memorySize bytes, + and returns the starting address of the data. If memorySize is insufficient to store the entire file, + an appropriate error is reported. Excess memory is set to 0 bit values. */ - if(pointLoc != NULL){ - if(strcmp(pointLoc, expectedExtension)==0){ - return(1); +byte *fileio_loadBin(const char *filePath, size_t memorySize) { + FILE *file = fopen(filePath, "rb"); + if (file == NULL) { + fprintf(stderr, "Couldn't open %s!\n", filePath); + exit(EXIT_FAILURE); + } + + byte *fileData = malloc(memorySize); + if (fileData == NULL) { + fprintf(stderr, "Ran out of memory attempting to load %s!\n", filePath); + exit(EXIT_FAILURE); + } + + // Loop while reading from the file yields data. Only terminates if EOF is reached or ERROR occurs. + // Explicitly deal with attempting to write too much data to memory block, rather than allow segfault. + const size_t byteCount = memorySize/sizeof(byte); + int i = 0; + while (fread(fileData + i, sizeof(byte), 1, file)) { + if (i >= byteCount) { + fprintf(stderr, "Attempting to load binary %s to memory of smaller size %zu!\n", filePath, memorySize); + exit(EXIT_FAILURE); } + + i++; } - return(0); + + if (ferror(file)) { + fprintf(stderr, "Encountered error attempting to read %s!\n", filePath); + exit(EXIT_FAILURE); + } + assert(fclose(file) != EOF); + + // If part of memory block was left uninitialized, initialize it to zero. + if (i < byteCount) { + memset(fileData + i, 0, (byteCount - i) * sizeof(byte)); + } + return fileData; } - -//writes a list of words (list of binary instructions) to a named output file -int writeBinaryFile(word instrs[], char outputFile[]){ - - if (!isValidFileFormat(filename, "bin")){ - exit(EXIT_FAILURE); - } - - FILE *fp; - - fp = fopen(outputFile, "wb"); - - if(fp == NULL){ - exit(EXIT_FAILURE); - } - - fwrite(instrs, sizeof(word), sizeof(instrs), fp); - fclose(fp); - - exit(EXIT_SUCCESS); -} - - -//reads assembly file of "inputFile" name, and passes -//each line into a parser -//TODO: allocate whole file in memory, line-by-line -int readAssemblyFile(char inputFile[]) { - if (!isValidFileFormat(filename, "s")){ - exit(EXIT_FAILURE); - } - - FILE *fp; - char savedLine[sizeof(a64inst_instruction)]; - - fp = fopen(inputFile, "r"); - - if(fp == NULL){ - exit(EXIT_FAILURE); - } - - while (fgets(savedLine, MAX_ASM_LINE_LENGTH-1, fp) != NULL) { - // removes newline char before saving them - savedLine[strcspn(savedLine, "\n")] = 0; - - } - - exit(EXIT_SUCCESS); -} \ No newline at end of file diff --git a/src/fileio.h b/src/fileio.h new file mode 100644 index 0000000..a2d4262 --- /dev/null +++ b/src/fileio.h @@ -0,0 +1,9 @@ +#ifndef __FILEIO__ +#define __FILEIO__ +#include +#include "global.h" + +#define EXIT_FAILURE 1 + +extern byte *fileio_loadBin(const char *filePath, size_t memorySize); +#endif diff --git a/src/global.h b/src/global.h index 138e11a..1cf226e 100644 --- a/src/global.h +++ b/src/global.h @@ -11,6 +11,8 @@ // Number of General Purpose Registers. #define REGISTER_COUNT 31 +// Register identifier interpreted as the 'zero register' +#define ZERO_REGISTER 31 // Size of the memory in bytes. #define MEMORY_SIZE 2097152 // Length of the memory address in bits. @@ -25,4 +27,4 @@ typedef uint32_t word; // Double word is a 64-bit unsigned integer. typedef uint64_t dword; -#endif \ No newline at end of file +#endif diff --git a/src/print.c b/src/print.c new file mode 100644 index 0000000..1d1d4a2 --- /dev/null +++ b/src/print.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include "print.h" +#include "emulator.h" + +#define UNSET_CONDITION_CODE_CHAR '-' + +// Prints the current machine state into the provided stream +void printState(Machine *state, FILE *stream) { + printRegisters(state, stream); + printMemory(state, stream); +} + +// Prints the current machine registers into the provided stream +void printRegisters(Machine *state, FILE *stream) { + fprintf(stream, "Registers:\n"); + for (int i = 0; i < REGISTER_COUNT; i++) { + fprintf(stream, "X%02d\t= %016" PRIx64 "\n", i, state->registers[i]); + } + fprintf(stream, "PC\t= %016" PRIx64 "\n", state->pc); + fprintf(stream, "PSTATE\t: %c%c%c%c", state->conditionCodes.Negative ? 'N' : UNSET_CONDITION_CODE_CHAR, + state->conditionCodes.Zero ? 'Z' : UNSET_CONDITION_CODE_CHAR, + state->conditionCodes.Carry ? 'C' : UNSET_CONDITION_CODE_CHAR, + state->conditionCodes.Overflow ? 'V' : UNSET_CONDITION_CODE_CHAR); +} + +// Returns the word starting at the provided address +word readWord(byte *memory, uint32_t address) { + word result = 0; + int bytesPerWord = WORD_BITS / BYTE_BITS - 1; + for (int i = 0; i <= bytesPerWord; i++) + result |= (word) memory[address + i] << (BYTE_BITS * i); + return result; +} + +// Returns the double word starting at the provided address +dword readDoubleWord(byte *memory, uint32_t address) { + dword result = 0; + int bytesPerDword = DWORD_BITS / BYTE_BITS - 1; + for (int i = 0; i <= bytesPerDword; i++) + result |= (dword) memory[address + i] << (BYTE_BITS * i); + return result; +} + +// Prints all non-zero memory locations into the provided stream +void printMemory(Machine *state, FILE *stream) { + fprintf(stream, "\nNon-zero memory:\n"); + + // print memory 4 byte aligned + for (int addr = 0; addr < MEMORY_SIZE; addr+= 4) { + word data = readWord(state->memory, addr); + if (data != 0) { + fprintf(stream, "0x%08x: %08x\n", addr, data); + } + } +} diff --git a/src/print.h b/src/print.h new file mode 100644 index 0000000..404e947 --- /dev/null +++ b/src/print.h @@ -0,0 +1,12 @@ +#ifndef __PRINT__ +#define __PRINT__ +#include +#include "emulator.h" + +word readWord(byte *memory, uint32_t address); +dword readDoubleWord(byte *memory, uint32_t address); +void printState(Machine *state, FILE *stream); +void printRegisters(Machine *state, FILE *stream); +void printMemory(Machine *state, FILE *stream); + +#endif diff --git a/src/symboltable.c b/src/symboltable.h similarity index 81% rename from src/symboltable.c rename to src/symboltable.h index fb448c1..cd2037c 100644 --- a/src/symboltable.c +++ b/src/symboltable.h @@ -19,8 +19,14 @@ struct st { // add new node to the end void st_add(st table, void* key, void* value) { node n = {key, value, table.tail}; - (*(table.tail)).next = &n; - table.tail = &n; + if (table.head == NULL) { + table.head = &n; + table.tail = &n; + } + else { + (*(table.tail)).next = &n; + table.tail = &n; + } } // returns the pointer to key of the specified node, or null, if it does not exist diff --git a/src/twopassassembly.c b/src/twopassassembly.c index 5729b61..cda97ec 100644 --- a/src/twopassassembly.c +++ b/src/twopassassembly.c @@ -1,17 +1,8 @@ -#include "global.h" - +# include "global.h" +# include "a64instruction.h" +# include "symboltable.h" //generates assembled code based on two pass assembly method -void generateSymbolTable(a64inst_instruction instrs[], int numInstrs){ - //TODO: - //generate symbol table based on inputted assembly code and labels - for(int i=0; itype==LABEL){ - // symbol table stuff here - } - } -} word assembleBranch(a64inst_instruction *instr){ word binInstr = 0; @@ -44,15 +35,192 @@ word assembleBranch(a64inst_instruction *instr){ return binInstr; } -void firstPass(a64inst_instruction instrs[], int numInstrs){ +st* firstPass(a64inst_instruction instrs[], int numInstrs){ //TODO: // -iterate over instructions, adding to symbol table // create symbol table and map labels to addresses/lines + struct st table; + for(int i=0; i