Subversion Repositories Shiroi

Rev

Blame | Last modification | View Log | Download | RSS feed

/* am9511.c
 *
 * First cut am9511 emulation. This version is NOT cycle accurate,
 * or even algorithm accurate. It should be a somewhat reasonable
 * stand-in, which should allow us to run base-line comparisions with
 * the real device.
 */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#include "am9511.h"
#include "floatcnv.h"
#include "ova.h"
#include "types.h"

/* Define fp_na() -- fp to native and
 *        na_fp() -- native to fp
 */
#ifdef z80
#define fp_na(x, y) fp_hi(x, y)
#define na_fp(x, y) hi_fp(x, y)
#else

#define fp_na(x, y) fp_ie(x, y)
#define na_fp(x, y) ie_fp(x, y)
#endif


/* Stack is 16 bytes long. sp is the stack pointer.
 * Points to next location to use.
 *
 * AM9511 status and operator latch
 */

struct am_context {
        unsigned char stack[16];
        int sp;
        void* fptmp;
        unsigned char status;
        unsigned char op_latch;
#ifndef NDEBUG
        unsigned char last_latch;
#endif

};

#define AM_OP 0x1f

/* Add to sp
 */
#define sp_add(n) ((ctx->sp + (n)) & 0xf)

/* Return pointer into stack
 */
#define stpos(offset) (ctx->stack + sp_add(offset))

/* Increment stack pointer
 */
#define inc_sp(n) ctx->sp = sp_add(n)

/* Decrement stack pointer
 */
#define dec_sp(n) ctx->sp = sp_add(-(n))

/* Push byte to am9511 stack
 */
void am_push(void* amp, unsigned char v) {
        struct am_context* ctx = (struct am_context*)amp;
        *stpos(0) = v;
        inc_sp(1);
}

/* Pop byte from am9511 stack
 */
unsigned char am_pop(void* amp) {
        struct am_context* ctx = (struct am_context*)amp;
        dec_sp(1);
        return *stpos(0);
}

/* Return status of am9511
 */
unsigned char am_status(void* amp) {
        struct am_context* ctx = (struct am_context*)amp;
        return ctx->status;
}

#define IS_SINGLE ((ctx->op_latch & AM_SINGLE) == AM_SINGLE)
#define IS_FIXED (ctx->op_latch & AM_FIXED)

/* Set SIGN and ZERO according to op type and top of stack.
 * Zero detect for integer is or'ing together all the bytes.
 * Zero detect for float is testing bit 23 for 0.
 * The sign bit for all types is the top-most bit. If 1 then
 * negative.
 */
static void sz(struct am_context* ctx) {
        if(IS_SINGLE) {
                if((*stpos(-1) | *stpos(-2)) == 0) ctx->status |= AM_ZERO;
        } else if(IS_FIXED) {
                if((*stpos(-1) | *stpos(-2) | *stpos(-3) | *stpos(-4)) == 0) ctx->status |= AM_ZERO;
        } else {
                if((*stpos(-2) & 0x80) == 0) ctx->status |= AM_ZERO;
        }
        if(*stpos(-1) & 0x80) ctx->status |= AM_SIGN;
}

/* PUPI
 */
static void pupi(struct am_context* ctx) {
        am_push(ctx, 0xda); /* little end to big end */
        am_push(ctx, 0x0f);
        am_push(ctx, 0xc9);
        am_push(ctx, 0x02);
        sz(ctx);
}

/* PTOS PTOD PTOF
 *
 * This relies on the stack data not moving during a push.
 */
static void pto(struct am_context* ctx) {
        unsigned char* s;

        if(IS_SINGLE) {
                s = stpos(-2);
                am_push(ctx, *s++);
                am_push(ctx, *s);
        } else {
                s = stpos(-4);
                am_push(ctx, *s++);
                am_push(ctx, *s++);
                am_push(ctx, *s++);
                am_push(ctx, *s);
        }
        sz(ctx);
}

