/* * RX helper functions * * Copyright (c) 2019 Yoshinori Sato * * 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 . */ #include "qemu/osdep.h" #include "qemu/bitops.h" #include "cpu.h" #include "exec/exec-all.h" #include "exec/helper-proto.h" #include "exec/cpu_ldst.h" #include "fpu/softfloat.h" static inline void QEMU_NORETURN raise_exception(CPURXState *env, int index, uintptr_t retaddr); static void _set_psw(CPURXState *env, uint32_t psw, uint32_t rte) { uint32_t prev_u; prev_u = env->psw_u; rx_cpu_unpack_psw(env, psw, rte); if (prev_u != env->psw_u) { /* switch r0 */ if (env->psw_u) { env->isp = env->regs[0]; env->regs[0] = env->usp; } else { env->usp = env->regs[0]; env->regs[0] = env->isp; } } } void helper_set_psw(CPURXState *env, uint32_t psw) { _set_psw(env, psw, 0); } void helper_set_psw_rte(CPURXState *env, uint32_t psw) { _set_psw(env, psw, 1); } uint32_t helper_pack_psw(CPURXState *env) { return rx_cpu_pack_psw(env); } #define SET_FPSW(b) \ do { \ env->fpsw = FIELD_DP32(env->fpsw, FPSW, C ## b, 1); \ if (!FIELD_EX32(env->fpsw, FPSW, E ## b)) { \ env->fpsw = FIELD_DP32(env->fpsw, FPSW, F ## b, 1); \ } \ } while (0) /* fp operations */ static void update_fpsw(CPURXState *env, float32 ret, uintptr_t retaddr) { int xcpt, cause, enable; env->psw_z = ret & ~(1 << 31); /* mask sign bit */ env->psw_s = ret; xcpt = get_float_exception_flags(&env->fp_status); /* Clear the cause entries */ env->fpsw = FIELD_DP32(env->fpsw, FPSW, CAUSE, 0); /* set FPSW */ if (unlikely(xcpt)) { if (xcpt & float_flag_invalid) { SET_FPSW(V); } if (xcpt & float_flag_divbyzero) { SET_FPSW(Z); } if (xcpt & float_flag_overflow) { SET_FPSW(O); } if (xcpt & float_flag_underflow) { SET_FPSW(U); } if (xcpt & float_flag_inexact) { SET_FPSW(X); } if ((xcpt & (float_flag_input_denormal | float_flag_output_denormal)) && !FIELD_EX32(env->fpsw, FPSW, DN)) { env->fpsw = FIELD_DP32(env->fpsw, FPSW, CE, 1); } /* update FPSW_FLAG_S */ if (FIELD_EX32(env->fpsw, FPSW, FLAGS) != 0) { env->fpsw = FIELD_DP32(env->fpsw, FPSW, FS, 1); } /* Generate an exception if enabled */ cause = FIELD_EX32(env->fpsw, FPSW, CAUSE); enable = FIELD_EX32(env->fpsw, FPSW, ENABLE); enable |= 1 << 5; /* CE always enabled */ if (cause & enable) { raise_exception(env, 21, retaddr); } } } void helper_set_fpsw(CPURXState *env, uint32_t val) { static const int roundmode[] = { float_round_nearest_even, float_round_to_zero, float_round_up, float_round_down, }; uint32_t fpsw = env->fpsw; fpsw |= 0x7fffff03; val &= ~0x80000000; fpsw &= val; FIELD_DP32(fpsw, FPSW, FS, FIELD_EX32(fpsw, FPSW, FLAGS) != 0); env->fpsw = fpsw; set_float_rounding_mode(roundmode[FIELD_EX32(env->fpsw, FPSW, RM)], &env->fp_status); } #define FLOATOP(op, func) \ float32 helper_##op(CPURXState *env, float32 t0, float32 t1) \ { \ float32 ret; \ ret = func(t0, t1, &env->fp_status); \ update_fpsw(env, *(uint32_t *)&ret, GETPC()); \ return ret; \ } FLOATOP(fadd, float32_add) FLOATOP(fsub, float32_sub) FLOATOP(fmul, float32_mul) FLOATOP(fdiv, float32_div) void helper_fcmp(CPURXState *env, float32 t0, float32 t1) { int st; st = float32_compare(t0, t1, &env->fp_status); update_fpsw(env, 0, GETPC()); env->psw_z = 1; env->psw_s = env->psw_o = 0; switch (st) { case float_relation_equal: env->psw_z = 0; break; case float_relation_less: env->psw_s = -1; break; case float_relation_unordered: env->psw_o = -1; break; } } uint32_t helper_ftoi(CPURXState *env, float32 t0) { uint32_t ret; ret = float32_to_int32_round_to_zero(t0, &env->fp_status); update_fpsw(env, ret, GETPC()); return ret; } uint32_t helper_round(CPURXState *env, float32 t0) { uint32_t ret; ret = float32_to_int32(t0, &env->fp_status); update_fpsw(env, ret, GETPC()); return ret; } float32 helper_itof(CPURXState *env, uint32_t t0) { float32 ret; ret = int32_to_float32(t0, &env->fp_status); update_fpsw(env, ret, GETPC()); return ret; } /* string operations */ void helper_scmpu(CPURXState *env) { uint8_t tmp0, tmp1; if (env->regs[3] == 0) { return; } while (env->regs[3] != 0) { tmp0 = cpu_ldub_data_ra(env, env->regs[1]++, GETPC()); tmp1 = cpu_ldub_data_ra(env, env->regs[2]++, GETPC()); env->regs[3]--; if (tmp0 != tmp1 || tmp0 == '\0') { break; } } env->psw_z = tmp0 - tmp1; env->psw_c = (tmp0 >= tmp1); } static uint32_t (* const cpu_ldufn[])(CPUArchState *env, target_ulong ptr, uintptr_t retaddr) = { cpu_ldub_data_ra, cpu_lduw_data_ra, cpu_ldl_data_ra, }; static uint32_t (* const cpu_ldfn[])(CPUArchState *env, target_ulong ptr, uintptr_t retaddr) = { cpu_ldub_data_ra, cpu_lduw_data_ra, cpu_ldl_data_ra, }; static void (* const cpu_stfn[])(CPUArchState *env, target_ulong ptr, uint32_t val, uintptr_t retaddr) = { cpu_stb_data_ra, cpu_stw_data_ra, cpu_stl_data_ra, }; void helper_sstr(CPURXState *env, uint32_t sz) { tcg_debug_assert(sz < 3); while (env->regs[3] != 0) { cpu_stfn[sz](env, env->regs[1], env->regs[2], GETPC()); env->regs[1] += 1 << sz; env->regs[3]--; } } #define OP_SMOVU 1 #define OP_SMOVF 0 #define OP_SMOVB 2 static void smov(uint32_t mode, CPURXState *env) { uint8_t tmp; int dir; dir = (mode & OP_SMOVB) ? -1 : 1; while (env->regs[3] != 0) { tmp = cpu_ldub_data_ra(env, env->regs[2], GETPC()); cpu_stb_data_ra(env, env->regs[1], tmp, GETPC()); env->regs[1] += dir; env->regs[2] += dir; env->regs[3]--; if ((mode & OP_SMOVU) && tmp == 0) { break; } } } void helper_smovu(CPURXState *env) { smov(OP_SMOVU, env); } void helper_smovf(CPURXState *env) { smov(OP_SMOVF, env); } void helper_smovb(CPURXState *env) { smov(OP_SMOVB, env); } void helper_suntil(CPURXState *env, uint32_t sz) { uint32_t tmp; tcg_debug_assert(sz < 3); if (env->regs[3] == 0) { return ; } while (env->regs[3] != 0) { tmp = cpu_ldufn[sz](env, env->regs[1], GETPC()); env->regs[1] += 1 << sz; env->regs[3]--; if (tmp == env->regs[2]) { break; } } env->psw_z = tmp - env->regs[2]; env->psw_c = (tmp <= env->regs[2]); } void helper_swhile(CPURXState *env, uint32_t sz) { uint32_t tmp; tcg_debug_assert(sz < 3); if (env->regs[3] == 0) { return ; } while (env->regs[3] != 0) { tmp = cpu_ldufn[sz](env, env->regs[1], GETPC()); env->regs[1] += 1 << sz; env->regs[3]--; if (tmp != env->regs[2]) { break; } } env->psw_z = env->regs[3]; env->psw_c = (tmp <= env->regs[2]); } /* accumlator operations */ void helper_rmpa(CPURXState *env, uint32_t sz) { uint64_t result_l, prev; int32_t result_h; int64_t tmp0, tmp1; if (env->regs[3] == 0) { return; } result_l = env->regs[5]; result_l <<= 32; result_l |= env->regs[4]; result_h = env->regs[6]; env->psw_o = 0; while (env->regs[3] != 0) { tmp0 = cpu_ldfn[sz](env, env->regs[1], GETPC()); tmp1 = cpu_ldfn[sz](env, env->regs[2], GETPC()); tmp0 *= tmp1; prev = result_l; result_l += tmp0; /* carry / bollow */ if (tmp0 < 0) { if (prev > result_l) { result_h--; } } else { if (prev < result_l) { result_h++; } } env->regs[1] += 1 << sz; env->regs[2] += 1 << sz; } env->psw_s = result_h; env->psw_o = (result_h != 0 && result_h != -1) << 31; env->regs[6] = result_h; env->regs[5] = result_l >> 32; env->regs[4] = result_l & 0xffffffff; } void helper_racw(CPURXState *env, uint32_t imm) { int64_t acc; acc = env->acc; acc <<= (imm + 1); acc += 0x0000000080000000LL; if (acc > 0x00007fff00000000LL) { acc = 0x00007fff00000000LL; } else if (acc < -0x800000000000LL) { acc = -0x800000000000LL; } else { acc &= 0xffffffff00000000LL; } env->acc = acc; } void helper_satr(CPURXState *env) { if (env->psw_o >> 31) { if ((int)env->psw_s < 0) { env->regs[6] = 0x00000000; env->regs[5] = 0x7fffffff; env->regs[4] = 0xffffffff; } else { env->regs[6] = 0xffffffff; env->regs[5] = 0x80000000; env->regs[4] = 0x00000000; } } } /* div */ uint32_t helper_div(CPURXState *env, uint32_t num, uint32_t den) { uint32_t ret = num; if (!((num == INT_MIN && den == -1) || den == 0)) { ret = (int32_t)num / (int32_t)den; env->psw_o = 0; } else { env->psw_o = -1; } return ret; } uint32_t helper_divu(CPURXState *env, uint32_t num, uint32_t den) { uint32_t ret = num; if (den != 0) { ret = num / den; env->psw_o = 0; } else { env->psw_o = -1; } return ret; } /* exception */ static inline void QEMU_NORETURN raise_exception(CPURXState *env, int index, uintptr_t retaddr) { CPUState *cs = env_cpu(env); cs->exception_index = index; cpu_loop_exit_restore(cs, retaddr); } void QEMU_NORETURN helper_raise_privilege_violation(CPURXState *env) { raise_exception(env, 20, GETPC()); } void QEMU_NORETURN helper_raise_access_fault(CPURXState *env) { raise_exception(env, 21, GETPC()); } void QEMU_NORETURN helper_raise_illegal_instruction(CPURXState *env) { raise_exception(env, 23, GETPC()); } void QEMU_NORETURN helper_wait(CPURXState *env) { CPUState *cs = env_cpu(env); cs->halted = 1; env->in_sleep = 1; raise_exception(env, EXCP_HLT, 0); } void QEMU_NORETURN helper_debug(CPURXState *env) { CPUState *cs = env_cpu(env); cs->exception_index = EXCP_DEBUG; cpu_loop_exit(cs); } void QEMU_NORETURN helper_rxint(CPURXState *env, uint32_t vec) { raise_exception(env, 0x100 + vec, 0); } void QEMU_NORETURN helper_rxbrk(CPURXState *env) { raise_exception(env, 0x100, 0); }