qemu/target/tricore/helper.c
Peter Maydell 28f13bccbe fpu: allow flushing of output denormals to be after rounding
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>
2025-02-11 16:22:07 +00:00

193 lines
6.5 KiB
C

/*
* Copyright (c) 2012-2014 Bastian Koppelmann C-Lab/University Paderborn
*
* 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/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/registerfields.h"
#include "cpu.h"
#include "exec/exec-all.h"
#include "exec/page-protection.h"
#include "fpu/softfloat-helpers.h"
#include "qemu/qemu-print.h"
enum {
TLBRET_DIRTY = -4,
TLBRET_INVALID = -3,
TLBRET_NOMATCH = -2,
TLBRET_BADADDR = -1,
TLBRET_MATCH = 0
};
static int get_physical_address(CPUTriCoreState *env, hwaddr *physical,
int *prot, target_ulong address,
MMUAccessType access_type, int mmu_idx)
{
int ret = TLBRET_MATCH;
*physical = address & 0xFFFFFFFF;
*prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
return ret;
}
hwaddr tricore_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
{
TriCoreCPU *cpu = TRICORE_CPU(cs);
hwaddr phys_addr;
int prot;
int mmu_idx = cpu_mmu_index(cs, false);
if (get_physical_address(&cpu->env, &phys_addr, &prot, addr,
MMU_DATA_LOAD, mmu_idx)) {
return -1;
}
return phys_addr;
}
/* TODO: Add exception support */
static void raise_mmu_exception(CPUTriCoreState *env, target_ulong address,
int rw, int tlb_error)
{
}
bool tricore_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
MMUAccessType rw, int mmu_idx,
bool probe, uintptr_t retaddr)
{
CPUTriCoreState *env = cpu_env(cs);
hwaddr physical;
int prot;
int ret = 0;
rw &= 1;
ret = get_physical_address(env, &physical, &prot,
address, rw, mmu_idx);
qemu_log_mask(CPU_LOG_MMU, "%s address=0x%" VADDR_PRIx " ret %d physical "
HWADDR_FMT_plx " prot %d\n",
__func__, address, ret, physical, prot);
if (ret == TLBRET_MATCH) {
tlb_set_page(cs, address & TARGET_PAGE_MASK,
physical & TARGET_PAGE_MASK, prot | PAGE_EXEC,
mmu_idx, TARGET_PAGE_SIZE);
return true;
} else {
assert(ret < 0);
if (probe) {
return false;
}
raise_mmu_exception(env, address, rw, ret);
cpu_loop_exit_restore(cs, retaddr);
}
}
void fpu_set_state(CPUTriCoreState *env)
{
switch (extract32(env->PSW, 24, 2)) {
case 0:
set_float_rounding_mode(float_round_nearest_even, &env->fp_status);
break;
case 1:
set_float_rounding_mode(float_round_up, &env->fp_status);
break;
case 2:
set_float_rounding_mode(float_round_down, &env->fp_status);
break;
case 3:
set_float_rounding_mode(float_round_to_zero, &env->fp_status);
break;
}
set_flush_inputs_to_zero(1, &env->fp_status);
set_flush_to_zero(1, &env->fp_status);
set_float_detect_tininess(float_tininess_before_rounding, &env->fp_status);
set_float_ftz_detection(float_ftz_before_rounding, &env->fp_status);
set_default_nan_mode(1, &env->fp_status);
/* Default NaN pattern: sign bit clear, frac msb set */
set_float_default_nan_pattern(0b01000000, &env->fp_status);
}
uint32_t psw_read(CPUTriCoreState *env)
{
/* clear all USB bits */
env->PSW &= 0x7ffffff;
/* now set them from the cache */
env->PSW |= ((env->PSW_USB_C != 0) << 31);
env->PSW |= ((env->PSW_USB_V & (1 << 31)) >> 1);
env->PSW |= ((env->PSW_USB_SV & (1 << 31)) >> 2);
env->PSW |= ((env->PSW_USB_AV & (1 << 31)) >> 3);
env->PSW |= ((env->PSW_USB_SAV & (1 << 31)) >> 4);
return env->PSW;
}
void psw_write(CPUTriCoreState *env, uint32_t val)
{
env->PSW_USB_C = (val & MASK_USB_C);
env->PSW_USB_V = (val & MASK_USB_V) << 1;
env->PSW_USB_SV = (val & MASK_USB_SV) << 2;
env->PSW_USB_AV = (val & MASK_USB_AV) << 3;
env->PSW_USB_SAV = (val & MASK_USB_SAV) << 4;
env->PSW = val;
fpu_set_state(env);
}
#define FIELD_GETTER_WITH_FEATURE(NAME, REG, FIELD, FEATURE) \
uint32_t NAME(CPUTriCoreState *env) \
{ \
if (tricore_has_feature(env, TRICORE_FEATURE_##FEATURE)) { \
return FIELD_EX32(env->REG, REG, FIELD ## _ ## FEATURE); \
} \
return FIELD_EX32(env->REG, REG, FIELD ## _13); \
}
#define FIELD_GETTER(NAME, REG, FIELD) \
uint32_t NAME(CPUTriCoreState *env) \
{ \
return FIELD_EX32(env->REG, REG, FIELD); \
}
#define FIELD_SETTER_WITH_FEATURE(NAME, REG, FIELD, FEATURE) \
void NAME(CPUTriCoreState *env, uint32_t val) \
{ \
if (tricore_has_feature(env, TRICORE_FEATURE_##FEATURE)) { \
env->REG = FIELD_DP32(env->REG, REG, FIELD ## _ ## FEATURE, val); \
} \
env->REG = FIELD_DP32(env->REG, REG, FIELD ## _13, val); \
}
#define FIELD_SETTER(NAME, REG, FIELD) \
void NAME(CPUTriCoreState *env, uint32_t val) \
{ \
env->REG = FIELD_DP32(env->REG, REG, FIELD, val); \
}
FIELD_GETTER_WITH_FEATURE(pcxi_get_pcpn, PCXI, PCPN, 161)
FIELD_SETTER_WITH_FEATURE(pcxi_set_pcpn, PCXI, PCPN, 161)
FIELD_GETTER_WITH_FEATURE(pcxi_get_pie, PCXI, PIE, 161)
FIELD_SETTER_WITH_FEATURE(pcxi_set_pie, PCXI, PIE, 161)
FIELD_GETTER_WITH_FEATURE(pcxi_get_ul, PCXI, UL, 161)
FIELD_SETTER_WITH_FEATURE(pcxi_set_ul, PCXI, UL, 161)
FIELD_GETTER(pcxi_get_pcxs, PCXI, PCXS)
FIELD_GETTER(pcxi_get_pcxo, PCXI, PCXO)
FIELD_GETTER_WITH_FEATURE(icr_get_ie, ICR, IE, 161)
FIELD_SETTER_WITH_FEATURE(icr_set_ie, ICR, IE, 161)
FIELD_GETTER(icr_get_ccpn, ICR, CCPN)
FIELD_SETTER(icr_set_ccpn, ICR, CCPN)