/* POPS POPD POPF
 *
 * Note that the SIGN and ZERO flags are set from the element that
 * is next on stack. But... it may be wrong! We do not know what the
 * new tos element really is! (in terms of type)
 * The guide states and SIGN and ZERO are affected, but no more than that.
 */
static void pop(struct am_context* ctx) {
        if(IS_SINGLE) dec_sp(2);
        else
                dec_sp(4);
        sz(ctx);
}

/* XCHS XCHD XCHF
 */
static void xch(struct am_context* ctx) {
        unsigned char *s, *t, v;

        if(IS_SINGLE) {
                s = stpos(-2);
                t = stpos(-4);
                v = *t;
                *t++ = *s;
                *s++ = v;
                v = *t;
                *t = *s;
                *s = v;
        } else {
                s = stpos(-4);
                t = stpos(-8);
                v = *t;
                *t++ = *s;
                *s++ = v;
                v = *t;
                *t++ = *s;
                *s++ = v;
                v = *t;
                *t++ = *s;
                *s++ = v;
                v = *t;
                *t = *s;
                *s = v;
        }
        sz(ctx);
}

/* CHSF
 */
static void chsf(struct am_context* ctx) {
        /* Floating point sign change - only flip sign
         * (if not zero). And, as with the AM9511 chip, CHSF
         * is even faster than CHSS.
         */
        if(*stpos(-2) & 0x80) *stpos(-1) ^= 0x80;
        sz(ctx);
}

/* CHSS CHSD
 */
static void chs(struct am_context* ctx) {
        if(IS_SINGLE) {
                if(cm16(stpos(-2), stpos(-2))) ctx->status |= AM_ERR_OVF;
        } else {
                if(cm32(stpos(-4), stpos(-4))) ctx->status |= AM_ERR_OVF;
        }
        sz(ctx);
}

/* Push float to stack, set SIGN and ZERO
 */
static void push_float(struct am_context* ctx, float x) {
        unsigned char v[4];

        na_fp(&x, ctx->fptmp);
        fp_am(ctx->fptmp, v);
        am_push(ctx, v[0]);
        am_push(ctx, v[1]);
        am_push(ctx, v[2]);
        am_push(ctx, v[3]);
        ctx->op_latch = AM_FLOAT;
        sz(ctx);
}

/* FLTS
 */
static void flts(struct am_context* ctx) {
        int16 n;
        float x;

        n = am_pop(ctx);
        n = (n << 8) | am_pop(ctx);
        x = n;
        push_float(ctx, x);
}

/* FLTD
 */
static void fltd(struct am_context* ctx) {
        int32 n;
        float x;
        int b;

        /* HI-TECH C long shift bug
         */
        b = am_pop(ctx);
        n = b;

        n = n << 8;
        b = am_pop(ctx);
        n = n | b;

        n = n << 8;
        b = am_pop(ctx);
        n = n | b;

        n = n << 8;
        b = am_pop(ctx);
        n = n | b;

        x = n;
        push_float(ctx, x);
}

/* FIXS
 */
static void fixs(struct am_context* ctx) {
        float x;
        unsigned char* s;
        int n;

        s = stpos(-4);
        am_fp(s, ctx->fptmp);
        fp_na(ctx->fptmp, &x);
        if((x < -32768.0) || (x > 32767.0)) {
                ctx->status |= AM_ERR_OVF;
                sz(ctx);
                return;
        }
        dec_sp(4);
        n = (int)x;
        am_push(ctx, n);
        am_push(ctx, n >> 8);
        ctx->op_latch = AM_SINGLE;
        sz(ctx);
}

/* FIXD
 */
static void fixd(struct am_context* ctx) {
        float x;
        unsigned char* s;
        int32 n;
        float xl, xh;

        s = stpos(-4);
        am_fp(s, ctx->fptmp);
        fp_na(ctx->fptmp, &x);
        n = -2147483648;
        xl = (float)n;
        n = 2147483647;
        xh = (float)n;
        if((x < xl) || (x > xh)) {
                ctx->status |= AM_ERR_OVF;
                sz(ctx);
                return;
        }
        dec_sp(4);
        n = (int32)x;
        am_push(ctx, n);
        am_push(ctx, n >> 8);
        am_push(ctx, n >> 16);
        am_push(ctx, n >> 24);
        ctx->op_latch = AM_DOUBLE;
        sz(ctx);
}

