static int pfpu_decode_insn(MilkymistPFPUState *s)

{

    uint32_t pc = s->regs[R_PC];

    uint32_t insn = s->microcode[pc];

    uint32_t reg_a = (insn >> 18) & 0x7f;

    uint32_t reg_b = (insn >> 11) & 0x7f;

    uint32_t op = (insn >> 7) & 0xf;

    uint32_t reg_d = insn & 0x7f;

    uint32_t r = 0;

    int latency = 0;



    switch (op) {

    case OP_NOP:

        break;

    case OP_FADD:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = a + b;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_FADD;

        D_EXEC(qemu_log("ADD a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_FSUB:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = a - b;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_FSUB;

        D_EXEC(qemu_log("SUB a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_FMUL:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = a * b;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_FMUL;

        D_EXEC(qemu_log("MUL a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_FABS:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float t = fabsf(a);

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_FABS;

        D_EXEC(qemu_log("ABS a=%f t=%f, r=%08x\n", a, t, r));

    } break;

    case OP_F2I:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        int32_t t = a;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_F2I;

        D_EXEC(qemu_log("F2I a=%f t=%d, r=%08x\n", a, t, r));

    } break;

    case OP_I2F:

    {

        int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);

        float t = a;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_I2F;

        D_EXEC(qemu_log("I2F a=%08x t=%f, r=%08x\n", a, t, r));

    } break;

    case OP_VECTOUT:

    {

        uint32_t a = cpu_to_be32(s->gp_regs[reg_a]);

        uint32_t b = cpu_to_be32(s->gp_regs[reg_b]);

        target_phys_addr_t dma_ptr =

            get_dma_address(s->regs[R_MESHBASE],

                    s->gp_regs[GPR_X], s->gp_regs[GPR_Y]);

        cpu_physical_memory_write(dma_ptr, (uint8_t *)&a, 4);

        cpu_physical_memory_write(dma_ptr + 4, (uint8_t *)&b, 4);

        s->regs[R_LASTDMA] = dma_ptr + 4;

        D_EXEC(qemu_log("VECTOUT a=%08x b=%08x dma=%08x\n", a, b, dma_ptr));

        trace_milkymist_pfpu_vectout(a, b, dma_ptr);

    } break;

    case OP_SIN:

    {

        int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);

        float t = sinf(a * (1.0f / (M_PI * 4096.0f)));

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_SIN;

        D_EXEC(qemu_log("SIN a=%d t=%f, r=%08x\n", a, t, r));

    } break;

    case OP_COS:

    {

        int32_t a = REINTERPRET_CAST(int32_t, s->gp_regs[reg_a]);

        float t = cosf(a * (1.0f / (M_PI * 4096.0f)));

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_COS;

        D_EXEC(qemu_log("COS a=%d t=%f, r=%08x\n", a, t, r));

    } break;

    case OP_ABOVE:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = (a > b) ? 1.0f : 0.0f;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_ABOVE;

        D_EXEC(qemu_log("ABOVE a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_EQUAL:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = (a == b) ? 1.0f : 0.0f;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_EQUAL;

        D_EXEC(qemu_log("EQUAL a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_COPY:

    {

        r = s->gp_regs[reg_a];

        latency = LATENCY_COPY;

        D_EXEC(qemu_log("COPY"));

    } break;

    case OP_IF:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        uint32_t f = s->gp_regs[GPR_FLAGS];

        float t = (f != 0) ? a : b;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_IF;

        D_EXEC(qemu_log("IF f=%u a=%f b=%f t=%f, r=%08x\n", f, a, b, t, r));

    } break;

    case OP_TSIGN:

    {

        float a = REINTERPRET_CAST(float, s->gp_regs[reg_a]);

        float b = REINTERPRET_CAST(float, s->gp_regs[reg_b]);

        float t = (b < 0) ? -a : a;

        r = REINTERPRET_CAST(uint32_t, t);

        latency = LATENCY_TSIGN;

        D_EXEC(qemu_log("TSIGN a=%f b=%f t=%f, r=%08x\n", a, b, t, r));

    } break;

    case OP_QUAKE:

    {

        uint32_t a = s->gp_regs[reg_a];

        r = 0x5f3759df - (a >> 1);

        latency = LATENCY_QUAKE;

        D_EXEC(qemu_log("QUAKE a=%d r=%08x\n", a, r));

    } break;



    default:

        error_report("milkymist_pfpu: unknown opcode %d", op);

        break;

    }



    if (!reg_d) {

        D_EXEC(qemu_log("%04d %8s R%03d, R%03d <L=%d, E=%04d>\n",

                    s->regs[R_PC], opcode_to_str[op], reg_a, reg_b, latency,

                    s->regs[R_PC] + latency));

    } else {

        D_EXEC(qemu_log("%04d %8s R%03d, R%03d <L=%d, E=%04d> -> R%03d\n",

                    s->regs[R_PC], opcode_to_str[op], reg_a, reg_b, latency,

                    s->regs[R_PC] + latency, reg_d));

    }



    if (op == OP_VECTOUT) {

        return 0;

    }



    /* store output for this cycle */

    if (reg_d) {

        uint32_t val = output_queue_remove(s);

        D_EXEC(qemu_log("R%03d <- 0x%08x\n", reg_d, val));

        s->gp_regs[reg_d] = val;

    }



    output_queue_advance(s);



    /* store op output */

    if (op != OP_NOP) {

        output_queue_insert(s, r, latency-1);

    }



    /* advance PC */

    s->regs[R_PC]++;



    return 1;

};
