diff --git a/src/Makefile b/src/Makefile index 7106e44..3a1441a 100755 --- a/src/Makefile +++ b/src/Makefile @@ -10,8 +10,7 @@ CFLAGS ?= -std=c17 -g\ all: assemble emulate assemble: assemble.o -emulate: emulate.o +emulate: emulate.o util/fileio.o emulator/execute.o emulator/decode.o emulator/print.o emulator/machine_util.o util/binary_util.o clean: - $(RM) *.o assemble emulate - + $(RM) *.o assemble emulate emulator/execute.o emulator/decode.o diff --git a/src/a64instruction/a64instruction.h b/src/a64instruction/a64instruction.h new file mode 100644 index 0000000..c12584c --- /dev/null +++ b/src/a64instruction/a64instruction.h @@ -0,0 +1,36 @@ +#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). +typedef enum { + a64inst_DPIMMEDIATE, + a64inst_DPREGISTER, + a64inst_SINGLETRANSFER, + a64inst_LOADLITERAL, + a64inst_BRANCH, + a64inst_HALT, + a64inst_LABEL, + a64inst_DIRECTIVE +} a64inst_type; + +// Structure the holds the type and operand data of an instruction +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; + +#endif diff --git a/src/a64instruction/a64instruction_Branch.h b/src/a64instruction/a64instruction_Branch.h new file mode 100644 index 0000000..d280973 --- /dev/null +++ b/src/a64instruction/a64instruction_Branch.h @@ -0,0 +1,40 @@ +#include +#include "a64instruction_global.h" + +typedef enum { + a64inst_UNCONDITIONAL = 0, + a64inst_REGISTER = 3, + a64inst_CONDITIONAL = 1 +} a64inst_BranchType; + +typedef struct { + word unconditionalOffset; +} a64inst_Branch_UnconditionalData; + +typedef struct { + a64inst_regSpecifier src; +} a64inst_Branch_RegisterData; + +typedef enum { + EQ = 0, // Equal + NE = 1, // Not Equal + GE = 10, // Signed greater or equal + LT = 11, // Signed less than + GT = 12, // Signed greater than + LE = 13, // signed less than or equal + AL = 14 // Always +} a64inst_ConditionType; //a64inst_Branch_ConditionType? + +typedef struct { + a64inst_ConditionType cond; + word offset; +} a64inst_Branch_ConditionalData; + +typedef struct { + a64inst_BranchType BranchType; + union { + a64inst_Branch_UnconditionalData unconditionalData; + a64inst_Branch_RegisterData registerData; + a64inst_Branch_ConditionalData conditionalData; + } processOpData; +} a64inst_BranchData; diff --git a/src/a64instruction/a64instruction_DP.h b/src/a64instruction/a64instruction_DP.h new file mode 100644 index 0000000..8ffb1f7 --- /dev/null +++ b/src/a64instruction/a64instruction_DP.h @@ -0,0 +1,12 @@ +#ifndef __A64INSTRUCTION_DP__ +#define __A64INSTRUCTION_DP__ + +// Denotes the type of arithmetic operations supported by the architecture +typedef enum { + a64inst_ADD = 0, + a64inst_ADDS = 1, + a64inst_SUB = 2, + a64inst_SUBS = 3 +} a64inst_arithmOp; + +#endif diff --git a/src/a64instruction/a64instruction_DPImmediate.h b/src/a64instruction/a64instruction_DPImmediate.h new file mode 100644 index 0000000..8b5e68c --- /dev/null +++ b/src/a64instruction/a64instruction_DPImmediate.h @@ -0,0 +1,42 @@ +#include +#include "a64instruction_global.h" +#include "a64instruction_DP.h" + +// Denotes the type of data processing operation +typedef enum { + a64inst_DPI_ARITHM, + a64inst_DPI_WIDEMOV +} a64inst_DPIOpType; + +// Denotes the type of wide move operations supported by the architecture +typedef enum { + a64inst_MOVN = 0, + a64inst_UNDEFINED = 1, + a64inst_MOVZ = 2, + a64inst_MOVK = 3 +} a64inst_wideMovOp; + +// Holds data specific to arithmetic immediate data processing instructions +typedef struct { + bool shiftImmediate; + uint16_t immediate; + a64inst_regSpecifier src; +} a64inst_DPImmediate_ArithmData; + +// Holds data specific to wide move immediate data processing instructions +typedef struct { + uint8_t shiftScalar; + uint16_t immediate; +} a64inst_DPImmediate_WideMovData; + +// Holds data for immediate data processing instructions +typedef struct { + a64inst_regType regType; + a64inst_DPIOpType DPIOpType; + unsigned int processOp; + union { + a64inst_DPImmediate_ArithmData arithmData; + a64inst_DPImmediate_WideMovData wideMovData; + } processOpData; + a64inst_regSpecifier dest; +} a64inst_DPImmediateData; diff --git a/src/a64instruction/a64instruction_DPRegister.h b/src/a64instruction/a64instruction_DPRegister.h new file mode 100644 index 0000000..ecf2f2c --- /dev/null +++ b/src/a64instruction/a64instruction_DPRegister.h @@ -0,0 +1,56 @@ +#include +#include "a64instruction_global.h" +#include "a64instruction_DP.h" + +// Denotes the type of data processing operation +typedef enum { + a64inst_DPR_ARITHMLOGIC = 0, + a64inst_DPR_MULTIPLY = 1 +} a64inst_DPROpType; + +// Denotes the logical operations supported by the architecture +typedef enum { + a64inst_AND = 0, + a64inst_OR = 1, + a64inst_XOR = 2, + a64inst_AND_FLAGGED = 3 +} a64inst_logicOp; + +// Denotes the different kinds of shifts supported by the architecture +typedef enum { + a64inst_LSL = 0, + a64inst_LSR = 1, + a64inst_ASR = 2, + a64inst_ROR = 3 +} a64inst_ShiftType; + +// Holds data specific to arithmetic/logic register data processing instructions +typedef struct { + enum { + 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; + +// Holds data specific to multiply register data processing instructions +typedef struct { + bool negProd; + a64inst_regSpecifier summand; +} a64inst_DPRegister_MultiplyData; + +// Holds data for register data processing instructions +typedef struct { + a64inst_regType regType; + a64inst_DPROpType DPROpType; + uint8_t processOp; + a64inst_regSpecifier src2; + union { + a64inst_DPRegister_ArithmLogicData arithmLogicData; + a64inst_DPRegister_MultiplyData multiplydata; + } processOpData; + a64inst_regSpecifier src1; + a64inst_regSpecifier dest; +} a64inst_DPRegisterData; diff --git a/src/a64instruction/a64instruction_Directive.h b/src/a64instruction/a64instruction_Directive.h new file mode 100644 index 0000000..fa2faaa --- /dev/null +++ b/src/a64instruction/a64instruction_Directive.h @@ -0,0 +1,5 @@ +#include "./a64instruction_global.h" + +typedef struct { + dword value; +} a64inst_DirectiveData; diff --git a/src/a64instruction/a64instruction_Label.h b/src/a64instruction/a64instruction_Label.h new file mode 100644 index 0000000..b1d26e9 --- /dev/null +++ b/src/a64instruction/a64instruction_Label.h @@ -0,0 +1,3 @@ +typedef struct { + char* label; +} a64inst_LabelData; diff --git a/src/a64instruction/a64instruction_SingleTransfer.h b/src/a64instruction/a64instruction_SingleTransfer.h new file mode 100644 index 0000000..3e3da2b --- /dev/null +++ b/src/a64instruction/a64instruction_SingleTransfer.h @@ -0,0 +1,46 @@ +#include +#include "a64instruction_global.h" + +typedef enum { + a64inst_SINGLE_TRANSFER_SINGLE_DATA_TRANSFER = 1, + a64inst_SINGLE_TRANSFER_LOAD_LITERAL = 0 +} a64inst_SingleTransferType; + +typedef enum { + a64inst_STORE, + a64inst_LOAD +} a64inst_TransferType; + +typedef enum { + a64inst_REGISTER_OFFSET = 2, + a64inst_PRE_INDEXED = 1, + a64inst_POST_INDEXED = 0, + a64inst_UNSIGNED_OFFSET = 3 +} a64inst_AddressingMode; + +typedef struct { + a64inst_TransferType transferType; + a64inst_AddressingMode addressingMode; + + union { + a64inst_regSpecifier offsetReg; + uint16_t indexedOffset; + uint16_t unsignedOffset; + } a64inst_addressingModeData; + + a64inst_regSpecifier base; +} a64inst_SingleDataTransferData; + +typedef struct { + uint32_t offset; +} a64inst_LoadLiteralData; + +typedef struct { + a64inst_SingleTransferType SingleTransferOpType; + a64inst_regType regType; + a64inst_regSpecifier target; + union { + a64inst_SingleDataTransferData singleDataTransferData; + a64inst_LoadLiteralData loadLiteralData; + } processOpData; +} a64inst_SingleTransferData; diff --git a/src/a64instruction/a64instruction_global.h b/src/a64instruction/a64instruction_global.h new file mode 100644 index 0000000..b50ab67 --- /dev/null +++ b/src/a64instruction/a64instruction_global.h @@ -0,0 +1,15 @@ +#ifndef __A64INSTRUCTION_GLOBAL__ +#define __A64INSTRUCTION_GLOBAL__ +#include +#include "../global.h" + +// Specifies the register being referred to +typedef uint8_t a64inst_regSpecifier; + +// Denotes the type of register being referred to +typedef enum { + a64inst_W = 0, + a64inst_X = 1 +} a64inst_regType; + +#endif diff --git a/src/emulate.c b/src/emulate.c index e2ad1c8..631d1f0 100755 --- a/src/emulate.c +++ b/src/emulate.c @@ -1,5 +1,72 @@ +/** @file emulate.c + * @brief The main file for the ARMv8 emulator. Reads a binary file and outputs + * the state of the machine after executing the instructions in the file. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + #include +#include +#include "a64instruction/a64instruction.h" +#include "a64instruction/a64instruction_global.h" +#include "emulator/emulator.h" +#include "util/fileio.h" +#include "global.h" +#include "emulator/print.h" +#include "emulator/decode.h" +#include "emulator/execute.h" +#include "emulator/machine_util.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; + } + + // If a second argument is provided, use it as output file instead of stdout. + 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 = readMemory(state.memory, state.pc, a64inst_W); + + // 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/decode.c b/src/emulator/decode.c new file mode 100644 index 0000000..569c63b --- /dev/null +++ b/src/emulator/decode.c @@ -0,0 +1,294 @@ +/** @file decode.c + * @brief Decode Function from binary words to internal representation structs. + * + * This defines the decode function which takes a binary word (uint32_t) and + * decodes it into an a64inst_instruction conveying the same information. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include +#include +#include "decode.h" +#include "../util/binary_util.h" + +// Macro that calls getBit() for a bitfield whose constants follow the format +// FIELDNAME_LSB and FIELDNAME_MSB, storing the result in the variable wrd +#define getField(fieldname) getBits(wrd, fieldname##_LSB, fieldname##_MSB) + +/************************************ + * CONSTANTS + ************************************/ + +#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_FLAG_LSB 25 +#define DP_REG_FLAG_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 +#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 + +/************************************ + * PROTOTYPES + ************************************/ + +static void decodeDPI(word wrd, a64inst_instruction *inst); +static void decodeBranch(word wrd, a64inst_instruction *inst); +static void decodeDPRegister(word wrd, a64inst_instruction *inst); +static void decodeSingleTransfer(word wrd, a64inst_instruction *inst); + +/************************************ + * FUNCTIONS + ************************************/ + +a64inst_instruction *decode(word wrd) { + + a64inst_instruction *inst = malloc(sizeof(a64inst_instruction)); + if (inst == NULL) { + fprintf(stderr, "Error: Could not allocate memory for an instruction\n"); + exit(EXIT_FAILURE); + } + + word typeId = getField(TYPE_ID); + + if (wrd == HALT_WORD) { + inst->type = a64inst_HALT; + + } else if (typeId == DP_IMM_ID) { + inst->type = a64inst_DPIMMEDIATE; + decodeDPI(wrd, inst); + + } else if (typeId == BRANCH_ID) { + inst->type = a64inst_BRANCH; + decodeBranch(wrd, inst); + + } else if (getField(DP_REG_FLAG) == 1) { + inst->type = a64inst_DPREGISTER; + decodeDPRegister(wrd, inst); + + } else { + inst->type = a64inst_SINGLETRANSFER; + decodeSingleTransfer(wrd, inst); + } + + return inst; +} + +/************************************ + * HELPER FUNCTIONS + ************************************/ + +static void decodeDPI(word wrd, a64inst_instruction *inst) { + inst->data.DPImmediateData.regType = getField(DP_WIDTH); + inst->data.DPImmediateData.processOp = getField(DP_OP); + inst->data.DPImmediateData.dest = getField(DP_DEST); + + switch(getField(DP_IMM_OPTYPE)) { + + case DP_IMM_OPTYPE_ARITHM: + inst->data.DPImmediateData.DPIOpType = a64inst_DPI_ARITHM; + inst->data.DPImmediateData.processOpData.arithmData.shiftImmediate = getField(DP_IMM_ARITHM_SHIFTFLAG); + inst->data.DPImmediateData.processOpData.arithmData.immediate = getField(DP_IMM_ARITHM_IMMVAL); + inst->data.DPImmediateData.processOpData.arithmData.src = getField(DP_IMM_ARITHM_DEST); + break; + + case DP_IMM_OPTYPE_WIDEMOV: + inst->data.DPImmediateData.DPIOpType = a64inst_DPI_WIDEMOV; + inst->data.DPImmediateData.processOpData.wideMovData.shiftScalar = getField(DP_IMM_WIDEMOV_SHIFTSCALAR); + inst->data.DPImmediateData.processOpData.wideMovData.immediate = getField(DP_IMM_WIDEMOV_IMMVAL); + break; + + default: + fprintf(stderr, "Unknown immediate data processing operation type found!\n"); + exit(1); + break; + } +} + +static void decodeBranch(word wrd, a64inst_instruction *inst) { + word branchTypeFlag = getField(BRANCH_TYPE); + inst->data.BranchData.BranchType = branchTypeFlag; + + switch (branchTypeFlag) { + case a64inst_UNCONDITIONAL: + inst->data.BranchData.processOpData.unconditionalData.unconditionalOffset = getField(BRANCH_UNCONDITIONAL_OFFSET); + break; + + case a64inst_CONDITIONAL: + inst->data.BranchData.processOpData.conditionalData.offset = getField(BRANCH_CONDITIONAL_OFFSET); + + word conditionFlag = getField(BRANCH_CONDITIONAL_COND); + + 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 = getField(BRANCH_REGISTER_SRC); + break; + + default: + fprintf(stderr, "Undefined branch type detected!\n"); + exit(1); + break; + } +} + +static void decodeDPRegister(word wrd, a64inst_instruction *inst) { + inst->data.DPRegisterData.regType = getField(DP_WIDTH); + inst->data.DPRegisterData.processOp = getField(DP_OP); + inst->data.DPRegisterData.dest = getField(DP_DEST); + inst->data.DPRegisterData.src1 = getField(DP_REG_SRC1); + inst->data.DPRegisterData.src2 = getField(DP_REG_SRC2); + inst->data.DPRegisterData.DPROpType = getField(DP_REG_OPTYPE); + + a64inst_DPRegister_ArithmLogicData *arithmLogicData = &inst->data.DPRegisterData.processOpData.arithmLogicData; + + arithmLogicData->type = getField(DP_REG_ARITHMLOGIC_ARITHMFLAG); + arithmLogicData->shiftType = getField(DP_REG_ARITHMLOGIC_SHIFTTYPE); + arithmLogicData->negShiftedSrc2 = getField(DP_REG_ARITHMLOGIC_NEGSRC2FLAG); + + 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 = getField(DP_REG_ARITHMLOGIC_SHIFTAMOUNT); + 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 = getField(DP_REG_MULTIPLY_SUMMAND); + inst->data.DPRegisterData.processOpData.multiplydata.negProd = getField(DP_REG_MULTIPLY_NEGPROD); + break; + } +} + +static void decodeSingleTransfer(word wrd, a64inst_instruction *inst) { + inst->data.SingleTransferData.regType = getField(SDT_REGTYPE_FLAG); + inst->data.SingleTransferData.target = getField(SDT_TARGET_REG); + + bool singleTransferOptype = getField(SDT_OPTYPE_FLAG); + if(singleTransferOptype == 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 = getField(SDT_TRANSFER_TYPE); + inst->data.SingleTransferData.processOpData.singleDataTransferData.base = getField(SDT_BASE_REG); + + if (getField(SDT_UNSIGNED_FLAG) == 1) { + // Unsigned offset + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = a64inst_UNSIGNED_OFFSET; + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.unsignedOffset = getField(SDT_OFFSET); + } else if (getField(SDT_REGISTER_FLAG) == 1) { + // Register Offset + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = a64inst_REGISTER_OFFSET; + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.offsetReg = getField(SDT_REGISTER_REG); + } else { + // Pre-Indexed or Post-Indexed + inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode = getField(SDT_INDEXED_ADDRMODE); + inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset = getField(SDT_INDEXED_OFFSET); + } + + } else { + // Load Literal + inst->data.SingleTransferData.SingleTransferOpType = a64inst_SINGLE_TRANSFER_LOAD_LITERAL; + inst->data.SingleTransferData.processOpData.loadLiteralData.offset = getField(SDT_LOAD_LITERAL_OFFSET); + } +} diff --git a/src/emulator/decode.h b/src/emulator/decode.h new file mode 100644 index 0000000..964e5fc --- /dev/null +++ b/src/emulator/decode.h @@ -0,0 +1,21 @@ +/** @file decode.h + * @brief Decode Function from binary words to a special internal + * representation of instructions, a64inst_instruction. + * + * This defines the decode function which takes a binary word (uint32_t) and + * decodes it into an a64inst_instruction conveying the same information. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include "../global.h" +#include "../a64instruction/a64instruction.h" + +/** @brief The main decode function. Takes a word (uint32_t) representing the + * instruction and decodes it into an a64inst_instruction struct. + * @param wrd The word (uint32_t) to be decoded in binary. + * @return a pointer to the heap-allocated a64inst_instruction struct + * representing the decoded instruction if successful. + */ +a64inst_instruction *decode(word wrd); diff --git a/src/emulator/emulator.h b/src/emulator/emulator.h new file mode 100644 index 0000000..33a92b0 --- /dev/null +++ b/src/emulator/emulator.h @@ -0,0 +1,32 @@ +/** @file emulator.h + * @brief Structures to represent the state of the ARMv8 machine to be used + * in the emulator. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#ifndef __EMULATOR__ +#define __EMULATOR__ +#include "../global.h" +#include + +/************************************ + * 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/emulator/execute.c b/src/emulator/execute.c new file mode 100644 index 0000000..2ac3316 --- /dev/null +++ b/src/emulator/execute.c @@ -0,0 +1,380 @@ +/** @file execute.c + * @brief Implementation of the execute function for the ARMv8 emulator. + * + * This defines the execute function which takes a pointer to the machine state + * and a pointer to an a64inst_instruction struct and executes the instruction + * updating the machine state accordingly. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include +#include +#include "execute.h" +#include "print.h" +#include "../util/binary_util.h" +#include "machine_util.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 + +// Type definition for a pointer to a function that executes a particular type +// of a64instruction (requires pointer to the processor state to update, and a +// pointer to the instruction struct to execute). +typedef void (*executeFunction)(Machine *, a64inst_instruction *); + +// Prototypes +static void executeSDT(Machine *state, a64inst_instruction *inst); +static void executeBranch(Machine *state, a64inst_instruction *inst); +static void executeDPImmediate(Machine *state, a64inst_instruction *inst); +static void executeDPRegister(Machine *state, a64inst_instruction *inst); + +// Halt function definition +static void executeHalt(Machine *state, a64inst_instruction *inst) { /*NOP*/ } + +// Define a constant array mapping instruction type enum values to their +// corresponding execute functions +const executeFunction EXECUTE_FUNCTIONS[] = + {&executeDPImmediate, + &executeDPRegister, + &executeSDT, + &executeSDT, + &executeBranch, + &executeHalt}; + + +void execute(Machine *state, a64inst_instruction *inst) { + EXECUTE_FUNCTIONS[inst->type](state, inst); +} + +// 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 = truncateValue(srcVal + arithmImm, regType); + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = getMSB(srcVal, regType) == getMSB(arithmImm, regType) && getMSB(result, regType) != getMSB(srcVal, regType); + state->conditionCodes.Carry = max(truncateValue(srcVal, regType), truncateValue(arithmImm, regType)) > result; + 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 = getMSB(srcVal, regType) != getMSB(arithmImm, regType) && getMSB(arithmImm, regType) == getMSB(result, regType); + state->conditionCodes.Carry = srcVal >= arithmImm; + 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 & ~((((dword)1 << 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) + 1 - 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 = truncateValue(src1Val + src2Val, regType); + writeRegister(state, dest, regType, result); + + updateCondNZ(state, result, regType); + state->conditionCodes.Overflow = getMSB(src1Val, regType) == getMSB(src2Val, regType) && getMSB(result, regType) != getMSB(src1Val, regType); + state->conditionCodes.Carry = max(truncateValue(src1Val, regType), truncateValue(src2Val, regType)) > result; + 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(src2Val, 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:; + 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); + break; + + // Unknown instruction detected! + default: + fprintf(stderr, "Attempting to execute instruction with unknown DPR operand type!\n"); + break; + } +} + +static void executeSDT(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 + signExtend(inst->data.SingleTransferData.processOpData.loadLiteralData.offset, 19) * 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 += signExtend(inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset, 9); + state->registers[inst->data.SingleTransferData.processOpData.singleDataTransferData.base] = address; + break; + case a64inst_POST_INDEXED: + break; + } + } + + if (isLoad) { + state->registers[inst->data.SingleTransferData.target] = readMemory(state->memory, address, inst->data.SingleTransferData.regType); + + // Update base register if post indexed + bool isSDT = inst->data.SingleTransferData.SingleTransferOpType == a64inst_SINGLE_TRANSFER_SINGLE_DATA_TRANSFER; + if (isSDT && inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode == a64inst_POST_INDEXED) { + dword result = address + signExtend(inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset, 9); + writeRegister(state, inst->data.SingleTransferData.processOpData.singleDataTransferData.base, inst->data.SingleTransferData.regType, result); + } + } else { + storeMemory(state->memory, address, state->registers[inst->data.SingleTransferData.target]); + + // Update base register if post indexed + bool isSDT = inst->data.SingleTransferData.SingleTransferOpType == a64inst_SINGLE_TRANSFER_SINGLE_DATA_TRANSFER; + if (isSDT && inst->data.SingleTransferData.processOpData.singleDataTransferData.addressingMode == a64inst_POST_INDEXED) { + dword result = address + signExtend(inst->data.SingleTransferData.processOpData.singleDataTransferData.a64inst_addressingModeData.indexedOffset, 9); + writeRegister(state, inst->data.SingleTransferData.processOpData.singleDataTransferData.base, inst->data.SingleTransferData.regType, result); + } + } + +} + +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); + } +} + +static void executeBranch(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); + } else { + state->pc += sizeof(word); + } + break; + } +} diff --git a/src/emulator/execute.h b/src/emulator/execute.h new file mode 100644 index 0000000..f9aafec --- /dev/null +++ b/src/emulator/execute.h @@ -0,0 +1,13 @@ +#ifndef __EXECUTE__ +#define __EXECUTE__ +#include "../a64instruction/a64instruction.h" +#include "emulator.h" + +/** @brief Executes the given instruction on the given machine state. + * Updates the machine state to reflect the execution of the instruction. + * + * @param state The machine state to execute the instruction on. + * @param inst The instruction to execute. + */ +void execute(Machine *state, a64inst_instruction *inst); +#endif diff --git a/src/emulator/machine_util.c b/src/emulator/machine_util.c new file mode 100644 index 0000000..7899114 --- /dev/null +++ b/src/emulator/machine_util.c @@ -0,0 +1,52 @@ +/** @file machine_util.c + * @brief Implementation of utility functions for manipulating the machine state. + * + * This defines utility functions for reading and writing memory and registers, + * which are used by the emulator to execute instructions. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include "assert.h" +#include "machine_util.h" +#include "../a64instruction/a64instruction_global.h" +#include "../global.h" +#include "emulator.h" +#include "../util/binary_util.h" + +dword readMemory(byte *memory, uint32_t address, a64inst_regType regType) { + dword result = 0; + int bytesPerWord = (regType == a64inst_W ? WORD_BITS : DWORD_BITS) / BYTE_BITS - 1; + for (int i = 0; i <= bytesPerWord; i++) { + if (regType == a64inst_W) { + result |= (word) memory[address + i] << (BYTE_BITS * i); + } else { + result |= (dword) memory[address + i] << (BYTE_BITS * i); + } + } + return result; +} + +void storeMemory(byte *memory, uint32_t address, dword data) { + int bytesPerDword = DWORD_BITS / BYTE_BITS - 1; + for (int i = 0; i <= bytesPerDword; i++) { + memory[address + i] = (byte)((data >> (BYTE_BITS * i)) & 0xFF); + } +} + +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); + } +} + +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); + } +} diff --git a/src/emulator/machine_util.h b/src/emulator/machine_util.h new file mode 100644 index 0000000..047287e --- /dev/null +++ b/src/emulator/machine_util.h @@ -0,0 +1,55 @@ +/** @file machine_util.h + * @brief Utility functions for manipulating the machine state. + * + * This defines utility functions for reading and writing memory and registers, + * which are used by the emulator to execute instructions. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#ifndef __MACHINE_UTIL__ +#define __MACHINE_UTIL__ +#include "../global.h" +#include "../a64instruction/a64instruction_global.h" +#include "emulator.h" + +/** @brief Reads a (double) word from memory starting at the given address. + * If regType is a64inst_W, the value is truncated to 32 bits. + * + * @param memory The starting address byte-addressable memory block. + * @param address The address to read from. + * @param regType The register type to truncate the value to if necessary. + * @return The (double) word read from memory. + */ +dword readMemory(byte *memory, uint32_t address, a64inst_regType regType); + +/** @brief Stores a double word into memory starting at the given address. + * + * @param memory The starting address byte-addressable memory block. + * @param address The address to store the data at. + * @param data The double word to store. + */ +void storeMemory(byte *memory, uint32_t address, dword data); + +/** @brief Reads a register from the machine state. + * If regType is a64inst_W, the value is truncated to 32 bits. + * + * @param state The machine state to read the register from. + * @param reg The register specifier to read. + * @param regType The register type to truncate the value to if necessary. + * @return The value stored in the register, truncated if necessary. + */ +dword readRegister(Machine *state, a64inst_regSpecifier reg, a64inst_regType regType); + +/** @brief Writes a value to a register in the machine state. + * If regType is a64inst_W, the value is truncated to 32 bits. + * + * @param state The machine state to write the register to. + * @param reg The register specifier to write to. + * @param regType The register type to truncate the value to if necessary. + * @param value The value to write to the register. + */ +void writeRegister(Machine *state, a64inst_regSpecifier reg, a64inst_regType regType, dword value); + +#endif diff --git a/src/emulator/print.c b/src/emulator/print.c new file mode 100644 index 0000000..978a74a --- /dev/null +++ b/src/emulator/print.c @@ -0,0 +1,49 @@ +/** @file print.c + * @brief Implementation of functions to print the machine state. + * + * The function prints the current machine state, including the registers, + * memory and condition codes. This is used to print the final state machine + * in the emulator after all instructions have been executed. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include +#include +#include +#include "print.h" +#include "../a64instruction/a64instruction_global.h" +#include "emulator.h" +#include "machine_util.h" + +#define UNSET_CONDITION_CODE_CHAR '-' + +void printState(Machine *state, FILE *stream) { + printRegisters(state, stream); + printMemory(state, 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); +} + +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 = readMemory(state->memory, addr, a64inst_W); + if (data != 0) { + fprintf(stream, "0x%08x: %08x\n", addr, data); + } + } +} diff --git a/src/emulator/print.h b/src/emulator/print.h new file mode 100644 index 0000000..9d946fd --- /dev/null +++ b/src/emulator/print.h @@ -0,0 +1,48 @@ +/** @file print.h + * @brief Functions to print the machine state, inc registers and memory. + * + * The function prints the current machine state, including the registers, + * memory and condition codes. This is used to print the final state machine + * in the emulator after all instructions have been executed. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#ifndef __PRINT__ +#define __PRINT__ +#include +#include "emulator.h" + +/** @brief Prints the current machine state into the provided stream. + * + * @param state The machine state to print. + * @param stream The stream to print the state to. + * + * @see printRegisters + * @see printMemory + */ +void printState(Machine *state, FILE *stream); + +/** @brief Prints the current machine registers into the provided stream. + * The registers are printed in the format "Xn = value" where n is the register + * number and value is the value stored in the register, in hexadecimal. The + * program counter (PC) and condition codes are also printed. SP is ignored. + * + * @param state The machine state to print the registers of. + * @param stream The stream to print the registers to. + */ +void printRegisters(Machine *state, FILE *stream); + +/** @brief Prints all non-zero memory locations into the provided stream. + * The memory is printed in the format "0xaddress: data" where address is the + * memory address and data is the data stored at that address, in hexadecimal. + * Memory locations are printed 4 bytes at a time. + * Note. Only non-zero memory locations are printed! + * + * @param state The machine state to print the memory of. + * @param stream The stream to print the memory to. + */ +void printMemory(Machine *state, FILE *stream); + +#endif diff --git a/src/global.h b/src/global.h index 138e11a..5d792e4 100644 --- a/src/global.h +++ b/src/global.h @@ -9,8 +9,15 @@ #define __GLOBAL__ #include + +/************************************ + * DEFINITIONS + ************************************/ + // 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 +32,8 @@ typedef uint32_t word; // Double word is a 64-bit unsigned integer. typedef uint64_t dword; -#endif \ No newline at end of file +#define BYTE_BITS 8 +#define WORD_BITS (BYTE_BITS * sizeof(word)) +#define DWORD_BITS (BYTE_BITS * sizeof(dword)) + +#endif diff --git a/src/util/binary_util.c b/src/util/binary_util.c new file mode 100644 index 0000000..68d8e38 --- /dev/null +++ b/src/util/binary_util.c @@ -0,0 +1,59 @@ +/** @file binary_util.c + * @brief Implementation of utility functions for binary manipulation. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include +#include "binary_util.h" + +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; +} + +dword max(dword a, dword b) { + return a > b ? a : b; +} + +dword truncateValue(dword value, a64inst_regType regType) { + if (regType == a64inst_X) { + return value; + } else { + return (word)value; + } +} + +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); + } +} + +dword getMSBPos(a64inst_regType regType) { + return (regType ? DWORD_BITS : WORD_BITS) - 1; +} + +uint8_t getMSB(dword value, a64inst_regType regType) { + return (value >> getMSBPos(regType)) & 1u; +} diff --git a/src/util/binary_util.h b/src/util/binary_util.h new file mode 100644 index 0000000..2b9bc15 --- /dev/null +++ b/src/util/binary_util.h @@ -0,0 +1,62 @@ +/** @file binary_util.h + * @brief Utility functions for binary manipulation. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ +#ifndef __BINARY_UTIL__ +#define __BINARY_UTIL__ + +#include "../global.h" +#include "../a64instruction/a64instruction_global.h" + +/** @brief Extracts the bits between the given indices from a word as an + * unsigned integer, uint32_t (word). + * + * @param wrd The input word (uint32_t) to extract bits from. + * @param lsb The index of the least significant bit to extract. + * @param msb The index of the most significant bit to extract. + * @return The extracted bits as a word. + */ +word getBits(word wrd, uint8_t lsb, uint8_t msb); + +/** @brief Returns the maximum of two given two double words (uint64_t). + * + * @param a The first double word. + * @param b The second double word. + * @return The maximum of the two given double words. + */ +dword max(dword a, dword b); + +/** @brief Truncates a given value to the size of the given register type. + * + * @param value The value to truncate. + * @param regType The register type to truncate the value to. + * @return The truncated value. + */ +dword truncateValue(dword value, a64inst_regType regType); + +/** @brief Sign extend a n-bit given value to a signed integer. + * + * @param value The value to sign extend. + * @param n The number of bits of the signed value. E.g., 16 for simm16. + */ +int64_t signExtend(dword value, unsigned int n); + +/** @brief Returns the position of the most significant bit of the given register type. + * + * @param regType The register type to get the most significant bit position of. + * @return The position of the most significant bit. This is either 31 or 63. + */ +dword getMSBPos(a64inst_regType regType); + +/** @brief Returns the most significant bit of the given value assuming it's of + * the size stored in the given register type. + * + * @param value The value to get the most significant bit of. + * @param regType The register type to get the most significant bit of. + * @return The most significant bit of the given value. + */ +uint8_t getMSB(dword value, a64inst_regType regType); + +#endif diff --git a/src/util/fileio.c b/src/util/fileio.c new file mode 100644 index 0000000..c427c09 --- /dev/null +++ b/src/util/fileio.c @@ -0,0 +1,51 @@ +/** @file fileio.c + * @brief Implementation of file input/output functions. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#include +#include +#include +#include "fileio.h" +#include "../global.h" + +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++; + } + + 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; +} diff --git a/src/util/fileio.h b/src/util/fileio.h new file mode 100644 index 0000000..fc36a23 --- /dev/null +++ b/src/util/fileio.h @@ -0,0 +1,26 @@ +/** @file fileio.h + * @brief File input/output functions, used in both the emulator and assembler. + * + * @author Saleh Bubshait + * @author Themis Demetriades + */ + +#ifndef __FILEIO__ +#define __FILEIO__ +#include +#include "../global.h" + +#define EXIT_FAILURE 1 + +/** @brief Loads a binary file located at filePath to memory, taking up a block + * of exactly memorySize bytes. + * If memorySize is insufficient to store the entire file, an appropriate error + * is reported. Excess memory is set to 0 bit values. + * + * @param filePath The path to the binary file to load. + * @param memorySize The size of the memory block to allocate. + * @return The starting address of the data loaded from the file. + */ +byte *fileio_loadBin(const char *filePath, size_t memorySize); + +#endif diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..49939c8 --- /dev/null +++ b/test.sh @@ -0,0 +1,24 @@ +echo "Trying to compile the program..." +make -C src + +echo "Fetching the test suite..." +git clone git@gitlab.doc.ic.ac.uk:teaching-fellows/armv8_testsuite.git +mkdir armv8_testsuite/solution +cp -r src/. ./armv8_testsuite/solution +cd armv8_testsuite +python3 -m pip install -r requirements.txt +./install + +echo "Running the test suite..." +./run -s + +echo "Test Suite Completed" +cd .. + +printf "%s " "Press enter to continue" +read ans + +echo "Cleaning Up..." +make -C src clean +rm -rf armv8_testsuite +echo "Done"