/* SADD DADD
 */
static void add(struct am_context* ctx) {
        int carry;
        int overflow;

        if(IS_SINGLE) {
                carry = add16(stpos(-4), stpos(-2), stpos(-4));
                overflow = oadd16(stpos(-4), stpos(-2), stpos(-4));
                dec_sp(2);
        } else {
                carry = add32(stpos(-8), stpos(-4), stpos(-8));
                overflow = oadd32(stpos(-8), stpos(-4), stpos(-8));
                dec_sp(4);
        }
        if(carry) ctx->status |= AM_CARRY;
        if(overflow) ctx->status |= AM_ERR_OVF;
        sz(ctx);
}

/* SSUB DSUB
 */
static void sub(struct am_context* ctx) {
        int carry;
        int overflow;

        if(IS_SINGLE) {
                carry = sub16(stpos(-4), stpos(-2), stpos(-4));
                overflow = osub16(stpos(-4), stpos(-2), stpos(-4));
                dec_sp(2);
        } else {
                carry = sub32(stpos(-8), stpos(-4), stpos(-8));
                overflow = osub32(stpos(-8), stpos(-4), stpos(-8));
                dec_sp(4);
        }
        if(carry) ctx->status |= AM_CARRY;
        if(overflow) ctx->status |= AM_ERR_OVF;
        sz(ctx);
}

/* MUL
 */
static void mul(struct am_context* ctx) {
        int overflow;

        if(IS_SINGLE) {
                overflow = mull16(stpos(-4), stpos(-2), stpos(-4));
                dec_sp(2);
        } else {
                overflow = mull32(stpos(-8), stpos(-4), stpos(-8));
                dec_sp(4);
        }
        if(overflow) ctx->status |= AM_ERR_OVF;
        sz(ctx);
}

/* MUU
 */
static void muu(struct am_context* ctx) {
        int overflow;

        if(IS_SINGLE) {
                overflow = mulu16(stpos(-4), stpos(-2), stpos(-4));
                dec_sp(2);
        } else {
                overflow = mulu32(stpos(-8), stpos(-4), stpos(-8));
                dec_sp(4);
        }
        if(overflow) ctx->status |= AM_ERR_OVF;
        sz(ctx);
}

/* DIV
 */
static void divi(struct am_context* ctx) {
        int div0;

        if(IS_SINGLE) {
                div0 = div16(stpos(-4), stpos(-2), stpos(-4));
                dec_sp(2);
        } else {
                div0 = div32(stpos(-8), stpos(-4), stpos(-8));
                dec_sp(4);
        }
        if(div0) ctx->status |= AM_ERR_DIV0;
        sz(ctx);
}

/* Detect and report float overflow/underflow
 */
static int fov(struct am_context* ctx, double r) {
        int e;

        frexp(r, &e);
        if(e > 63) {
                ctx->status |= AM_ERR_OVF;
                return 1;
        } else if(e < -64) {
                ctx->status |= AM_ERR_UND;
                return 1;
        }
        return 0;
}

/* basicf - basic FADD/FSUB/FMUL/FDIV
 *
 * The guide says that overflow and underflow are detected on the
 * exponent. The mantissa is maintained, and the exponent is offset
 * by 128. So... that is what we do. Note that frexp() and ldexp()
 * should be implemented via bit operations, not arithmetic.
 */
