2018-03-02 13:31:11 +01:00
|
|
|
/*
|
|
|
|
* RISC-V emulation for qemu: main translation routines.
|
|
|
|
*
|
|
|
|
* Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2 or later, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with
|
|
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "qemu/log.h"
|
|
|
|
#include "cpu.h"
|
2020-01-01 12:23:00 +01:00
|
|
|
#include "tcg/tcg-op.h"
|
2018-03-02 13:31:11 +01:00
|
|
|
#include "disas/disas.h"
|
|
|
|
#include "exec/cpu_ldst.h"
|
|
|
|
#include "exec/exec-all.h"
|
|
|
|
#include "exec/helper-proto.h"
|
|
|
|
#include "exec/helper-gen.h"
|
|
|
|
|
2018-02-14 00:27:54 +01:00
|
|
|
#include "exec/translator.h"
|
2018-03-02 13:31:11 +01:00
|
|
|
#include "exec/log.h"
|
|
|
|
|
|
|
|
#include "instmap.h"
|
|
|
|
|
|
|
|
/* global register indices */
|
2020-07-01 17:24:49 +02:00
|
|
|
static TCGv cpu_gpr[32], cpu_pc, cpu_vl;
|
2018-03-02 13:31:11 +01:00
|
|
|
static TCGv_i64 cpu_fpr[32]; /* assume F and D extensions */
|
|
|
|
static TCGv load_res;
|
|
|
|
static TCGv load_val;
|
|
|
|
|
|
|
|
#include "exec/gen-icount.h"
|
|
|
|
|
2021-08-23 21:55:10 +02:00
|
|
|
/*
|
|
|
|
* If an operation is being performed on less than TARGET_LONG_BITS,
|
|
|
|
* it may require the inputs to be sign- or zero-extended; which will
|
|
|
|
* depend on the exact operation being performed.
|
|
|
|
*/
|
|
|
|
typedef enum {
|
|
|
|
EXT_NONE,
|
|
|
|
EXT_SIGN,
|
|
|
|
EXT_ZERO,
|
|
|
|
} DisasExtend;
|
|
|
|
|
2018-03-02 13:31:11 +01:00
|
|
|
typedef struct DisasContext {
|
2018-02-14 00:28:36 +01:00
|
|
|
DisasContextBase base;
|
|
|
|
/* pc_succ_insn points to the instruction following base.pc_next */
|
|
|
|
target_ulong pc_succ_insn;
|
2019-01-15 00:58:32 +01:00
|
|
|
target_ulong priv_ver;
|
2021-08-23 21:55:10 +02:00
|
|
|
target_ulong misa;
|
2020-02-01 02:02:46 +01:00
|
|
|
uint32_t opcode;
|
2019-01-15 00:57:50 +01:00
|
|
|
uint32_t mstatus_fs;
|
2018-03-02 13:31:11 +01:00
|
|
|
uint32_t mem_idx;
|
|
|
|
/* Remember the rounding mode encoded in the previous fp instruction,
|
|
|
|
which we have already installed into env->fp_status. Or -1 for
|
|
|
|
no previous fp instruction. Note that we exit the TB when writing
|
|
|
|
to any system register, which includes CSR_FRM, so we do not have
|
|
|
|
to reset this known value. */
|
|
|
|
int frm;
|
2021-08-23 21:55:10 +02:00
|
|
|
bool w;
|
|
|
|
bool virt_enabled;
|
2019-06-24 10:59:05 +02:00
|
|
|
bool ext_ifencei;
|
2020-11-04 05:43:31 +01:00
|
|
|
bool hlsx;
|
2020-07-01 17:24:52 +02:00
|
|
|
/* vector extension */
|
|
|
|
bool vill;
|
|
|
|
uint8_t lmul;
|
|
|
|
uint8_t sew;
|
|
|
|
uint16_t vlen;
|
2020-07-01 17:24:54 +02:00
|
|
|
uint16_t mlen;
|
2020-07-01 17:24:52 +02:00
|
|
|
bool vl_eq_vlmax;
|
2021-08-23 21:55:10 +02:00
|
|
|
uint8_t ntemp;
|
2021-01-08 23:42:52 +01:00
|
|
|
CPUState *cs;
|
2021-08-23 21:55:10 +02:00
|
|
|
TCGv zero;
|
|
|
|
/* Space for 3 operands plus 1 extra for address computation. */
|
|
|
|
TCGv temp[4];
|
2018-03-02 13:31:11 +01:00
|
|
|
} DisasContext;
|
|
|
|
|
2019-01-15 00:58:42 +01:00
|
|
|
static inline bool has_ext(DisasContext *ctx, uint32_t ext)
|
|
|
|
{
|
|
|
|
return ctx->misa & ext;
|
2020-07-24 02:28:02 +02:00
|
|
|
}
|
|
|
|
|
2021-04-24 05:33:18 +02:00
|
|
|
#ifdef TARGET_RISCV32
|
|
|
|
# define is_32bit(ctx) true
|
|
|
|
#elif defined(CONFIG_USER_ONLY)
|
|
|
|
# define is_32bit(ctx) false
|
|
|
|
#else
|
|
|
|
static inline bool is_32bit(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
return (ctx->misa & RV32) == RV32;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-07-24 02:28:02 +02:00
|
|
|
/*
|
|
|
|
* RISC-V requires NaN-boxing of narrower width floating point values.
|
|
|
|
* This applies when a 32-bit value is assigned to a 64-bit FP register.
|
|
|
|
* For consistency and simplicity, we nanbox results even when the RVD
|
|
|
|
* extension is not present.
|
|
|
|
*/
|
|
|
|
static void gen_nanbox_s(TCGv_i64 out, TCGv_i64 in)
|
|
|
|
{
|
|
|
|
tcg_gen_ori_i64(out, in, MAKE_64BIT_MASK(32, 32));
|
2020-07-24 02:28:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A narrow n-bit operation, where n < FLEN, checks that input operands
|
|
|
|
* are correctly Nan-boxed, i.e., all upper FLEN - n bits are 1.
|
|
|
|
* If so, the least-significant bits of the input are used, otherwise the
|
|
|
|
* input value is treated as an n-bit canonical NaN (v2.2 section 9.2).
|
|
|
|
*
|
|
|
|
* Here, the result is always nan-boxed, even the canonical nan.
|
|
|
|
*/
|
|
|
|
static void gen_check_nanbox_s(TCGv_i64 out, TCGv_i64 in)
|
|
|
|
{
|
2021-08-23 21:55:06 +02:00
|
|
|
TCGv_i64 t_max = tcg_constant_i64(0xffffffff00000000ull);
|
|
|
|
TCGv_i64 t_nan = tcg_constant_i64(0xffffffff7fc00000ull);
|
2020-07-24 02:28:05 +02:00
|
|
|
|
|
|
|
tcg_gen_movcond_i64(TCG_COND_GEU, out, in, t_max, in, t_nan);
|
2019-01-15 00:58:42 +01:00
|
|
|
}
|
|
|
|
|
2018-03-02 13:31:11 +01:00
|
|
|
static void generate_exception(DisasContext *ctx, int excp)
|
|
|
|
{
|
2018-02-14 00:28:36 +01:00
|
|
|
tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);
|
2021-08-23 21:55:06 +02:00
|
|
|
gen_helper_raise_exception(cpu_env, tcg_constant_i32(excp));
|
2018-02-14 00:28:36 +01:00
|
|
|
ctx->base.is_jmp = DISAS_NORETURN;
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2021-03-19 20:45:29 +01:00
|
|
|
static void generate_exception_mtval(DisasContext *ctx, int excp)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
2018-02-14 00:28:36 +01:00
|
|
|
tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);
|
2018-03-02 13:31:11 +01:00
|
|
|
tcg_gen_st_tl(cpu_pc, cpu_env, offsetof(CPURISCVState, badaddr));
|
2021-08-23 21:55:06 +02:00
|
|
|
gen_helper_raise_exception(cpu_env, tcg_constant_i32(excp));
|
2018-02-14 00:28:36 +01:00
|
|
|
ctx->base.is_jmp = DISAS_NORETURN;
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_exception_debug(void)
|
|
|
|
{
|
2021-08-23 21:55:06 +02:00
|
|
|
gen_helper_raise_exception(cpu_env, tcg_constant_i32(EXCP_DEBUG));
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2019-03-25 12:45:54 +01:00
|
|
|
/* Wrapper around tcg_gen_exit_tb that handles single stepping */
|
|
|
|
static void exit_tb(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
if (ctx->base.singlestep_enabled) {
|
|
|
|
gen_exception_debug();
|
|
|
|
} else {
|
|
|
|
tcg_gen_exit_tb(NULL, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Wrapper around tcg_gen_lookup_and_goto_ptr that handles single stepping */
|
|
|
|
static void lookup_and_goto_ptr(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
if (ctx->base.singlestep_enabled) {
|
|
|
|
gen_exception_debug();
|
|
|
|
} else {
|
|
|
|
tcg_gen_lookup_and_goto_ptr();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-02 13:31:11 +01:00
|
|
|
static void gen_exception_illegal(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
generate_exception(ctx, RISCV_EXCP_ILLEGAL_INST);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_exception_inst_addr_mis(DisasContext *ctx)
|
|
|
|
{
|
2021-03-19 20:45:29 +01:00
|
|
|
generate_exception_mtval(ctx, RISCV_EXCP_INST_ADDR_MIS);
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_goto_tb(DisasContext *ctx, int n, target_ulong dest)
|
|
|
|
{
|
2021-06-21 01:34:21 +02:00
|
|
|
if (translator_use_goto_tb(&ctx->base, dest)) {
|
2018-03-02 13:31:11 +01:00
|
|
|
tcg_gen_goto_tb(n);
|
|
|
|
tcg_gen_movi_tl(cpu_pc, dest);
|
2018-05-31 03:06:23 +02:00
|
|
|
tcg_gen_exit_tb(ctx->base.tb, n);
|
2018-03-02 13:31:11 +01:00
|
|
|
} else {
|
|
|
|
tcg_gen_movi_tl(cpu_pc, dest);
|
2019-03-25 12:45:54 +01:00
|
|
|
lookup_and_goto_ptr(ctx);
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-23 21:55:10 +02:00
|
|
|
/*
|
|
|
|
* Wrappers for getting reg values.
|
|
|
|
*
|
|
|
|
* The $zero register does not have cpu_gpr[0] allocated -- we supply the
|
|
|
|
* constant zero as a source, and an uninitialized sink as destination.
|
|
|
|
*
|
|
|
|
* Further, we may provide an extension for word operations.
|
2018-03-02 13:31:11 +01:00
|
|
|
*/
|
2021-08-23 21:55:10 +02:00
|
|
|
static TCGv temp_new(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
assert(ctx->ntemp < ARRAY_SIZE(ctx->temp));
|
|
|
|
return ctx->temp[ctx->ntemp++] = tcg_temp_new();
|
|
|
|
}
|
|
|
|
|
|
|
|
static TCGv get_gpr(DisasContext *ctx, int reg_num, DisasExtend ext)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
2021-08-23 21:55:10 +02:00
|
|
|
TCGv t;
|
|
|
|
|
2018-03-02 13:31:11 +01:00
|
|
|
if (reg_num == 0) {
|
2021-08-23 21:55:10 +02:00
|
|
|
return ctx->zero;
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
2021-08-23 21:55:10 +02:00
|
|
|
|
|
|
|
switch (ctx->w ? ext : EXT_NONE) {
|
|
|
|
case EXT_NONE:
|
|
|
|
return cpu_gpr[reg_num];
|
|
|
|
case EXT_SIGN:
|
|
|
|
t = temp_new(ctx);
|
|
|
|
tcg_gen_ext32s_tl(t, cpu_gpr[reg_num]);
|
|
|
|
return t;
|
|
|
|
case EXT_ZERO:
|
|
|
|
t = temp_new(ctx);
|
|
|
|
tcg_gen_ext32u_tl(t, cpu_gpr[reg_num]);
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
g_assert_not_reached();
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2021-08-23 21:55:10 +02:00
|
|
|
static void gen_get_gpr(DisasContext *ctx, TCGv t, int reg_num)
|
|
|
|
{
|
|
|
|
tcg_gen_mov_tl(t, get_gpr(ctx, reg_num, EXT_NONE));
|
|
|
|
}
|
|
|
|
|
|
|
|
static TCGv __attribute__((unused)) dest_gpr(DisasContext *ctx, int reg_num)
|
|
|
|
{
|
|
|
|
if (reg_num == 0 || ctx->w) {
|
|
|
|
return temp_new(ctx);
|
|
|
|
}
|
|
|
|
return cpu_gpr[reg_num];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_set_gpr(DisasContext *ctx, int reg_num, TCGv t)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
2021-08-23 21:55:10 +02:00
|
|
|
if (reg_num != 0) {
|
|
|
|
if (ctx->w) {
|
|
|
|
tcg_gen_ext32s_tl(cpu_gpr[reg_num], t);
|
|
|
|
} else {
|
|
|
|
tcg_gen_mov_tl(cpu_gpr[reg_num], t);
|
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_mulhsu(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv rl = tcg_temp_new();
|
|
|
|
TCGv rh = tcg_temp_new();
|
|
|
|
|
|
|
|
tcg_gen_mulu2_tl(rl, rh, arg1, arg2);
|
|
|
|
/* fix up for one negative */
|
|
|
|
tcg_gen_sari_tl(rl, arg1, TARGET_LONG_BITS - 1);
|
|
|
|
tcg_gen_and_tl(rl, rl, arg2);
|
|
|
|
tcg_gen_sub_tl(ret, rh, rl);
|
|
|
|
|
|
|
|
tcg_temp_free(rl);
|
|
|
|
tcg_temp_free(rh);
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
static void gen_div(TCGv ret, TCGv source1, TCGv source2)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
2021-08-23 21:55:08 +02:00
|
|
|
TCGv temp1, temp2, zero, one, mone, min;
|
|
|
|
|
|
|
|
temp1 = tcg_temp_new();
|
|
|
|
temp2 = tcg_temp_new();
|
|
|
|
zero = tcg_constant_tl(0);
|
|
|
|
one = tcg_constant_tl(1);
|
|
|
|
mone = tcg_constant_tl(-1);
|
|
|
|
min = tcg_constant_tl(1ull << (TARGET_LONG_BITS - 1));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If overflow, set temp2 to 1, else source2.
|
|
|
|
* This produces the required result of min.
|
|
|
|
*/
|
|
|
|
tcg_gen_setcond_tl(TCG_COND_EQ, temp1, source1, min);
|
|
|
|
tcg_gen_setcond_tl(TCG_COND_EQ, temp2, source2, mone);
|
|
|
|
tcg_gen_and_tl(temp1, temp1, temp2);
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_NE, temp2, temp1, zero, one, source2);
|
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
/*
|
2021-08-23 21:55:08 +02:00
|
|
|
* If div by zero, set temp1 to -1 and temp2 to 1 to
|
|
|
|
* produce the required result of -1.
|
2019-02-13 16:54:06 +01:00
|
|
|
*/
|
2021-08-23 21:55:08 +02:00
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp1, source2, zero, mone, source1);
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp2, source2, zero, one, temp2);
|
|
|
|
|
|
|
|
tcg_gen_div_tl(ret, temp1, temp2);
|
|
|
|
|
|
|
|
tcg_temp_free(temp1);
|
|
|
|
tcg_temp_free(temp2);
|
2019-02-13 16:54:06 +01:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
static void gen_divu(TCGv ret, TCGv source1, TCGv source2)
|
|
|
|
{
|
2021-08-23 21:55:08 +02:00
|
|
|
TCGv temp1, temp2, zero, one, max;
|
|
|
|
|
|
|
|
temp1 = tcg_temp_new();
|
|
|
|
temp2 = tcg_temp_new();
|
|
|
|
zero = tcg_constant_tl(0);
|
|
|
|
one = tcg_constant_tl(1);
|
|
|
|
max = tcg_constant_tl(~0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If div by zero, set temp1 to max and temp2 to 1 to
|
|
|
|
* produce the required result of max.
|
|
|
|
*/
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp1, source2, zero, max, source1);
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp2, source2, zero, one, source2);
|
|
|
|
tcg_gen_divu_tl(ret, temp1, temp2);
|
|
|
|
|
|
|
|
tcg_temp_free(temp1);
|
|
|
|
tcg_temp_free(temp2);
|
2019-02-13 16:54:06 +01:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
static void gen_rem(TCGv ret, TCGv source1, TCGv source2)
|
|
|
|
{
|
2021-08-23 21:55:08 +02:00
|
|
|
TCGv temp1, temp2, zero, one, mone, min;
|
|
|
|
|
|
|
|
temp1 = tcg_temp_new();
|
|
|
|
temp2 = tcg_temp_new();
|
|
|
|
zero = tcg_constant_tl(0);
|
|
|
|
one = tcg_constant_tl(1);
|
|
|
|
mone = tcg_constant_tl(-1);
|
|
|
|
min = tcg_constant_tl(1ull << (TARGET_LONG_BITS - 1));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If overflow, set temp1 to 0, else source1.
|
|
|
|
* This avoids a possible host trap, and produces the required result of 0.
|
|
|
|
*/
|
|
|
|
tcg_gen_setcond_tl(TCG_COND_EQ, temp1, source1, min);
|
|
|
|
tcg_gen_setcond_tl(TCG_COND_EQ, temp2, source2, mone);
|
|
|
|
tcg_gen_and_tl(temp1, temp1, temp2);
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_NE, temp1, temp1, zero, zero, source1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If div by zero, set temp2 to 1, else source2.
|
|
|
|
* This avoids a possible host trap, but produces an incorrect result.
|
|
|
|
*/
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp2, source2, zero, one, source2);
|
|
|
|
|
|
|
|
tcg_gen_rem_tl(temp1, temp1, temp2);
|
|
|
|
|
|
|
|
/* If div by zero, the required result is the original dividend. */
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, ret, source2, zero, source1, temp1);
|
|
|
|
|
|
|
|
tcg_temp_free(temp1);
|
|
|
|
tcg_temp_free(temp2);
|
2019-02-13 16:54:06 +01:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
static void gen_remu(TCGv ret, TCGv source1, TCGv source2)
|
|
|
|
{
|
2021-08-23 21:55:08 +02:00
|
|
|
TCGv temp, zero, one;
|
|
|
|
|
|
|
|
temp = tcg_temp_new();
|
|
|
|
zero = tcg_constant_tl(0);
|
|
|
|
one = tcg_constant_tl(1);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If div by zero, set temp to 1, else source2.
|
|
|
|
* This avoids a possible host trap, but produces an incorrect result.
|
|
|
|
*/
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, temp, source2, zero, one, source2);
|
|
|
|
|
|
|
|
tcg_gen_remu_tl(temp, source1, temp);
|
|
|
|
|
|
|
|
/* If div by zero, the required result is the original dividend. */
|
|
|
|
tcg_gen_movcond_tl(TCG_COND_EQ, ret, source2, zero, source1, temp);
|
|
|
|
|
|
|
|
tcg_temp_free(temp);
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 00:58:42 +01:00
|
|
|
static void gen_jal(DisasContext *ctx, int rd, target_ulong imm)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
|
|
|
target_ulong next_pc;
|
|
|
|
|
|
|
|
/* check misaligned: */
|
2018-02-14 00:28:36 +01:00
|
|
|
next_pc = ctx->base.pc_next + imm;
|
2019-01-15 00:58:42 +01:00
|
|
|
if (!has_ext(ctx, RVC)) {
|
2018-03-02 13:31:11 +01:00
|
|
|
if ((next_pc & 0x3) != 0) {
|
|
|
|
gen_exception_inst_addr_mis(ctx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rd != 0) {
|
2018-02-14 00:28:36 +01:00
|
|
|
tcg_gen_movi_tl(cpu_gpr[rd], ctx->pc_succ_insn);
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2018-02-14 00:28:36 +01:00
|
|
|
gen_goto_tb(ctx, 0, ctx->base.pc_next + imm); /* must use this for safety */
|
|
|
|
ctx->base.is_jmp = DISAS_NORETURN;
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2019-01-15 00:57:59 +01:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
/* The states of mstatus_fs are:
|
|
|
|
* 0 = disabled, 1 = initial, 2 = clean, 3 = dirty
|
|
|
|
* We will have already diagnosed disabled state,
|
|
|
|
* and need to turn initial/clean into dirty.
|
|
|
|
*/
|
|
|
|
static void mark_fs_dirty(DisasContext *ctx)
|
|
|
|
{
|
|
|
|
TCGv tmp;
|
2021-04-24 05:33:18 +02:00
|
|
|
target_ulong sd;
|
|
|
|
|
2019-01-15 00:57:59 +01:00
|
|
|
if (ctx->mstatus_fs == MSTATUS_FS) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* Remember the state change for the rest of the TB. */
|
|
|
|
ctx->mstatus_fs = MSTATUS_FS;
|
|
|
|
|
|
|
|
tmp = tcg_temp_new();
|
2021-04-24 05:33:18 +02:00
|
|
|
sd = is_32bit(ctx) ? MSTATUS32_SD : MSTATUS64_SD;
|
|
|
|
|
2019-01-15 00:57:59 +01:00
|
|
|
tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus));
|
2021-04-24 05:33:18 +02:00
|
|
|
tcg_gen_ori_tl(tmp, tmp, MSTATUS_FS | sd);
|
2019-01-15 00:57:59 +01:00
|
|
|
tcg_gen_st_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus));
|
2020-02-01 02:02:46 +01:00
|
|
|
|
|
|
|
if (ctx->virt_enabled) {
|
|
|
|
tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus_hs));
|
2021-04-24 05:33:18 +02:00
|
|
|
tcg_gen_ori_tl(tmp, tmp, MSTATUS_FS | sd);
|
2020-02-01 02:02:46 +01:00
|
|
|
tcg_gen_st_tl(tmp, cpu_env, offsetof(CPURISCVState, mstatus_hs));
|
|
|
|
}
|
2019-01-15 00:57:59 +01:00
|
|
|
tcg_temp_free(tmp);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void mark_fs_dirty(DisasContext *ctx) { }
|
|
|
|
#endif
|
|
|
|
|
2018-03-02 13:31:11 +01:00
|
|
|
static void gen_set_rm(DisasContext *ctx, int rm)
|
|
|
|
{
|
|
|
|
if (ctx->frm == rm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ctx->frm = rm;
|
2021-08-23 21:55:06 +02:00
|
|
|
gen_helper_set_rounding_mode(cpu_env, tcg_constant_i32(rm));
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
2020-07-01 17:24:54 +02:00
|
|
|
static int ex_plus_1(DisasContext *ctx, int nf)
|
|
|
|
{
|
|
|
|
return nf + 1;
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:53:41 +01:00
|
|
|
#define EX_SH(amount) \
|
2019-03-21 03:21:31 +01:00
|
|
|
static int ex_shift_##amount(DisasContext *ctx, int imm) \
|
2019-02-13 16:53:41 +01:00
|
|
|
{ \
|
|
|
|
return imm << amount; \
|
|
|
|
}
|
2019-02-13 16:53:42 +01:00
|
|
|
EX_SH(1)
|
2019-02-13 16:53:56 +01:00
|
|
|
EX_SH(2)
|
|
|
|
EX_SH(3)
|
2019-02-13 16:53:57 +01:00
|
|
|
EX_SH(4)
|
2019-02-13 16:53:41 +01:00
|
|
|
EX_SH(12)
|
|
|
|
|
2019-02-13 16:53:48 +01:00
|
|
|
#define REQUIRE_EXT(ctx, ext) do { \
|
|
|
|
if (!has_ext(ctx, ext)) { \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2021-04-24 05:34:12 +02:00
|
|
|
#define REQUIRE_64BIT(ctx) do { \
|
|
|
|
if (is_32bit(ctx)) { \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
2019-03-21 03:21:31 +01:00
|
|
|
static int ex_rvc_register(DisasContext *ctx, int reg)
|
2019-02-13 16:53:56 +01:00
|
|
|
{
|
|
|
|
return 8 + reg;
|
|
|
|
}
|
|
|
|
|
2019-04-01 05:11:51 +02:00
|
|
|
static int ex_rvc_shifti(DisasContext *ctx, int imm)
|
|
|
|
{
|
|
|
|
/* For RV128 a shamt of 0 means a shift by 64. */
|
|
|
|
return imm ? imm : 64;
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:53:41 +01:00
|
|
|
/* Include the auto-generated decoder for 32 bit insn */
|
2020-08-07 12:10:23 +02:00
|
|
|
#include "decode-insn32.c.inc"
|
2019-02-13 16:54:03 +01:00
|
|
|
|
2019-04-01 05:11:54 +02:00
|
|
|
static bool gen_arith_imm_fn(DisasContext *ctx, arg_i *a,
|
|
|
|
void (*func)(TCGv, TCGv, target_long))
|
|
|
|
{
|
|
|
|
TCGv source1;
|
|
|
|
source1 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
2019-04-01 05:11:54 +02:00
|
|
|
|
|
|
|
(*func)(source1, source1, a->imm);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2019-04-01 05:11:54 +02:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool gen_arith_imm_tl(DisasContext *ctx, arg_i *a,
|
|
|
|
void (*func)(TCGv, TCGv, TCGv))
|
2019-02-13 16:54:03 +01:00
|
|
|
{
|
|
|
|
TCGv source1, source2;
|
|
|
|
source1 = tcg_temp_new();
|
|
|
|
source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
2019-02-13 16:54:03 +01:00
|
|
|
tcg_gen_movi_tl(source2, a->imm);
|
|
|
|
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2019-02-13 16:54:03 +01:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_addw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_add_tl(ret, arg1, arg2);
|
|
|
|
tcg_gen_ext32s_tl(ret, ret);
|
|
|
|
}
|
2019-02-13 16:54:04 +01:00
|
|
|
|
|
|
|
static void gen_subw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_sub_tl(ret, arg1, arg2);
|
|
|
|
tcg_gen_ext32s_tl(ret, ret);
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:54:06 +01:00
|
|
|
static void gen_mulw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_mul_tl(ret, arg1, arg2);
|
|
|
|
tcg_gen_ext32s_tl(ret, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool gen_arith_div_w(DisasContext *ctx, arg_r *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source1, source2;
|
|
|
|
source1 = tcg_temp_new();
|
|
|
|
source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
|
|
|
gen_get_gpr(ctx, source2, a->rs2);
|
2019-02-13 16:54:06 +01:00
|
|
|
tcg_gen_ext32s_tl(source1, source1);
|
|
|
|
tcg_gen_ext32s_tl(source2, source2);
|
|
|
|
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
|
|
|
tcg_gen_ext32s_tl(source1, source1);
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2019-02-13 16:54:06 +01:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
target/riscv: Zero extend the inputs of divuw and remuw
While running the GCC test suite against 4.0.0-rc0, Kito found a
regression introduced by the decodetree conversion that caused divuw and
remuw to sign-extend their inputs. The ISA manual says they are
supposed to be zero extended:
DIVW and DIVUW instructions are only valid for RV64, and divide the
lower 32 bits of rs1 by the lower 32 bits of rs2, treating them as
signed and unsigned integers respectively, placing the 32-bit
quotient in rd, sign-extended to 64 bits. REMW and REMUW
instructions are only valid for RV64, and provide the corresponding
signed and unsigned remainder operations respectively. Both REMW
and REMUW always sign-extend the 32-bit result to 64 bits, including
on a divide by zero.
Here's Kito's reduced test case from the GCC test suite
unsigned calc_mp(unsigned mod)
{
unsigned a,b,c;
c=-1;
a=c/mod;
b=0-a*mod;
if (b > mod) { a += 1; b-=mod; }
return b;
}
int main(int argc, char *argv[])
{
unsigned x = 1234;
unsigned y = calc_mp(x);
if ((sizeof (y) == 4 && y != 680)
|| (sizeof (y) == 2 && y != 134))
abort ();
exit (0);
}
I haven't done any other testing on this, but it does fix the test case.
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
2019-03-21 15:59:20 +01:00
|
|
|
static bool gen_arith_div_uw(DisasContext *ctx, arg_r *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source1, source2;
|
|
|
|
source1 = tcg_temp_new();
|
|
|
|
source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
|
|
|
gen_get_gpr(ctx, source2, a->rs2);
|
target/riscv: Zero extend the inputs of divuw and remuw
While running the GCC test suite against 4.0.0-rc0, Kito found a
regression introduced by the decodetree conversion that caused divuw and
remuw to sign-extend their inputs. The ISA manual says they are
supposed to be zero extended:
DIVW and DIVUW instructions are only valid for RV64, and divide the
lower 32 bits of rs1 by the lower 32 bits of rs2, treating them as
signed and unsigned integers respectively, placing the 32-bit
quotient in rd, sign-extended to 64 bits. REMW and REMUW
instructions are only valid for RV64, and provide the corresponding
signed and unsigned remainder operations respectively. Both REMW
and REMUW always sign-extend the 32-bit result to 64 bits, including
on a divide by zero.
Here's Kito's reduced test case from the GCC test suite
unsigned calc_mp(unsigned mod)
{
unsigned a,b,c;
c=-1;
a=c/mod;
b=0-a*mod;
if (b > mod) { a += 1; b-=mod; }
return b;
}
int main(int argc, char *argv[])
{
unsigned x = 1234;
unsigned y = calc_mp(x);
if ((sizeof (y) == 4 && y != 680)
|| (sizeof (y) == 2 && y != 134))
abort ();
exit (0);
}
I haven't done any other testing on this, but it does fix the test case.
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
2019-03-21 15:59:20 +01:00
|
|
|
tcg_gen_ext32u_tl(source1, source1);
|
|
|
|
tcg_gen_ext32u_tl(source2, source2);
|
|
|
|
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
|
|
|
tcg_gen_ext32s_tl(source1, source1);
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
target/riscv: Zero extend the inputs of divuw and remuw
While running the GCC test suite against 4.0.0-rc0, Kito found a
regression introduced by the decodetree conversion that caused divuw and
remuw to sign-extend their inputs. The ISA manual says they are
supposed to be zero extended:
DIVW and DIVUW instructions are only valid for RV64, and divide the
lower 32 bits of rs1 by the lower 32 bits of rs2, treating them as
signed and unsigned integers respectively, placing the 32-bit
quotient in rd, sign-extended to 64 bits. REMW and REMUW
instructions are only valid for RV64, and provide the corresponding
signed and unsigned remainder operations respectively. Both REMW
and REMUW always sign-extend the 32-bit result to 64 bits, including
on a divide by zero.
Here's Kito's reduced test case from the GCC test suite
unsigned calc_mp(unsigned mod)
{
unsigned a,b,c;
c=-1;
a=c/mod;
b=0-a*mod;
if (b > mod) { a += 1; b-=mod; }
return b;
}
int main(int argc, char *argv[])
{
unsigned x = 1234;
unsigned y = calc_mp(x);
if ((sizeof (y) == 4 && y != 680)
|| (sizeof (y) == 2 && y != 134))
abort ();
exit (0);
}
I haven't done any other testing on this, but it does fix the test case.
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>
2019-03-21 15:59:20 +01:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:06 +02:00
|
|
|
static void gen_pack(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_deposit_tl(ret, arg1, arg2,
|
|
|
|
TARGET_LONG_BITS / 2,
|
|
|
|
TARGET_LONG_BITS / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_packu(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
tcg_gen_shri_tl(t, arg1, TARGET_LONG_BITS / 2);
|
|
|
|
tcg_gen_deposit_tl(ret, arg2, t, 0, TARGET_LONG_BITS / 2);
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_packh(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
tcg_gen_ext8u_tl(t, arg2);
|
|
|
|
tcg_gen_deposit_tl(ret, arg1, t, 8, TARGET_LONG_BITS - 8);
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:10 +02:00
|
|
|
static void gen_sbop_mask(TCGv ret, TCGv shamt)
|
|
|
|
{
|
|
|
|
tcg_gen_movi_tl(ret, 1);
|
|
|
|
tcg_gen_shl_tl(ret, ret, shamt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_bset(TCGv ret, TCGv arg1, TCGv shamt)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
|
|
|
|
gen_sbop_mask(t, shamt);
|
|
|
|
tcg_gen_or_tl(ret, arg1, t);
|
|
|
|
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_bclr(TCGv ret, TCGv arg1, TCGv shamt)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
|
|
|
|
gen_sbop_mask(t, shamt);
|
|
|
|
tcg_gen_andc_tl(ret, arg1, t);
|
|
|
|
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_binv(TCGv ret, TCGv arg1, TCGv shamt)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
|
|
|
|
gen_sbop_mask(t, shamt);
|
|
|
|
tcg_gen_xor_tl(ret, arg1, t);
|
|
|
|
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_bext(TCGv ret, TCGv arg1, TCGv shamt)
|
|
|
|
{
|
|
|
|
tcg_gen_shr_tl(ret, arg1, shamt);
|
|
|
|
tcg_gen_andi_tl(ret, ret, 1);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:11 +02:00
|
|
|
static void gen_slo(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_not_tl(ret, arg1);
|
|
|
|
tcg_gen_shl_tl(ret, ret, arg2);
|
|
|
|
tcg_gen_not_tl(ret, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_sro(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_not_tl(ret, arg1);
|
|
|
|
tcg_gen_shr_tl(ret, ret, arg2);
|
|
|
|
tcg_gen_not_tl(ret, ret);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:13 +02:00
|
|
|
static bool gen_grevi(DisasContext *ctx, arg_grevi *a)
|
|
|
|
{
|
|
|
|
TCGv source1 = tcg_temp_new();
|
|
|
|
TCGv source2;
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
2021-05-05 18:06:13 +02:00
|
|
|
|
|
|
|
if (a->shamt == (TARGET_LONG_BITS - 8)) {
|
|
|
|
/* rev8, byte swaps */
|
|
|
|
tcg_gen_bswap_tl(source1, source1);
|
|
|
|
} else {
|
|
|
|
source2 = tcg_temp_new();
|
|
|
|
tcg_gen_movi_tl(source2, a->shamt);
|
|
|
|
gen_helper_grev(source1, source1, source2);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
}
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2021-05-05 18:06:13 +02:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:15 +02:00
|
|
|
#define GEN_SHADD(SHAMT) \
|
|
|
|
static void gen_sh##SHAMT##add(TCGv ret, TCGv arg1, TCGv arg2) \
|
|
|
|
{ \
|
|
|
|
TCGv t = tcg_temp_new(); \
|
|
|
|
\
|
|
|
|
tcg_gen_shli_tl(t, arg1, SHAMT); \
|
|
|
|
tcg_gen_add_tl(ret, t, arg2); \
|
|
|
|
\
|
|
|
|
tcg_temp_free(t); \
|
|
|
|
}
|
|
|
|
|
|
|
|
GEN_SHADD(1)
|
|
|
|
GEN_SHADD(2)
|
|
|
|
GEN_SHADD(3)
|
|
|
|
|
2021-05-05 18:06:03 +02:00
|
|
|
static void gen_ctzw(TCGv ret, TCGv arg1)
|
|
|
|
{
|
|
|
|
tcg_gen_ori_tl(ret, arg1, (target_ulong)MAKE_64BIT_MASK(32, 32));
|
|
|
|
tcg_gen_ctzi_tl(ret, ret, 64);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_clzw(TCGv ret, TCGv arg1)
|
|
|
|
{
|
|
|
|
tcg_gen_ext32u_tl(ret, arg1);
|
|
|
|
tcg_gen_clzi_tl(ret, ret, 64);
|
|
|
|
tcg_gen_subi_tl(ret, ret, 32);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:04 +02:00
|
|
|
static void gen_cpopw(TCGv ret, TCGv arg1)
|
|
|
|
{
|
|
|
|
tcg_gen_ext32u_tl(arg1, arg1);
|
|
|
|
tcg_gen_ctpop_tl(ret, arg1);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:06 +02:00
|
|
|
static void gen_packw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
tcg_gen_ext16s_tl(t, arg2);
|
|
|
|
tcg_gen_deposit_tl(ret, arg1, t, 16, 48);
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_packuw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv t = tcg_temp_new();
|
|
|
|
tcg_gen_shri_tl(t, arg1, 16);
|
|
|
|
tcg_gen_deposit_tl(ret, arg2, t, 0, 16);
|
|
|
|
tcg_gen_ext32s_tl(ret, ret);
|
|
|
|
tcg_temp_free(t);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:12 +02:00
|
|
|
static void gen_rorw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv_i32 t1 = tcg_temp_new_i32();
|
|
|
|
TCGv_i32 t2 = tcg_temp_new_i32();
|
|
|
|
|
|
|
|
/* truncate to 32-bits */
|
|
|
|
tcg_gen_trunc_tl_i32(t1, arg1);
|
|
|
|
tcg_gen_trunc_tl_i32(t2, arg2);
|
|
|
|
|
|
|
|
tcg_gen_rotr_i32(t1, t1, t2);
|
|
|
|
|
|
|
|
/* sign-extend 64-bits */
|
|
|
|
tcg_gen_ext_i32_tl(ret, t1);
|
|
|
|
|
|
|
|
tcg_temp_free_i32(t1);
|
|
|
|
tcg_temp_free_i32(t2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_rolw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
TCGv_i32 t1 = tcg_temp_new_i32();
|
|
|
|
TCGv_i32 t2 = tcg_temp_new_i32();
|
|
|
|
|
|
|
|
/* truncate to 32-bits */
|
|
|
|
tcg_gen_trunc_tl_i32(t1, arg1);
|
|
|
|
tcg_gen_trunc_tl_i32(t2, arg2);
|
|
|
|
|
|
|
|
tcg_gen_rotl_i32(t1, t1, t2);
|
|
|
|
|
|
|
|
/* sign-extend 64-bits */
|
|
|
|
tcg_gen_ext_i32_tl(ret, t1);
|
|
|
|
|
|
|
|
tcg_temp_free_i32(t1);
|
|
|
|
tcg_temp_free_i32(t2);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:13 +02:00
|
|
|
static void gen_grevw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_ext32u_tl(arg1, arg1);
|
|
|
|
gen_helper_grev(ret, arg1, arg2);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:14 +02:00
|
|
|
static void gen_gorcw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_ext32u_tl(arg1, arg1);
|
|
|
|
gen_helper_gorcw(ret, arg1, arg2);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:15 +02:00
|
|
|
#define GEN_SHADD_UW(SHAMT) \
|
|
|
|
static void gen_sh##SHAMT##add_uw(TCGv ret, TCGv arg1, TCGv arg2) \
|
|
|
|
{ \
|
|
|
|
TCGv t = tcg_temp_new(); \
|
|
|
|
\
|
|
|
|
tcg_gen_ext32u_tl(t, arg1); \
|
|
|
|
\
|
|
|
|
tcg_gen_shli_tl(t, t, SHAMT); \
|
|
|
|
tcg_gen_add_tl(ret, t, arg2); \
|
|
|
|
\
|
|
|
|
tcg_temp_free(t); \
|
|
|
|
}
|
|
|
|
|
|
|
|
GEN_SHADD_UW(1)
|
|
|
|
GEN_SHADD_UW(2)
|
|
|
|
GEN_SHADD_UW(3)
|
|
|
|
|
2021-05-05 18:06:16 +02:00
|
|
|
static void gen_add_uw(TCGv ret, TCGv arg1, TCGv arg2)
|
|
|
|
{
|
|
|
|
tcg_gen_ext32u_tl(arg1, arg1);
|
|
|
|
tcg_gen_add_tl(ret, arg1, arg2);
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:54:07 +01:00
|
|
|
static bool gen_arith(DisasContext *ctx, arg_r *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
2019-02-13 16:54:04 +01:00
|
|
|
{
|
|
|
|
TCGv source1, source2;
|
|
|
|
source1 = tcg_temp_new();
|
|
|
|
source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
|
|
|
gen_get_gpr(ctx, source2, a->rs2);
|
2019-02-13 16:54:04 +01:00
|
|
|
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2019-02-13 16:54:04 +01:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:54:05 +01:00
|
|
|
static bool gen_shift(DisasContext *ctx, arg_r *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source1 = tcg_temp_new();
|
|
|
|
TCGv source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
|
|
|
gen_get_gpr(ctx, source2, a->rs2);
|
2019-02-13 16:54:05 +01:00
|
|
|
|
|
|
|
tcg_gen_andi_tl(source2, source2, TARGET_LONG_BITS - 1);
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2019-02-13 16:54:05 +01:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-08 23:42:52 +01:00
|
|
|
static uint32_t opcode_at(DisasContextBase *dcbase, target_ulong pc)
|
|
|
|
{
|
|
|
|
DisasContext *ctx = container_of(dcbase, DisasContext, base);
|
|
|
|
CPUState *cpu = ctx->cs;
|
|
|
|
CPURISCVState *env = cpu->env_ptr;
|
|
|
|
|
|
|
|
return cpu_ldl_code(env, pc);
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:09 +02:00
|
|
|
static bool gen_shifti(DisasContext *ctx, arg_shift *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
if (a->shamt >= TARGET_LONG_BITS) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
TCGv source1 = tcg_temp_new();
|
|
|
|
TCGv source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
2021-05-05 18:06:09 +02:00
|
|
|
|
|
|
|
tcg_gen_movi_tl(source2, a->shamt);
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2021-05-05 18:06:09 +02:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:10 +02:00
|
|
|
static bool gen_shiftw(DisasContext *ctx, arg_r *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source1 = tcg_temp_new();
|
|
|
|
TCGv source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
|
|
|
gen_get_gpr(ctx, source2, a->rs2);
|
2021-05-05 18:06:10 +02:00
|
|
|
|
|
|
|
tcg_gen_andi_tl(source2, source2, 31);
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
tcg_gen_ext32s_tl(source1, source1);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2021-05-05 18:06:10 +02:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:09 +02:00
|
|
|
static bool gen_shiftiw(DisasContext *ctx, arg_shift *a,
|
|
|
|
void(*func)(TCGv, TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source1 = tcg_temp_new();
|
|
|
|
TCGv source2 = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source1, a->rs1);
|
2021-05-05 18:06:09 +02:00
|
|
|
tcg_gen_movi_tl(source2, a->shamt);
|
|
|
|
|
|
|
|
(*func)(source1, source1, source2);
|
|
|
|
tcg_gen_ext32s_tl(source1, source1);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source1);
|
2021-05-05 18:06:09 +02:00
|
|
|
tcg_temp_free(source1);
|
|
|
|
tcg_temp_free(source2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-05 18:06:03 +02:00
|
|
|
static void gen_ctz(TCGv ret, TCGv arg1)
|
|
|
|
{
|
|
|
|
tcg_gen_ctzi_tl(ret, arg1, TARGET_LONG_BITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void gen_clz(TCGv ret, TCGv arg1)
|
|
|
|
{
|
|
|
|
tcg_gen_clzi_tl(ret, arg1, TARGET_LONG_BITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool gen_unary(DisasContext *ctx, arg_r2 *a,
|
|
|
|
void(*func)(TCGv, TCGv))
|
|
|
|
{
|
|
|
|
TCGv source = tcg_temp_new();
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_get_gpr(ctx, source, a->rs1);
|
2021-05-05 18:06:03 +02:00
|
|
|
|
|
|
|
(*func)(source, source);
|
|
|
|
|
2021-08-23 21:55:09 +02:00
|
|
|
gen_set_gpr(ctx, a->rd, source);
|
2021-05-05 18:06:03 +02:00
|
|
|
tcg_temp_free(source);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-13 16:53:41 +01:00
|
|
|
/* Include insn module translation function */
|
2020-02-04 12:41:01 +01:00
|
|
|
#include "insn_trans/trans_rvi.c.inc"
|
|
|
|
#include "insn_trans/trans_rvm.c.inc"
|
|
|
|
#include "insn_trans/trans_rva.c.inc"
|
|
|
|
#include "insn_trans/trans_rvf.c.inc"
|
|
|
|
#include "insn_trans/trans_rvd.c.inc"
|
|
|
|
#include "insn_trans/trans_rvh.c.inc"
|
|
|
|
#include "insn_trans/trans_rvv.c.inc"
|
2021-05-05 18:06:03 +02:00
|
|
|
#include "insn_trans/trans_rvb.c.inc"
|
2020-02-04 12:41:01 +01:00
|
|
|
#include "insn_trans/trans_privileged.c.inc"
|
2019-02-13 16:53:41 +01:00
|
|
|
|
2019-08-09 17:24:57 +02:00
|
|
|
/* Include the auto-generated decoder for 16 bit insn */
|
2020-08-07 12:10:23 +02:00
|
|
|
#include "decode-insn16.c.inc"
|
2019-02-13 16:53:56 +01:00
|
|
|
|
2020-02-25 13:47:05 +01:00
|
|
|
static void decode_opc(CPURISCVState *env, DisasContext *ctx, uint16_t opcode)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
|
|
|
/* check for compressed insn */
|
2020-02-25 13:47:05 +01:00
|
|
|
if (extract16(opcode, 0, 2) != 3) {
|
2019-01-15 00:58:42 +01:00
|
|
|
if (!has_ext(ctx, RVC)) {
|
2018-03-02 13:31:11 +01:00
|
|
|
gen_exception_illegal(ctx);
|
|
|
|
} else {
|
2018-02-14 00:28:36 +01:00
|
|
|
ctx->pc_succ_insn = ctx->base.pc_next + 2;
|
2020-02-25 13:47:05 +01:00
|
|
|
if (!decode_insn16(ctx, opcode)) {
|
2021-03-22 13:16:09 +01:00
|
|
|
gen_exception_illegal(ctx);
|
2019-02-13 16:53:56 +01:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
} else {
|
2020-02-25 13:47:05 +01:00
|
|
|
uint32_t opcode32 = opcode;
|
|
|
|
opcode32 = deposit32(opcode32, 16, 16,
|
|
|
|
translator_lduw(env, ctx->base.pc_next + 2));
|
2018-02-14 00:28:36 +01:00
|
|
|
ctx->pc_succ_insn = ctx->base.pc_next + 4;
|
2020-02-25 13:47:05 +01:00
|
|
|
if (!decode_insn32(ctx, opcode32)) {
|
2019-02-13 16:54:09 +01:00
|
|
|
gen_exception_illegal(ctx);
|
2019-02-13 16:53:41 +01:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-06 19:42:27 +02:00
|
|
|
static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
|
2018-03-02 13:31:11 +01:00
|
|
|
{
|
2018-04-06 19:42:27 +02:00
|
|
|
DisasContext *ctx = container_of(dcbase, DisasContext, base);
|
2019-01-15 00:58:32 +01:00
|
|
|
CPURISCVState *env = cs->env_ptr;
|
2019-06-24 10:59:05 +02:00
|
|
|
RISCVCPU *cpu = RISCV_CPU(cs);
|
2020-07-01 17:24:52 +02:00
|
|
|
uint32_t tb_flags = ctx->base.tb->flags;
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2018-04-06 19:42:27 +02:00
|
|
|
ctx->pc_succ_insn = ctx->base.pc_first;
|
2020-07-01 17:24:52 +02:00
|
|
|
ctx->mem_idx = tb_flags & TB_FLAGS_MMU_MASK;
|
|
|
|
ctx->mstatus_fs = tb_flags & TB_FLAGS_MSTATUS_FS;
|
2019-01-15 00:58:32 +01:00
|
|
|
ctx->priv_ver = env->priv_ver;
|
2020-02-01 02:02:46 +01:00
|
|
|
#if !defined(CONFIG_USER_ONLY)
|
2020-02-01 02:02:49 +01:00
|
|
|
if (riscv_has_ext(env, RVH)) {
|
|
|
|
ctx->virt_enabled = riscv_cpu_virt_enabled(env);
|
|
|
|
} else {
|
|
|
|
ctx->virt_enabled = false;
|
|
|
|
}
|
2020-02-01 02:02:46 +01:00
|
|
|
#else
|
|
|
|
ctx->virt_enabled = false;
|
|
|
|
#endif
|
2019-01-15 00:58:42 +01:00
|
|
|
ctx->misa = env->misa;
|
2018-04-06 19:42:27 +02:00
|
|
|
ctx->frm = -1; /* unknown rounding mode */
|
2019-06-24 10:59:05 +02:00
|
|
|
ctx->ext_ifencei = cpu->cfg.ext_ifencei;
|
2020-07-01 17:24:52 +02:00
|
|
|
ctx->vlen = cpu->cfg.vlen;
|
2020-11-04 05:43:31 +01:00
|
|
|
ctx->hlsx = FIELD_EX32(tb_flags, TB_FLAGS, HLSX);
|
2020-07-01 17:24:52 +02:00
|
|
|
ctx->vill = FIELD_EX32(tb_flags, TB_FLAGS, VILL);
|
|
|
|
ctx->sew = FIELD_EX32(tb_flags, TB_FLAGS, SEW);
|
|
|
|
ctx->lmul = FIELD_EX32(tb_flags, TB_FLAGS, LMUL);
|
2020-07-01 17:24:54 +02:00
|
|
|
ctx->mlen = 1 << (ctx->sew + 3 - ctx->lmul);
|
2020-07-01 17:24:52 +02:00
|
|
|
ctx->vl_eq_vlmax = FIELD_EX32(tb_flags, TB_FLAGS, VL_EQ_VLMAX);
|
2021-01-08 23:42:52 +01:00
|
|
|
ctx->cs = cs;
|
2021-08-23 21:55:10 +02:00
|
|
|
ctx->w = false;
|
|
|
|
ctx->ntemp = 0;
|
|
|
|
memset(ctx->temp, 0, sizeof(ctx->temp));
|
|
|
|
|
|
|
|
ctx->zero = tcg_constant_tl(0);
|
2018-04-06 19:42:27 +02:00
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2018-04-06 19:42:27 +02:00
|
|
|
static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)
|
|
|
|
{
|
|
|
|
}
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2018-04-06 19:42:27 +02:00
|
|
|
static void riscv_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu)
|
|
|
|
{
|
|
|
|
DisasContext *ctx = container_of(dcbase, DisasContext, base);
|
|
|
|
|
|
|
|
tcg_gen_insn_start(ctx->base.pc_next);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
|
|
|
|
{
|
|
|
|
DisasContext *ctx = container_of(dcbase, DisasContext, base);
|
|
|
|
CPURISCVState *env = cpu->env_ptr;
|
2020-02-25 13:47:05 +01:00
|
|
|
uint16_t opcode16 = translator_lduw(env, ctx->base.pc_next);
|
2018-03-02 13:31:11 +01:00
|
|
|
|
2020-02-25 13:47:05 +01:00
|
|
|
decode_opc(env, ctx, opcode16);
|
2018-04-06 19:42:27 +02:00
|
|
|
ctx->base.pc_next = ctx->pc_succ_insn;
|
2021-08-23 21:55:10 +02:00
|
|
|
ctx->w = false;
|
|
|
|
|
|
|
|
for (int i = ctx->ntemp - 1; i >= 0; --i) {
|
|
|
|
tcg_temp_free(ctx->temp[i]);
|
|
|
|
ctx->temp[i] = NULL;
|
|
|
|
}
|
|
|
|
ctx->ntemp = 0;
|
2018-04-06 19:42:27 +02:00
|
|
|
|
|
|
|
if (ctx->base.is_jmp == DISAS_NEXT) {
|
|
|
|
target_ulong page_start;
|
|
|
|
|
|
|
|
page_start = ctx->base.pc_first & TARGET_PAGE_MASK;
|
|
|
|
if (ctx->base.pc_next - page_start >= TARGET_PAGE_SIZE) {
|
|
|
|
ctx->base.is_jmp = DISAS_TOO_MANY;
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
}
|
2018-04-06 19:42:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void riscv_tr_tb_stop(DisasContextBase *dcbase, CPUState *cpu)
|
|
|
|
{
|
|
|
|
DisasContext *ctx = container_of(dcbase, DisasContext, base);
|
|
|
|
|
|
|
|
switch (ctx->base.is_jmp) {
|
2018-02-14 00:27:54 +01:00
|
|
|
case DISAS_TOO_MANY:
|
2018-07-29 04:14:34 +02:00
|
|
|
gen_goto_tb(ctx, 0, ctx->base.pc_next);
|
2018-03-02 13:31:11 +01:00
|
|
|
break;
|
2018-02-14 00:27:54 +01:00
|
|
|
case DISAS_NORETURN:
|
2018-03-02 13:31:11 +01:00
|
|
|
break;
|
2018-02-14 00:27:54 +01:00
|
|
|
default:
|
|
|
|
g_assert_not_reached();
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
2018-04-06 19:42:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void riscv_tr_disas_log(const DisasContextBase *dcbase, CPUState *cpu)
|
|
|
|
{
|
2020-02-01 02:01:59 +01:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
RISCVCPU *rvcpu = RISCV_CPU(cpu);
|
|
|
|
CPURISCVState *env = &rvcpu->env;
|
|
|
|
#endif
|
|
|
|
|
2018-04-06 19:42:27 +02:00
|
|
|
qemu_log("IN: %s\n", lookup_symbol(dcbase->pc_first));
|
2020-02-01 02:01:59 +01:00
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
|
|
qemu_log("Priv: "TARGET_FMT_ld"; Virt: "TARGET_FMT_ld"\n", env->priv, env->virt);
|
|
|
|
#endif
|
2018-04-06 19:42:27 +02:00
|
|
|
log_target_disas(cpu, dcbase->pc_first, dcbase->tb->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const TranslatorOps riscv_tr_ops = {
|
|
|
|
.init_disas_context = riscv_tr_init_disas_context,
|
|
|
|
.tb_start = riscv_tr_tb_start,
|
|
|
|
.insn_start = riscv_tr_insn_start,
|
|
|
|
.translate_insn = riscv_tr_translate_insn,
|
|
|
|
.tb_stop = riscv_tr_tb_stop,
|
|
|
|
.disas_log = riscv_tr_disas_log,
|
|
|
|
};
|
|
|
|
|
2019-04-16 08:54:54 +02:00
|
|
|
void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns)
|
2018-04-06 19:42:27 +02:00
|
|
|
{
|
|
|
|
DisasContext ctx;
|
|
|
|
|
2019-04-16 08:54:54 +02:00
|
|
|
translator_loop(&riscv_tr_ops, &ctx.base, cs, tb, max_insns);
|
2018-03-02 13:31:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void riscv_translate_init(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* cpu_gpr[0] is a placeholder for the zero register. Do not use it. */
|
|
|
|
/* Use the gen_set_gpr and gen_get_gpr helper functions when accessing */
|
|
|
|
/* registers, unless you specifically block reads/writes to reg 0 */
|
|
|
|
cpu_gpr[0] = NULL;
|
|
|
|
|
|
|
|
for (i = 1; i < 32; i++) {
|
|
|
|
cpu_gpr[i] = tcg_global_mem_new(cpu_env,
|
|
|
|
offsetof(CPURISCVState, gpr[i]), riscv_int_regnames[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
cpu_fpr[i] = tcg_global_mem_new_i64(cpu_env,
|
|
|
|
offsetof(CPURISCVState, fpr[i]), riscv_fpr_regnames[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
cpu_pc = tcg_global_mem_new(cpu_env, offsetof(CPURISCVState, pc), "pc");
|
2020-07-01 17:24:49 +02:00
|
|
|
cpu_vl = tcg_global_mem_new(cpu_env, offsetof(CPURISCVState, vl), "vl");
|
2018-03-02 13:31:11 +01:00
|
|
|
load_res = tcg_global_mem_new(cpu_env, offsetof(CPURISCVState, load_res),
|
|
|
|
"load_res");
|
|
|
|
load_val = tcg_global_mem_new(cpu_env, offsetof(CPURISCVState, load_val),
|
|
|
|
"load_val");
|
|
|
|
}
|