/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.Allegrex;
import java.util.Arrays;
import jpcsp.Emulator;
/**
* Floating Point Unit, handles floating point operations, including BCU and LSU
*
* @author hli, gid15
*/
public class FpuState extends BcuState {
public static final boolean IMPLEMENT_ROUNDING_MODES = true;
private static final String roundingModeNames[] = {
"Round to neareast number",
"Round toward zero",
"Round toward positive infinity",
"Round toward negative infinity"
};
public static final int ROUNDING_MODE_NEAREST = 0;
public static final int ROUNDING_MODE_TOWARD_ZERO = 1;
public static final int ROUNDING_MODE_TOWARD_POSITIVE_INF = 2;
public static final int ROUNDING_MODE_TOWARD_NEGATIVE_INF = 3;
public static final class Fcr0 {
public static final int imp = 0; /* FPU design number */
public static final int rev = 0; /* FPU revision bumber */
}
public class Fcr31 {
public int rm;
public boolean c;
public boolean fs;
public void reset() {
rm = 0;
c = false;
fs = false;
}
public Fcr31() {
reset();
}
public Fcr31(Fcr31 that) {
rm = that.rm;
c = that.c;
fs = that.fs;
}
public void copy(Fcr31 that) {
rm = that.rm;
c = that.c;
fs = that.fs;
}
}
public float[] fpr;
public Fcr31 fcr31;
@Override
public void reset() {
Arrays.fill(fpr, 0.0f);
fcr31.reset();
}
@Override
public void resetAll() {
super.resetAll();
Arrays.fill(fpr, 0.0f);
fcr31.reset();
}
public FpuState() {
fpr = new float[32];
fcr31 = new Fcr31();
}
public void copy(FpuState that) {
super.copy(that);
System.arraycopy(that.fpr, 0, fpr, 0, fpr.length);
fcr31.copy(that.fcr31);
}
public FpuState(FpuState that) {
super(that);
fpr = that.fpr.clone();
fcr31 = new Fcr31(that.fcr31);
}
public float round(double d) {
float f = (float) d;
if (Float.isInfinite(f) || Float.isNaN(f)) {
return f;
}
if (fcr31.fs) {
// Flush-to-zero for denormalized numbers
int exp = Math.getExponent(f);
if (exp < Float.MIN_EXPONENT) {
return 0f;
}
}
switch (fcr31.rm) {
case ROUNDING_MODE_NEAREST:
// This is the java default rounding mode, nothing more to do.
break;
case ROUNDING_MODE_TOWARD_ZERO:
if (d < 0.0) {
if (d > f) {
f = Math.nextUp(f);
}
} else {
if (d < f) {
f = Math.nextAfter(f, 0.0);
}
}
break;
case ROUNDING_MODE_TOWARD_POSITIVE_INF:
if (d > f) {
f = Math.nextUp(f);
}
break;
case ROUNDING_MODE_TOWARD_NEGATIVE_INF:
if (d < f) {
f = Math.nextAfter(f, Double.NEGATIVE_INFINITY);
}
break;
default:
Emulator.log.error(String.format("Unknown rounding mode %d", fcr31.rm));
break;
}
return f;
}
public void doMFC1(int rt, int c1dr) {
if (rt != 0) {
setRegister(rt, Float.floatToRawIntBits(fpr[c1dr]));
}
}
public void doCFC1(int rt, int c1cr) {
if (rt != 0) {
switch (c1cr) {
case 0:
setRegister(rt, (Fcr0.imp << 8) | (Fcr0.rev));
break;
case 31:
setRegister(rt, (fcr31.fs ? (1 << 24) : 0) | (fcr31.c ? (1 << 23) : 0) | (fcr31.rm & 3));
break;
default:
doUNK(String.format("Unsupported cfc1 instruction for fcr%d", c1cr));
}
}
}
public void doMTC1(int rt, int c1dr) {
fpr[c1dr] = Float.intBitsToFloat(getRegister(rt));
}
public void doCTC1(int rt, int c1cr) {
switch (c1cr) {
case 31:
int bits = getRegister(rt) & 0x01800003;
fcr31.rm = bits & 3;
fcr31.fs = ((bits >> 24) & 1) != 0;
fcr31.c = ((bits >> 23) & 1) != 0;
if (fcr31.rm != ROUNDING_MODE_NEAREST) {
// Only rounding mode 0 is supported in Java
Emulator.log.warn(String.format("CTC1 unsupported rounding mode '%s' (rm=%d)", roundingModeNames[fcr31.rm], fcr31.rm));
}
if (fcr31.fs) {
// Flush-to-zero is not supported in Java
Emulator.log.warn(String.format("CTC1 unsupported flush-to-zero fs=%b", fcr31.fs));
}
break;
default:
doUNK(String.format("Unsupported ctc1 instruction for fcr%d", c1cr));
}
}
public boolean doBC1F(int simm16) {
npc = !fcr31.c ? branchTarget(pc, simm16) : (pc + 4);
return true;
}
public boolean doBC1T(int simm16) {
npc = fcr31.c ? branchTarget(pc, simm16) : (pc + 4);
return true;
}
public boolean doBC1FL(int simm16) {
if (!fcr31.c) {
npc = branchTarget(pc, simm16);
return true;
}
pc += 4;
return false;
}
public boolean doBC1TL(int simm16) {
if (fcr31.c) {
npc = branchTarget(pc, simm16);
return true;
}
pc += 4;
return false;
}
public void doADDS(int fd, int fs, int ft) {
fpr[fd] = fpr[fs] + fpr[ft];
}
public void doSUBS(int fd, int fs, int ft) {
fpr[fd] = fpr[fs] - fpr[ft];
}
public void doMULS(int fd, int fs, int ft) {
if (IMPLEMENT_ROUNDING_MODES) {
fpr[fd] = round(fpr[fs] * (double) fpr[ft]);
} else {
fpr[fd] = fpr[fs] * fpr[ft];
}
}
public void doDIVS(int fd, int fs, int ft) {
fpr[fd] = fpr[fs] / fpr[ft];
}
public void doSQRTS(int fd, int fs) {
fpr[fd] = (float) Math.sqrt(fpr[fs]);
}
public void doABSS(int fd, int fs) {
fpr[fd] = Math.abs(fpr[fs]);
}
public void doMOVS(int fd, int fs) {
fpr[fd] = fpr[fs];
}
public void doNEGS(int fd, int fs) {
fpr[fd] = 0.0f - fpr[fs];
}
public void doROUNDWS(int fd, int fs) {
fpr[fd] = Float.intBitsToFloat(Math.round(fpr[fs]));
}
public void doTRUNCWS(int fd, int fs) {
fpr[fd] = Float.intBitsToFloat((int) (fpr[fs]));
}
public void doCEILWS(int fd, int fs) {
fpr[fd] = Float.intBitsToFloat((int) Math.ceil(fpr[fs]));
}
public void doFLOORWS(int fd, int fs) {
fpr[fd] = Float.intBitsToFloat((int) Math.floor(fpr[fs]));
}
public void doCVTSW(int fd, int fs) {
fpr[fd] = Float.floatToRawIntBits(fpr[fs]);
}
public void doCVTWS(int fd, int fs) {
switch (fcr31.rm) {
case ROUNDING_MODE_TOWARD_ZERO:
fpr[fd] = Float.intBitsToFloat((int) (fpr[fs]));
break;
case ROUNDING_MODE_TOWARD_POSITIVE_INF:
fpr[fd] = Float.intBitsToFloat((int) Math.ceil(fpr[fs]));
break;
case ROUNDING_MODE_TOWARD_NEGATIVE_INF:
fpr[fd] = Float.intBitsToFloat((int) Math.floor(fpr[fs]));
break;
default:
fpr[fd] = Float.intBitsToFloat((int) Math.rint(fpr[fs]));
break;
}
}
public void doCCONDS(int fs, int ft, int cond) {
float x = fpr[fs];
float y = fpr[ft];
if (Float.isNaN(x) || Float.isNaN(y)) {
fcr31.c = (cond & 1) != 0;
} else {
boolean equal = ((cond & 2) != 0) && (x == y);
boolean less = ((cond & 4) != 0) && (x < y);
fcr31.c = less || equal;
}
}
public void doLWC1(int ft, int rs, int simm16) {
fpr[ft] = Float.intBitsToFloat(memory.read32(getRegister(rs) + simm16));
}
public void doSWC1(int ft, int rs, int simm16) {
memory.write32(getRegister(rs) + simm16, Float.floatToRawIntBits(fpr[ft]));
}
}