static void basicf(struct am_context* ctx) {
        unsigned char *ap, *bp;
        float a, b, r;
        double m;
        int e;

        ap = stpos(-4);
        am_fp(ap, ctx->fptmp);
        fp_na(ctx->fptmp, &a);

        bp = stpos(-8);
        am_fp(bp, ctx->fptmp);
        fp_na(ctx->fptmp, &b);

        switch(ctx->op_latch & AM_OP) {
        case AM_FADD:
                r = a + b;
                break;
        case AM_FSUB:
                r = b - a;
                break;
        case AM_FMUL:
                r = a * b;
                break;
        case AM_FDIV:
                if(a == 0.0) {
                        r = b;
                        ctx->status |= AM_ERR_DIV0;
                } else
                        r = b / a;
                break;
        }

        /* We do not use fov() because we want to bias exponent by 128
         * on OVF/UND per the guide.
         */
        m = frexp(r, &e);
        if(e > 63) {
                ctx->status |= AM_ERR_OVF;
                e -= 128;
                r = ldexp(m, e);
        } else if(e < -64) {
                ctx->status |= AM_ERR_UND;
                e += 128;
                r = ldexp(m, e);
        }
        na_fp(&r, ctx->fptmp);
        fp_am(ctx->fptmp, bp);
        dec_sp(4);
        ctx->op_latch = AM_FLOAT;
        sz(ctx);
}

/* SQRT EXP SIN COS TAN LN LOG etc (functions with single arg)
 *
 * Note that we use the -lm math library with GCC, and the -LF library
 * with HI-TECH C. This means we are limited to only using functions
 * that are in both. This explains the strange shenanigans with double
 * here.
 */
static void ffunc(struct am_context* ctx) {
        unsigned char* ap;
        float a;
        double x;

        ap = stpos(-4);
        am_fp(ap, ctx->fptmp);
        fp_na(ctx->fptmp, &a);

        x = a;
        switch(ctx->op_latch & AM_OP) {
        case AM_SQRT:
                if(a < 0.0) {
                        ctx->status |= AM_ERR_NEG;
                        goto err;
                }
                x = sqrt(x);
                break;
        case AM_EXP:
                /* -1.0 x 2^5 .. 1.0 x 2^5 */
                if((a < -32.0) || (a > 32.0)) {
                        ctx->status |= AM_ERR_ARG;
                        goto err;
                }
                x = exp(x);
                break;
        case AM_SIN:
                x = sin(x);
                break;
        case AM_COS:
                x = cos(x);
                break;
        case AM_TAN:
                /* less than 2^-12 : return A as tan(A) */
                if(a >= (1.0 / 4096.0)) x = tan(x);
                break;
        case AM_LN:
                if(a < 0.0) {
                        ctx->status |= AM_ERR_NEG;
                        goto err;
                }
                x = log(x);
                break;
        case AM_LOG:
                if(a < 0.0) {
                        ctx->status |= AM_ERR_NEG;
                        goto err;
                }
                x = log10(x);
                break;
        case AM_ASIN:
                if((a < -1.0) || (a > 1.0)) {
                        ctx->status |= AM_ERR_ARG;
                        goto err;
                }
                x = asin(x);
                break;
        case AM_ACOS:
                if((a < -1.0) || (a > 1.0)) {
                        ctx->status |= AM_ERR_ARG;
                        goto err;
                }
                x = acos(x);
                break;
        case AM_ATAN:
                x = atan(x);
                break;
        }
        if(fov(ctx, x)) goto err;
        a = x;
        na_fp(&a, ctx->fptmp);
        fp_am(ctx->fptmp, ap);
err:
        ctx->op_latch = AM_FLOAT;
        sz(ctx);
}

/* PWR
 *
 * B^A = EXP( A * LN(B) )
 */
static void pwr(struct am_context* ctx) {
        /* B^A = EXP( A * LN(B) ) */
        unsigned char *ap, *bp;
        float a, b;
        double x;

        /* A */
        ap = stpos(-4);
        am_fp(ap, ctx->fptmp);
        fp_na(ctx->fptmp, &a);

        /* B */
        bp = stpos(-8);
        am_fp(bp, ctx->fptmp);
        fp_na(ctx->fptmp, &b);

        /* LN(B) */
        if(b < 0.0) {
                ctx->status |= AM_ERR_NEG;
                goto err;
        }
        x = b;
        x = log(x);

        /* A * LN(B) */
        x = (double)a * x;

        /* EXP( A * LN(B) ) */
        if((x < -32.0) || (x > 32.0)) {
                ctx->status |= AM_ERR_ARG;
                goto err;
        }
        x = exp(x);

        if(fov(ctx, x)) goto err;

        /* replace B with result */
        b = x;
        na_fp(&b, ctx->fptmp);
        fp_am(ctx->fptmp, bp);

        /* roll stack */
        dec_sp(4);
err:
        sz(ctx);
}

