From 856a3e92dea7816197c4f330878370fe1e4335b7 Mon Sep 17 00:00:00 2001 From: Risheng1128 Date: Sun, 13 Nov 2022 05:22:08 +0800 Subject: [PATCH] Introduce basic block This commit introduces the basic block in emulator, meaning that it makes emulator decode and execute numerous instructions at a time. Use hash table and block prediction to manage blocks efficiently. In decode stage, allocate a new block which contains up to 1024 instruction by default, decode the instruction into block until it is full or the latest instruction is a branch instruction and put it into the block map. In execution stage, emulator executes instructions in block. The number of instructions based on the member insn_num in struct block. In particular, when an exception/interrupt occurs, emulator will do the following steps: 1. Execute the exception/interrupt handler that resets a new program counter from the register mtvec and function emulate returns false. 2. Enter to the decode stage again, and create new block based on the new program counter. That is, emulator will stop executing old block and create the new one from new program counter. On the other hand, the file decode.c includes the header file riscv_private.h which includes the gdbstub file. It will make emulator compile failed because the gdbstub is cloned until compiling emulate.c. To resolve this problem, swapping the compile order between emulate.o and decode.o . --- Makefile | 3 +- src/decode.c | 31 +++-- src/decode.h | 42 ++++--- src/emulate.c | 272 ++++++++++++++++++++++++++------------------ src/riscv.c | 131 +++++++++++++++++++++ src/riscv.h | 2 +- src/riscv_private.h | 19 +++- 7 files changed, 366 insertions(+), 134 deletions(-) create mode 100644 src/riscv.c diff --git a/Makefile b/Makefile index 804e4f6e..1f81a044 100644 --- a/Makefile +++ b/Makefile @@ -96,8 +96,9 @@ all: $(BIN) OBJS := \ map.o \ utils.o \ - decode.o \ emulate.o \ + decode.o \ + riscv.o \ io.o \ elf.o \ main.o \ diff --git a/src/decode.c b/src/decode.c index 88d1aae8..a94619dd 100644 --- a/src/decode.c +++ b/src/decode.c @@ -4,9 +4,10 @@ */ #include -#include +#include #include "decode.h" +#include "riscv_private.h" /* decode rd field * rd = insn[11:7] @@ -1653,19 +1654,18 @@ static inline bool op_cbnez(struct rv_insn_t *ir, const uint32_t insn) #define op_cfsd OP_UNIMP /* handler for all unimplemented opcodes */ -static inline bool op_unimp(struct rv_insn_t *rv_insn UNUSED, - uint32_t insn UNUSED) +static inline bool op_unimp(struct rv_insn_t *ir UNUSED, uint32_t insn UNUSED) { return false; } /* RV32 decode handler type */ -typedef bool (*decode_t)(struct rv_insn_t *rv_insn, uint32_t insn); +typedef bool (*decode_t)(struct rv_insn_t *ir, uint32_t insn); /* decode RISC-V instruction */ -bool rv_decode(struct rv_insn_t *ir, uint32_t insn, uint8_t *insn_len) +bool rv_decode(struct rv_insn_t *ir, uint32_t insn) { - assert(ir && insn_len); + assert(ir); #define OP_UNIMP op_unimp #define OP(insn) op_##insn @@ -1704,7 +1704,7 @@ bool rv_decode(struct rv_insn_t *ir, uint32_t insn, uint8_t *insn_len) if ((insn & FC_OPCODE) != 3) { insn &= 0x0000FFFF; const uint16_t c_index = (insn & FC_FUNC3) >> 11 | (insn & FC_OPCODE); - *insn_len = INSN_16; + ir->insn_len = INSN_16; /* decode instruction (compressed instructions) */ const decode_t op = rvc_jump_table[c_index]; @@ -1715,7 +1715,7 @@ bool rv_decode(struct rv_insn_t *ir, uint32_t insn, uint8_t *insn_len) /* standard uncompressed instruction */ const uint32_t index = (insn & INSN_6_2) >> 2; - *insn_len = INSN_32; + ir->insn_len = INSN_32; /* decode instruction */ const decode_t op = rv_jump_table[index]; @@ -1725,3 +1725,18 @@ bool rv_decode(struct rv_insn_t *ir, uint32_t insn, uint8_t *insn_len) #undef OP_UNIMP #undef OP } + +/* clear all block in the block map */ +void block_map_clear(struct block_map *map) +{ + assert(map); + for (uint32_t i = 0; i < map->block_capacity; i++) { + struct block *block = map->map[i]; + if (block) { + free(block->ir); + free(block); + map->map[i] = NULL; + } + } + map->size = 0; +} diff --git a/src/decode.h b/src/decode.h index 7cf3c7e8..34442a91 100644 --- a/src/decode.h +++ b/src/decode.h @@ -219,7 +219,6 @@ enum { /* clang-format on */ enum { - INSN_UNKNOWN = 0, INSN_16 = 2, INSN_32 = 4, }; @@ -236,21 +235,36 @@ struct rv_insn_t { #if RV32_HAS(EXT_C) uint8_t shamt; #endif + + /* instruction length */ + uint8_t insn_len; +}; + +/* translated basic block */ +struct block { + /* number of instructions encompased */ + uint32_t insn_number; + /* address range of the basic block */ + uint32_t pc_start, pc_end; + /* maximum of instructions encompased */ + uint32_t insn_capacity; + /* block predictoin */ + struct block *predict; + /* memory blocks */ + struct rv_insn_t *ir; }; -/* sign extend a 16 bit value */ -static inline uint32_t sign_extend_h(const uint32_t x) -{ - return (int32_t) ((int16_t) x); -} +struct block_map { + /* max number of entries in the block map */ + uint32_t block_capacity; + /* number of entries currently in the map */ + uint32_t size; + /* block map */ + struct block **map; +}; -/* sign extend an 8 bit value */ -static inline uint32_t sign_extend_b(const uint32_t x) -{ - return (int32_t) ((int8_t) x); -} +/* clear all block in the block map */ +void block_map_clear(struct block_map *map); /* decode the RISC-V instruction */ -bool rv_decode(struct rv_insn_t *rv_insn, - const uint32_t insn, - uint8_t *insn_len); +bool rv_decode(struct rv_insn_t *ir, const uint32_t insn); diff --git a/src/emulate.c b/src/emulate.c index 56105173..2c311077 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -4,9 +4,7 @@ */ #include -#include #include -#include #include #include @@ -52,7 +50,7 @@ enum { static void rv_exception_default_handler(struct riscv_t *rv) { - rv->csr_mepc += rv->insn_len; + rv->csr_mepc += rv->compressed ? INSN_16 : INSN_32; rv->PC = rv->csr_mepc; /* mret */ } @@ -256,8 +254,11 @@ static bool insn_is_misaligned(uint32_t pc) ); } -static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) +static bool emulate(struct riscv_t *rv, struct rv_insn_t *ir) { + /* check instruction is compressed or not */ + rv->compressed = (ir->insn_len == INSN_16); + switch (ir->opcode) { /* RV32I Base Instruction Set */ case rv_insn_lui: /* LUI: Load Upper Immediate */ @@ -286,7 +287,7 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) rv->PC += ir->imm; /* link with return address */ if (ir->rd) - rv->X[ir->rd] = pc + rv->insn_len; + rv->X[ir->rd] = pc + ir->insn_len; /* check instruction misaligned */ if (insn_is_misaligned(rv->PC)) { rv_except_insn_misaligned(rv, pc); @@ -308,7 +309,7 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) rv->PC = (rv->X[ir->rs1] + ir->imm) & ~1U; /* link */ if (ir->rd) - rv->X[ir->rd] = pc + rv->insn_len; + rv->X[ir->rd] = pc + ir->insn_len; /* check instruction misaligned */ if (insn_is_misaligned(rv->PC)) { rv_except_insn_misaligned(rv, pc); @@ -963,7 +964,7 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) rv->X[ir->rd] += (int16_t) ir->imm; break; case rv_insn_cjal: - rv->X[1] = rv->PC + rv->insn_len; + rv->X[1] = rv->PC + ir->insn_len; rv->PC += ir->imm; if (rv->PC & 0x1) { rv_except_insn_misaligned(rv, rv->PC); @@ -1055,11 +1056,11 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) * the value in register rs1' is zero. It expands to beq rs1', x0, * offset[8:1]. */ - rv->PC += (!rv->X[ir->rs1]) ? (uint32_t) ir->imm : rv->insn_len; + rv->PC += (!rv->X[ir->rs1]) ? (uint32_t) ir->imm : ir->insn_len; /* can branch */ return true; case rv_insn_cbnez: /* C.BEQZ */ - rv->PC += (rv->X[ir->rs1]) ? (uint32_t) ir->imm : rv->insn_len; + rv->PC += (rv->X[ir->rs1]) ? (uint32_t) ir->imm : ir->insn_len; /* can branch */ return true; case rv_insn_cslli: /* C.SLLI */ @@ -1093,7 +1094,7 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) case rv_insn_cjalr: { /* C.JALR */ /* Unconditional jump and store PC+2 to ra */ const int32_t jump_to = rv->X[ir->rs1]; - rv->X[rv_reg_ra] = rv->PC + rv->insn_len; + rv->X[rv_reg_ra] = rv->PC + ir->insn_len; rv->PC = jump_to; if (rv->PC & 0x1) { rv_except_insn_misaligned(rv, rv->PC); @@ -1125,146 +1126,201 @@ static bool rv_emulate(struct riscv_t *rv, struct rv_insn_t *ir) } /* step over instruction */ - rv->PC += rv->insn_len; + rv->PC += ir->insn_len; return true; } -void rv_step(struct riscv_t *rv, int32_t cycles) +static bool insn_is_branch(uint8_t opcode) { - assert(rv); - uint32_t insn; - struct rv_insn_t ir; - const uint64_t cycles_target = rv->csr_cycle + cycles; - - while (rv->csr_cycle < cycles_target && !rv->halt) { - /* enforce zero register */ - rv->X[rv_reg_zero] = 0; - - /* fetch the next instruction */ - insn = rv->io.mem_ifetch(rv, rv->PC); - memset(&ir, 0, sizeof(struct rv_insn_t)); - - /* decode the instruction */ - if (!rv_decode(&ir, insn, &rv->insn_len)) { - rv_except_illegal_insn(rv, insn); - break; - } - - /* execute the instruciton */ - if (!rv_emulate(rv, &ir)) - break; - - /* increment the cycles csr */ - rv->csr_cycle++; + switch (opcode) { + case rv_insn_jal: + case rv_insn_jalr: + case rv_insn_beq: + case rv_insn_bne: + case rv_insn_blt: + case rv_insn_bge: + case rv_insn_bltu: + case rv_insn_bgeu: + case rv_insn_ecall: + case rv_insn_ebreak: + case rv_insn_mret: +#if RV32_HAS(EXT_C) + case rv_insn_cj: + case rv_insn_cjr: + case rv_insn_cjal: + case rv_insn_cjalr: + case rv_insn_cbeqz: + case rv_insn_cbnez: + case rv_insn_cebreak: +#endif +#if RV32_HAS(Zifencei) + case rv_insn_fencei: +#endif + return true; } + return false; } -riscv_user_t rv_userdata(struct riscv_t *rv) +/* hash function is used when mapping address into the block map */ +static uint32_t hash(size_t k) { - assert(rv); - return rv->userdata; + k ^= k << 21; + k ^= k >> 17; +#if (SIZE_MAX > 0xFFFFFFFF) + k ^= k >> 35; + k ^= k >> 51; +#endif + return k; } -bool rv_set_pc(struct riscv_t *rv, riscv_word_t pc) +/* allocate a basic block */ +static struct block *block_alloc(const uint8_t bits) { - assert(rv); -#if RV32_HAS(EXT_C) - if (pc & 1) -#else - if (pc & 3) -#endif - return false; - - rv->PC = pc; - return true; + struct block *block = malloc(sizeof(struct block)); + block->insn_capacity = 1 << bits; + block->insn_number = 0; + block->predict = NULL; + block->ir = malloc(block->insn_capacity * sizeof(struct rv_insn_t)); + return block; } -riscv_word_t rv_get_pc(struct riscv_t *rv) +/* insert a block into block map */ +static void block_insert(struct block_map *map, const struct block *block) { - assert(rv); - return rv->PC; + assert(map && block); + const uint32_t mask = map->block_capacity - 1; + uint32_t index = hash(block->pc_start); + + /* insert into the block map */ + for (;; index++) { + if (!map->map[index & mask]) { + map->map[index & mask] = (struct block *) block; + break; + } + } + map->size++; } -void rv_set_reg(struct riscv_t *rv, uint32_t reg, riscv_word_t in) +/* try to locate an already translated block in the block map */ +static struct block *block_find(const struct block_map *map, const uint32_t pc) { - assert(rv); - if (reg < RV_NUM_REGS && reg != rv_reg_zero) - rv->X[reg] = in; + assert(map); + uint32_t index = hash(pc); + const uint32_t mask = map->block_capacity - 1; + + /* find block in block map */ + for (;; index++) { + struct block *block = map->map[index & mask]; + if (!block) + return NULL; + + if (block->pc_start == pc) + return block; + } + return NULL; } -riscv_word_t rv_get_reg(struct riscv_t *rv, uint32_t reg) +/* execute a basic block */ +static bool block_emulate(struct riscv_t *rv, const struct block *block) { - assert(rv); - if (reg < RV_NUM_REGS) - return rv->X[reg]; + /* execute the block */ + for (uint32_t i = 0; i < block->insn_number; i++) { + /* enforce zero register */ + rv->X[rv_reg_zero] = 0; + + /* execute the instruction */ + if (!emulate(rv, block->ir + i)) + return false; - return ~0U; + /* increment the cycles csr */ + rv->csr_cycle++; + } + return true; } -struct riscv_t *rv_create(const struct riscv_io_t *io, riscv_user_t userdata) +static void block_translate(struct riscv_t *rv, struct block *block) { - assert(io); - struct riscv_t *rv = (struct riscv_t *) malloc(sizeof(struct riscv_t)); - memset(rv, 0, sizeof(struct riscv_t)); + block->pc_start = rv->PC; + block->pc_end = rv->PC; - /* copy over the IO interface */ - memcpy(&rv->io, io, sizeof(struct riscv_io_t)); + /* translate the basic block */ + while (block->insn_number < block->insn_capacity) { + struct rv_insn_t *ir = block->ir + block->insn_number; + memset(ir, 0, sizeof(struct rv_insn_t)); - /* copy over the userdata */ - rv->userdata = userdata; + /* fetch the next instruction */ + const uint32_t insn = rv->io.mem_ifetch(rv, block->pc_end); - /* reset */ - rv_reset(rv, 0U); + /* decode the instruction */ + if (!rv_decode(ir, insn)) { + rv_except_illegal_insn(rv, insn); + break; + } - return rv; -} + /* compute the end of pc */ + block->pc_end += ir->insn_len; + block->insn_number++; -void rv_halt(struct riscv_t *rv) -{ - rv->halt = true; + /* stop on branch */ + if (insn_is_branch(ir->opcode)) + break; + } } -bool rv_has_halted(struct riscv_t *rv) +static struct block *block_find_or_translate(struct riscv_t *rv, + struct block *prev) { - return rv->halt; -} + struct block_map *map = &rv->block_map; + /* lookup the next block in the block map */ + struct block *next = block_find(map, rv->PC); + + if (!next) { + if (map->size * 1.25 > map->block_capacity) { + block_map_clear(map); + prev = NULL; + } -void rv_delete(struct riscv_t *rv) -{ - assert(rv); - free(rv); + /* allocate a new block */ + next = block_alloc(10); + + /* translate the basic block */ + block_translate(rv, next); + + /* insert the block into block map */ + block_insert(&rv->block_map, next); + + /* update the block prediction */ + if (prev) + prev->predict = next; + } + + return next; } -void rv_reset(struct riscv_t *rv, riscv_word_t pc) +void rv_step(struct riscv_t *rv, int32_t cycles) { assert(rv); - memset(rv->X, 0, sizeof(uint32_t) * RV_NUM_REGS); - - /* set the reset address */ - rv->PC = pc; - rv->insn_len = INSN_UNKNOWN; - /* set the default stack pointer */ - rv->X[rv_reg_sp] = DEFAULT_STACK_ADDR; + /* find or translate a block for starting PC */ + struct block *prev = NULL, *next = NULL; + const uint64_t cycles_target = rv->csr_cycle + cycles; - /* reset the csrs */ - rv->csr_mtvec = 0; - rv->csr_cycle = 0; - rv->csr_mstatus = 0; + while (rv->csr_cycle < cycles_target && !rv->halt) { + /* check the block prediction first */ + if (prev && prev->predict && prev->predict->pc_start == rv->PC) { + next = prev->predict; + } else { + next = block_find_or_translate(rv, prev); + } -#if RV32_HAS(EXT_F) - /* reset float registers */ - memset(rv->F, 0, sizeof(float) * RV_NUM_REGS); - rv->csr_fcsr = 0; -#endif + assert(next); - rv->halt = false; -} + /* execute the block */ + if (!block_emulate(rv, next)) + break; -/* FIXME: provide real implementation */ -void rv_stats(struct riscv_t *rv) -{ - printf("CSR cycle count: %" PRIu64 "\n", rv->csr_cycle); + prev = next; + } } void ebreak_handler(struct riscv_t *rv) diff --git a/src/riscv.c b/src/riscv.c new file mode 100644 index 00000000..eb63d1cd --- /dev/null +++ b/src/riscv.c @@ -0,0 +1,131 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#include +#include +#include +#include +#include + +#include "riscv_private.h" + +/* initialize the block map */ +static void block_map_init(struct block_map *map, const uint8_t bits) +{ + map->block_capacity = 1 << bits; + map->size = 0; + map->map = calloc(map->block_capacity, sizeof(struct block *)); +} + +riscv_user_t rv_userdata(struct riscv_t *rv) +{ + assert(rv); + return rv->userdata; +} + +bool rv_set_pc(struct riscv_t *rv, riscv_word_t pc) +{ + assert(rv); +#if RV32_HAS(EXT_C) + if (pc & 1) +#else + if (pc & 3) +#endif + return false; + + rv->PC = pc; + return true; +} + +riscv_word_t rv_get_pc(struct riscv_t *rv) +{ + assert(rv); + return rv->PC; +} + +void rv_set_reg(struct riscv_t *rv, uint32_t reg, riscv_word_t in) +{ + assert(rv); + if (reg < RV_NUM_REGS && reg != rv_reg_zero) + rv->X[reg] = in; +} + +riscv_word_t rv_get_reg(struct riscv_t *rv, uint32_t reg) +{ + assert(rv); + if (reg < RV_NUM_REGS) + return rv->X[reg]; + + return ~0U; +} + +struct riscv_t *rv_create(const struct riscv_io_t *io, riscv_user_t userdata) +{ + assert(io); + + struct riscv_t *rv = calloc(1, sizeof(struct riscv_t)); + + /* copy over the IO interface */ + memcpy(&rv->io, io, sizeof(struct riscv_io_t)); + + /* copy over the userdata */ + rv->userdata = userdata; + + /* initialize the block map */ + block_map_init(&rv->block_map, 10); + + /* reset */ + rv_reset(rv, 0U); + + return rv; +} + +void rv_halt(struct riscv_t *rv) +{ + rv->halt = true; +} + +bool rv_has_halted(struct riscv_t *rv) +{ + return rv->halt; +} + +void rv_delete(struct riscv_t *rv) +{ + assert(rv); + block_map_clear(&rv->block_map); + free(rv); +} + +void rv_reset(struct riscv_t *rv, riscv_word_t pc) +{ + assert(rv); + memset(rv->X, 0, sizeof(uint32_t) * RV_NUM_REGS); + + /* set the reset address */ + rv->PC = pc; + + /* set the default stack pointer */ + rv->X[rv_reg_sp] = DEFAULT_STACK_ADDR; + + /* reset the csrs */ + rv->csr_mtvec = 0; + rv->csr_cycle = 0; + rv->csr_mstatus = 0; + +#if RV32_HAS(EXT_F) + /* reset float registers */ + memset(rv->F, 0, sizeof(float) * RV_NUM_REGS); + rv->csr_fcsr = 0; +#endif + + rv->halt = false; +} + +/* FIXME: provide real implementation */ +void rv_stats(struct riscv_t *rv) +{ + printf("CSR cycle count: %" PRIu64 "\n", rv->csr_cycle); +} \ No newline at end of file diff --git a/src/riscv.h b/src/riscv.h index e8e3e930..bbeca5ae 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -111,7 +111,7 @@ struct riscv_io_t { struct riscv_t *rv_create(const struct riscv_io_t *io, riscv_user_t user_data); /* delete a RISC-V emulator */ -void rv_delete(struct riscv_t *); +void rv_delete(struct riscv_t *rv); /* reset the RISC-V processor */ void rv_reset(struct riscv_t *, riscv_word_t pc); diff --git a/src/riscv_private.h b/src/riscv_private.h index b937c1ea..b2b8a411 100644 --- a/src/riscv_private.h +++ b/src/riscv_private.h @@ -10,6 +10,7 @@ #include "breakpoint.h" #include "mini-gdbstub/include/gdbstub.h" #endif +#include "decode.h" #include "riscv.h" #define RV_NUM_REGS 32 @@ -96,6 +97,20 @@ struct riscv_t { uint32_t csr_mip; uint32_t csr_mbadaddr; - /* current instruction length */ - uint8_t insn_len; + /* current instruction is compressed or not */ + bool compressed; + /* basic block map */ + struct block_map block_map; }; + +/* sign extend a 16 bit value */ +static inline uint32_t sign_extend_h(const uint32_t x) +{ + return (int32_t) ((int16_t) x); +} + +/* sign extend an 8 bit value */ +static inline uint32_t sign_extend_b(const uint32_t x) +{ + return (int32_t) ((int8_t) x); +}