mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 15:23:53 -06:00

Currently we handle flushing of output denormals in uncanon_normal always before we deal with rounding. This works for architectures that detect tininess before rounding, but is usually not the right place when the architecture detects tininess after rounding. For example, for x86 the SDM states that the MXCSR FTZ control bit causes outputs to be flushed to zero "when it detects a floating-point underflow condition". This means that we mustn't flush to zero if the input is such that after rounding it is no longer tiny. At least one of our guest architectures does underflow detection after rounding but flushing of denormals before rounding (MIPS MSA); this means we need to have a config knob for this that is separate from our existing tininess_before_rounding setting. Add an ftz_detection flag. For consistency with tininess_before_rounding, we make it default to "detect ftz after rounding"; this means that we need to explicitly set the flag to "detect ftz before rounding" on every existing architecture that sets flush_to_zero, so that this commit has no behaviour change. (This means more code change here but for the long term a less confusing API.) For several architectures the current behaviour is either definitely or possibly wrong; annotate those with TODO comments. These architectures are definitely wrong (and should detect ftz after rounding): * x86 * Alpha For these architectures the spec is unclear: * MIPS (for non-MSA) * RX * SH4 PA-RISC makes ftz detection IMPDEF, but we aren't setting the "tininess before rounding" setting that we ought to. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
334 lines
9.7 KiB
C
334 lines
9.7 KiB
C
/*
|
|
* QEMU SuperH CPU
|
|
*
|
|
* Copyright (c) 2005 Samuel Tardieu
|
|
* Copyright (c) 2012 SUSE LINUX Products GmbH
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see
|
|
* <http://www.gnu.org/licenses/lgpl-2.1.html>
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/qemu-print.h"
|
|
#include "cpu.h"
|
|
#include "migration/vmstate.h"
|
|
#include "exec/exec-all.h"
|
|
#include "exec/translation-block.h"
|
|
#include "fpu/softfloat-helpers.h"
|
|
#include "tcg/tcg.h"
|
|
|
|
static void superh_cpu_set_pc(CPUState *cs, vaddr value)
|
|
{
|
|
SuperHCPU *cpu = SUPERH_CPU(cs);
|
|
|
|
cpu->env.pc = value;
|
|
}
|
|
|
|
static vaddr superh_cpu_get_pc(CPUState *cs)
|
|
{
|
|
SuperHCPU *cpu = SUPERH_CPU(cs);
|
|
|
|
return cpu->env.pc;
|
|
}
|
|
|
|
static void superh_cpu_synchronize_from_tb(CPUState *cs,
|
|
const TranslationBlock *tb)
|
|
{
|
|
SuperHCPU *cpu = SUPERH_CPU(cs);
|
|
|
|
tcg_debug_assert(!tcg_cflags_has(cs, CF_PCREL));
|
|
cpu->env.pc = tb->pc;
|
|
cpu->env.flags = tb->flags & TB_FLAG_ENVFLAGS_MASK;
|
|
}
|
|
|
|
static void superh_restore_state_to_opc(CPUState *cs,
|
|
const TranslationBlock *tb,
|
|
const uint64_t *data)
|
|
{
|
|
SuperHCPU *cpu = SUPERH_CPU(cs);
|
|
|
|
cpu->env.pc = data[0];
|
|
cpu->env.flags = data[1];
|
|
/*
|
|
* Theoretically delayed_pc should also be restored. In practice the
|
|
* branch instruction is re-executed after exception, so the delayed
|
|
* branch target will be recomputed.
|
|
*/
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
static bool superh_io_recompile_replay_branch(CPUState *cs,
|
|
const TranslationBlock *tb)
|
|
{
|
|
CPUSH4State *env = cpu_env(cs);
|
|
|
|
if ((env->flags & (TB_FLAG_DELAY_SLOT | TB_FLAG_DELAY_SLOT_COND))
|
|
&& !tcg_cflags_has(cs, CF_PCREL) && env->pc != tb->pc) {
|
|
env->pc -= 2;
|
|
env->flags &= ~(TB_FLAG_DELAY_SLOT | TB_FLAG_DELAY_SLOT_COND);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static bool superh_cpu_has_work(CPUState *cs)
|
|
{
|
|
return cs->interrupt_request & CPU_INTERRUPT_HARD;
|
|
}
|
|
|
|
static int sh4_cpu_mmu_index(CPUState *cs, bool ifetch)
|
|
{
|
|
CPUSH4State *env = cpu_env(cs);
|
|
|
|
/*
|
|
* The instruction in a RTE delay slot is fetched in privileged mode,
|
|
* but executed in user mode.
|
|
*/
|
|
if (ifetch && (env->flags & TB_FLAG_DELAY_SLOT_RTE)) {
|
|
return 0;
|
|
} else {
|
|
return (env->sr & (1u << SR_MD)) == 0 ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
static void superh_cpu_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
CPUState *cs = CPU(obj);
|
|
SuperHCPUClass *scc = SUPERH_CPU_GET_CLASS(obj);
|
|
CPUSH4State *env = cpu_env(cs);
|
|
|
|
if (scc->parent_phases.hold) {
|
|
scc->parent_phases.hold(obj, type);
|
|
}
|
|
|
|
memset(env, 0, offsetof(CPUSH4State, end_reset_fields));
|
|
|
|
env->pc = 0xA0000000;
|
|
#if defined(CONFIG_USER_ONLY)
|
|
env->fpscr = FPSCR_PR; /* value for userspace according to the kernel */
|
|
set_float_rounding_mode(float_round_nearest_even, &env->fp_status); /* ?! */
|
|
#else
|
|
env->sr = (1u << SR_MD) | (1u << SR_RB) | (1u << SR_BL) |
|
|
(1u << SR_I3) | (1u << SR_I2) | (1u << SR_I1) | (1u << SR_I0);
|
|
env->fpscr = FPSCR_DN | FPSCR_RM_ZERO; /* CPU reset value according to SH4 manual */
|
|
set_float_rounding_mode(float_round_to_zero, &env->fp_status);
|
|
set_flush_to_zero(1, &env->fp_status);
|
|
#endif
|
|
set_default_nan_mode(1, &env->fp_status);
|
|
/* sign bit clear, set all frac bits other than msb */
|
|
set_float_default_nan_pattern(0b00111111, &env->fp_status);
|
|
/*
|
|
* TODO: "SH-4 CPU Core Architecture ADCS 7182230F" doesn't say whether
|
|
* it detects tininess before or after rounding. Section 6.4 is clear
|
|
* that flush-to-zero happens when the result underflows, though, so
|
|
* either this should be "detect ftz after rounding" or else we should
|
|
* be setting "detect tininess before rounding".
|
|
*/
|
|
set_float_ftz_detection(float_ftz_before_rounding, &env->fp_status);
|
|
}
|
|
|
|
static void superh_cpu_disas_set_info(CPUState *cpu, disassemble_info *info)
|
|
{
|
|
info->mach = bfd_mach_sh4;
|
|
info->print_insn = print_insn_sh;
|
|
}
|
|
|
|
static ObjectClass *superh_cpu_class_by_name(const char *cpu_model)
|
|
{
|
|
ObjectClass *oc;
|
|
char *s, *typename = NULL;
|
|
|
|
s = g_ascii_strdown(cpu_model, -1);
|
|
if (strcmp(s, "any") == 0) {
|
|
oc = object_class_by_name(TYPE_SH7750R_CPU);
|
|
goto out;
|
|
}
|
|
|
|
typename = g_strdup_printf(SUPERH_CPU_TYPE_NAME("%s"), s);
|
|
oc = object_class_by_name(typename);
|
|
|
|
out:
|
|
g_free(s);
|
|
g_free(typename);
|
|
return oc;
|
|
}
|
|
|
|
static void sh7750r_cpu_initfn(Object *obj)
|
|
{
|
|
CPUSH4State *env = cpu_env(CPU(obj));
|
|
|
|
env->id = SH_CPU_SH7750R;
|
|
env->features = SH_FEATURE_BCR3_AND_BCR4;
|
|
}
|
|
|
|
static void sh7750r_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
SuperHCPUClass *scc = SUPERH_CPU_CLASS(oc);
|
|
|
|
scc->pvr = 0x00050000;
|
|
scc->prr = 0x00000100;
|
|
scc->cvr = 0x00110000;
|
|
}
|
|
|
|
static void sh7751r_cpu_initfn(Object *obj)
|
|
{
|
|
CPUSH4State *env = cpu_env(CPU(obj));
|
|
|
|
env->id = SH_CPU_SH7751R;
|
|
env->features = SH_FEATURE_BCR3_AND_BCR4;
|
|
}
|
|
|
|
static void sh7751r_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
SuperHCPUClass *scc = SUPERH_CPU_CLASS(oc);
|
|
|
|
scc->pvr = 0x04050005;
|
|
scc->prr = 0x00000113;
|
|
scc->cvr = 0x00110000; /* Neutered caches, should be 0x20480000 */
|
|
}
|
|
|
|
static void sh7785_cpu_initfn(Object *obj)
|
|
{
|
|
CPUSH4State *env = cpu_env(CPU(obj));
|
|
|
|
env->id = SH_CPU_SH7785;
|
|
env->features = SH_FEATURE_SH4A;
|
|
}
|
|
|
|
static void sh7785_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
SuperHCPUClass *scc = SUPERH_CPU_CLASS(oc);
|
|
|
|
scc->pvr = 0x10300700;
|
|
scc->prr = 0x00000200;
|
|
scc->cvr = 0x71440211;
|
|
}
|
|
|
|
static void superh_cpu_realizefn(DeviceState *dev, Error **errp)
|
|
{
|
|
CPUState *cs = CPU(dev);
|
|
SuperHCPUClass *scc = SUPERH_CPU_GET_CLASS(dev);
|
|
Error *local_err = NULL;
|
|
|
|
cpu_exec_realizefn(cs, &local_err);
|
|
if (local_err != NULL) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
|
|
cpu_reset(cs);
|
|
qemu_init_vcpu(cs);
|
|
|
|
scc->parent_realize(dev, errp);
|
|
}
|
|
|
|
static void superh_cpu_initfn(Object *obj)
|
|
{
|
|
CPUSH4State *env = cpu_env(CPU(obj));
|
|
|
|
env->movcal_backup_tail = &(env->movcal_backup);
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
static const VMStateDescription vmstate_sh_cpu = {
|
|
.name = "cpu",
|
|
.unmigratable = 1,
|
|
};
|
|
|
|
#include "hw/core/sysemu-cpu-ops.h"
|
|
|
|
static const struct SysemuCPUOps sh4_sysemu_ops = {
|
|
.get_phys_page_debug = superh_cpu_get_phys_page_debug,
|
|
};
|
|
#endif
|
|
|
|
#include "hw/core/tcg-cpu-ops.h"
|
|
|
|
static const TCGCPUOps superh_tcg_ops = {
|
|
.initialize = sh4_translate_init,
|
|
.translate_code = sh4_translate_code,
|
|
.synchronize_from_tb = superh_cpu_synchronize_from_tb,
|
|
.restore_state_to_opc = superh_restore_state_to_opc,
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
.tlb_fill = superh_cpu_tlb_fill,
|
|
.cpu_exec_interrupt = superh_cpu_exec_interrupt,
|
|
.cpu_exec_halt = superh_cpu_has_work,
|
|
.do_interrupt = superh_cpu_do_interrupt,
|
|
.do_unaligned_access = superh_cpu_do_unaligned_access,
|
|
.io_recompile_replay_branch = superh_io_recompile_replay_branch,
|
|
#endif /* !CONFIG_USER_ONLY */
|
|
};
|
|
|
|
static void superh_cpu_class_init(ObjectClass *oc, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(oc);
|
|
CPUClass *cc = CPU_CLASS(oc);
|
|
SuperHCPUClass *scc = SUPERH_CPU_CLASS(oc);
|
|
ResettableClass *rc = RESETTABLE_CLASS(oc);
|
|
|
|
device_class_set_parent_realize(dc, superh_cpu_realizefn,
|
|
&scc->parent_realize);
|
|
|
|
resettable_class_set_parent_phases(rc, NULL, superh_cpu_reset_hold, NULL,
|
|
&scc->parent_phases);
|
|
|
|
cc->class_by_name = superh_cpu_class_by_name;
|
|
cc->has_work = superh_cpu_has_work;
|
|
cc->mmu_index = sh4_cpu_mmu_index;
|
|
cc->dump_state = superh_cpu_dump_state;
|
|
cc->set_pc = superh_cpu_set_pc;
|
|
cc->get_pc = superh_cpu_get_pc;
|
|
cc->gdb_read_register = superh_cpu_gdb_read_register;
|
|
cc->gdb_write_register = superh_cpu_gdb_write_register;
|
|
#ifndef CONFIG_USER_ONLY
|
|
cc->sysemu_ops = &sh4_sysemu_ops;
|
|
dc->vmsd = &vmstate_sh_cpu;
|
|
#endif
|
|
cc->disas_set_info = superh_cpu_disas_set_info;
|
|
|
|
cc->gdb_num_core_regs = 59;
|
|
cc->tcg_ops = &superh_tcg_ops;
|
|
}
|
|
|
|
#define DEFINE_SUPERH_CPU_TYPE(type_name, cinit, initfn) \
|
|
{ \
|
|
.name = type_name, \
|
|
.parent = TYPE_SUPERH_CPU, \
|
|
.class_init = cinit, \
|
|
.instance_init = initfn, \
|
|
}
|
|
static const TypeInfo superh_cpu_type_infos[] = {
|
|
{
|
|
.name = TYPE_SUPERH_CPU,
|
|
.parent = TYPE_CPU,
|
|
.instance_size = sizeof(SuperHCPU),
|
|
.instance_align = __alignof(SuperHCPU),
|
|
.instance_init = superh_cpu_initfn,
|
|
.abstract = true,
|
|
.class_size = sizeof(SuperHCPUClass),
|
|
.class_init = superh_cpu_class_init,
|
|
},
|
|
DEFINE_SUPERH_CPU_TYPE(TYPE_SH7750R_CPU, sh7750r_class_init,
|
|
sh7750r_cpu_initfn),
|
|
DEFINE_SUPERH_CPU_TYPE(TYPE_SH7751R_CPU, sh7751r_class_init,
|
|
sh7751r_cpu_initfn),
|
|
DEFINE_SUPERH_CPU_TYPE(TYPE_SH7785_CPU, sh7785_class_init,
|
|
sh7785_cpu_initfn),
|
|
|
|
};
|
|
|
|
DEFINE_TYPES(superh_cpu_type_infos)
|