/* Issue am9511 command. Does not return until command
 * is complete.
 */
void am_command(void* amp, unsigned char op) {
        struct am_context* ctx = (struct am_context*)amp;

        ctx->op_latch = op;

#ifndef NDEBUG
        ctx->last_latch = op;
#endif


        ctx->status = AM_BUSY;

        switch(ctx->op_latch & AM_OP) {

        case AM_NOP: /* no operation */
                ctx->status = 0;
                break;

        case AM_PUPI: /* push pi */
                pupi(ctx);
                break;

        case AM_CHS: /* change sign */
                chs(ctx);
                break;

        case AM_CHSF:      /* float change sign */
                chsf(ctx); /* per Wayne Hortensius */
                break;

        case AM_POP: /* pop */
                pop(ctx);
                break;

        case AM_PTO: /* push tos (copy) */
                pto(ctx);
                break;

        case AM_XCH: /* exchange tos and nos */
                xch(ctx);
                break;

        case AM_FLTD: /* 32 bit to float */
                fltd(ctx);
                break;

        case AM_FLTS: /* 16 bit to float */
                flts(ctx);
                break;

        case AM_FIXD: /* float to 32 bit */
                fixd(ctx);
                break;

        case AM_FIXS: /* float to 16 bit */
                fixs(ctx);
                break;

        case AM_ADD: /* add */
                add(ctx);
                break;

        case AM_SUB: /* subtract nos-tos */
                sub(ctx);
                break;

        case AM_MUL: /* multiply, lower half */
                mul(ctx);
                break;

        case AM_MUU: /* multiply, upper half */
                muu(ctx);
                break;

        case AM_DIV: /* divide nos/tos */
                divi(ctx);
                break;

        case AM_FADD: /* floating add */
        case AM_FSUB: /* floating subtract */
        case AM_FMUL: /* floating multiply */
        case AM_FDIV: /* floating divide */
                basicf(ctx);
                break;

        case AM_SQRT: /* square root */
        case AM_EXP:  /* exponential (e^x) */
        case AM_SIN:  /* sine */
        case AM_COS:  /* cosine */
        case AM_TAN:  /* tangent */
        case AM_LOG:  /* common logarithm (base 10) */
        case AM_LN:   /* natural logarthm (base e) */
        case AM_ASIN: /* inverse sine */
        case AM_ACOS: /* inverse cosine */
        case AM_ATAN: /* inverse tangent */
                ffunc(ctx);
                break;

        case AM_PWR: /* power nos^tos */
                pwr(ctx);
                break;

        default:
                break;
        }

        ctx->status &= ~AM_BUSY;
}

/* Reset the am9511 emulator
 */
void am_reset(void* amp) {
        struct am_context* ctx = (struct am_context*)amp;
        int i;

        ctx->sp = 0;
        ctx->status = 0;
        ctx->op_latch = 0;
        ctx->last_latch = 0;
        for(i = 0; i < 16; ++i) ctx->stack[i] = 0;
}

/* Create chip.
 */
void* am_create(int status, int data) {
        struct am_context* p;
        void* fpp;
        fpp = malloc(fp_size());
        if(fpp == NULL) return NULL;
        p = malloc(sizeof(struct am_context));
        if(p == NULL) {
                free(fpp);
                return NULL;
        }
        p->fptmp = fpp;
        am_reset(p);
        return (void*)p;
}

/* Dump stack A..H or A..D, format depends on arg (AM_SINGLE,
 * AM_DOUBLE, AM_FLOAT). Dump status and last op_latch.
 */
void am_dump(void* amp, unsigned char op) {
        struct am_context* ctx = (struct am_context*)amp;
        int i;
        int16 n;
        int32 nl;
        float x;
        unsigned char t = ctx->status;
        int b;
        static char* opnames[] = {"NOP", "SQRT", "SIN", "COS", "TAN", "ASIN", "ACOS", "ATAN", "LOG", "LN", "EXP", "PWR", "ADD", "SUB", "MUL", "DIV", "FADD", "FSUB", "FMUL", "FDIV", "CHS", "CHSF", "MUU", "PTO", "POP", "XCH", "PUPI", "", "FLTD", "FLTS", "FIXD", "FIXS"};

        printf("AM9511 STATUS: %02x ", ctx->status);
        if(t & AM_BUSY) printf("BUSY ");
        if(t & AM_SIGN) printf("SIGN ");
        if(t & AM_ZERO) printf("ZERO ");
        if(t & AM_CARRY) printf("CARRY ");
        printf("ERROR: ");
        t &= AM_ERR_MASK;
        if(t == AM_ERR_NONE) printf("NONE");
        if(t & AM_ERR_DIV0) printf("DIV0");
        if(t & AM_ERR_NEG) printf("NEG");
        if(t & AM_ERR_ARG) printf("ARG");
        if(t & AM_ERR_ARG) printf("ARG");
        if(t & AM_ERR_UND) printf("UND");
        if(t & AM_ERR_OVF) printf("OVF");
        printf("\n");
        t = ctx->last_latch;
        printf("LAST COMMAND: ");
        if(t & AM_SR) printf("SR ");
        if((t & AM_SINGLE) == AM_SINGLE) printf("SINGLE ");
        else if(t & AM_FIXED)
                printf("DOUBLE ");
        else
                printf("FLOAT ");
        printf("%s\n", opnames[t & AM_OP]);
        t = ctx->op_latch;
        ctx->op_latch = op;
        printf("AM9511 STACK ");
        if(IS_SINGLE) {
                printf("(SINGLE)\n");
                for(i = 0; i < 8; ++i) {
                        n = *stpos(-(i * 2) - 1);
                        n = (n << 8) | *stpos(-(i * 2) - 2);
                        printf("%c: %02x %02x %d\n", 'A' + i, *stpos(-(i * 2) - 1), *stpos(-(i * 2) - 2), n);
                }
        } else {
                if(IS_FIXED) printf("(DOUBLE)\n");
                else
                        printf("(FLOAT)\n");
                for(i = 0; i < 4; ++i) {
                        printf("%c: %02x %02x %02x %02x ", 'A' + i, *stpos(-(i * 4) - 1), *stpos(-(i * 4) - 2), *stpos(-(i * 4) - 3), *stpos(-(i * 4) - 4));
                        if(IS_FIXED) {
#if 0
                /* Borked -- HI-TECH C bug
                 *
                 * We have seen this before -- use a "fix" (work-around)
                 */
                nl =             *stpos(-(i * 4) - 1);
                nl = (nl << 8) | *stpos(-(i * 4) - 2);
                nl = (nl << 8) | *stpos(-(i * 4) - 3);
                nl = (nl << 8) | *stpos(-(i * 4) - 4);
#else

                                /* We use the following instead, which seems to work
                                 */
                                b = *stpos(-(i * 4) - 1);
                                nl = b;

                                nl = nl << 8;
                                b = *stpos(-(i * 4) - 2);
                                nl = nl | b;

                                nl = nl << 8;
                                b = *stpos(-(i * 4) - 3);
                                nl = nl | b;

                                nl = nl << 8;
                                b = *stpos(-(i * 4) - 4);
                                nl = nl | b;
#endif

                                printf("%ld\n", (long)nl);
                        } else {
                                am_fp(stpos(-(i * 4) - 4), ctx->fptmp);
                                fp_na(ctx->fptmp, &x);
                                printf("%g\n", x);
                        }
                }
        }
        ctx->op_latch = t;
}