summary refs log tree commit diff stats
path: root/target/microblaze
diff options
context:
space:
mode:
Diffstat (limited to 'target/microblaze')
-rw-r--r--target/microblaze/cpu.c126
-rw-r--r--target/microblaze/cpu.h81
-rw-r--r--target/microblaze/gdbstub.c6
-rw-r--r--target/microblaze/helper.c250
-rw-r--r--target/microblaze/machine.c106
-rw-r--r--target/microblaze/meson.build5
-rw-r--r--target/microblaze/mmu.c39
-rw-r--r--target/microblaze/mmu.h20
-rw-r--r--target/microblaze/op_helper.c2
-rw-r--r--target/microblaze/translate.c198
10 files changed, 497 insertions, 336 deletions
diff --git a/target/microblaze/cpu.c b/target/microblaze/cpu.c
index 67017ecc33..9b2482159d 100644
--- a/target/microblaze/cpu.c
+++ b/target/microblaze/cpu.c
@@ -26,7 +26,6 @@
 #include "cpu.h"
 #include "qemu/module.h"
 #include "hw/qdev-properties.h"
-#include "migration/vmstate.h"
 #include "exec/exec-all.h"
 #include "fpu/softfloat-helpers.h"
 
@@ -80,6 +79,16 @@ static void mb_cpu_set_pc(CPUState *cs, vaddr value)
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
 
     cpu->env.pc = value;
+    /* Ensure D_FLAG and IMM_FLAG are clear for the new PC */
+    cpu->env.iflags = 0;
+}
+
+static void mb_cpu_synchronize_from_tb(CPUState *cs, TranslationBlock *tb)
+{
+    MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
+
+    cpu->env.pc = tb->pc;
+    cpu->env.iflags = tb->flags & IFLAGS_TB_MASK;
 }
 
 static bool mb_cpu_has_work(CPUState *cs)
@@ -125,10 +134,6 @@ static void mb_cpu_reset(DeviceState *dev)
 #else
     mb_cpu_write_msr(env, 0);
     mmu_init(&env->mmu);
-    env->mmu.c_mmu = 3;
-    env->mmu.c_mmu_tlb_access = 3;
-    env->mmu.c_mmu_zones = 16;
-    env->mmu.c_addr_mask = MAKE_64BIT_MASK(0, cpu->cfg.addr_size);
 #endif
 }
 
@@ -143,7 +148,6 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
     CPUState *cs = CPU(dev);
     MicroBlazeCPUClass *mcc = MICROBLAZE_CPU_GET_CLASS(dev);
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
-    CPUMBState *env = &cpu->env;
     uint8_t version_code = 0;
     const char *version;
     int i = 0;
@@ -163,16 +167,6 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
 
     qemu_init_vcpu(cs);
 
-    env->pvr.regs[0] = PVR0_USE_EXC_MASK
-                       | PVR0_USE_ICACHE_MASK
-                       | PVR0_USE_DCACHE_MASK;
-    env->pvr.regs[2] = PVR2_D_OPB_MASK
-                        | PVR2_D_LMB_MASK
-                        | PVR2_I_OPB_MASK
-                        | PVR2_I_LMB_MASK
-                        | PVR2_FPU_EXC_MASK
-                        | 0;
-
     version = cpu->cfg.version ? cpu->cfg.version : DEFAULT_CPU_VERSION;
     for (i = 0; mb_cpu_lookup[i].name && version; i++) {
         if (strcmp(mb_cpu_lookup[i].name, version) == 0) {
@@ -185,46 +179,58 @@ static void mb_cpu_realizefn(DeviceState *dev, Error **errp)
         qemu_log("Invalid MicroBlaze version number: %s\n", cpu->cfg.version);
     }
 
-    env->pvr.regs[0] |= (cpu->cfg.stackprot ? PVR0_SPROT_MASK : 0) |
-                        (cpu->cfg.use_fpu ? PVR0_USE_FPU_MASK : 0) |
-                        (cpu->cfg.use_hw_mul ? PVR0_USE_HW_MUL_MASK : 0) |
-                        (cpu->cfg.use_barrel ? PVR0_USE_BARREL_MASK : 0) |
-                        (cpu->cfg.use_div ? PVR0_USE_DIV_MASK : 0) |
-                        (cpu->cfg.use_mmu ? PVR0_USE_MMU_MASK : 0) |
-                        (cpu->cfg.endi ? PVR0_ENDI_MASK : 0) |
-                        (version_code << PVR0_VERSION_SHIFT) |
-                        (cpu->cfg.pvr == C_PVR_FULL ? PVR0_PVR_FULL_MASK : 0) |
-                        cpu->cfg.pvr_user1;
-
-    env->pvr.regs[1] = cpu->cfg.pvr_user2;
-    env->pvr.regs[2] |= (cpu->cfg.use_fpu ? PVR2_USE_FPU_MASK : 0) |
-                        (cpu->cfg.use_fpu > 1 ? PVR2_USE_FPU2_MASK : 0) |
-                        (cpu->cfg.use_hw_mul ? PVR2_USE_HW_MUL_MASK : 0) |
-                        (cpu->cfg.use_hw_mul > 1 ? PVR2_USE_MUL64_MASK : 0) |
-                        (cpu->cfg.use_barrel ? PVR2_USE_BARREL_MASK : 0) |
-                        (cpu->cfg.use_div ? PVR2_USE_DIV_MASK : 0) |
-                        (cpu->cfg.use_msr_instr ? PVR2_USE_MSR_INSTR : 0) |
-                        (cpu->cfg.use_pcmp_instr ? PVR2_USE_PCMP_INSTR : 0) |
-                        (cpu->cfg.dopb_bus_exception ?
-                                                 PVR2_DOPB_BUS_EXC_MASK : 0) |
-                        (cpu->cfg.iopb_bus_exception ?
-                                                 PVR2_IOPB_BUS_EXC_MASK : 0) |
-                        (cpu->cfg.div_zero_exception ?
-                                                 PVR2_DIV_ZERO_EXC_MASK : 0) |
-                        (cpu->cfg.illegal_opcode_exception ?
-                                                PVR2_ILL_OPCODE_EXC_MASK : 0) |
-                        (cpu->cfg.unaligned_exceptions ?
-                                                PVR2_UNALIGNED_EXC_MASK : 0) |
-                        (cpu->cfg.opcode_0_illegal ?
-                                                 PVR2_OPCODE_0x0_ILL_MASK : 0);
-
-    env->pvr.regs[5] |= cpu->cfg.dcache_writeback ?
-                                        PVR5_DCACHE_WRITEBACK_MASK : 0;
-
-    env->pvr.regs[10] = 0x0c000000 | /* Default to spartan 3a dsp family.  */
-                        (cpu->cfg.addr_size - 32) << PVR10_ASIZE_SHIFT;
-    env->pvr.regs[11] = (cpu->cfg.use_mmu ? PVR11_USE_MMU : 0) |
-                        16 << 17;
+    cpu->cfg.pvr_regs[0] =
+        (PVR0_USE_EXC_MASK |
+         PVR0_USE_ICACHE_MASK |
+         PVR0_USE_DCACHE_MASK |
+         (cpu->cfg.stackprot ? PVR0_SPROT_MASK : 0) |
+         (cpu->cfg.use_fpu ? PVR0_USE_FPU_MASK : 0) |
+         (cpu->cfg.use_hw_mul ? PVR0_USE_HW_MUL_MASK : 0) |
+         (cpu->cfg.use_barrel ? PVR0_USE_BARREL_MASK : 0) |
+         (cpu->cfg.use_div ? PVR0_USE_DIV_MASK : 0) |
+         (cpu->cfg.use_mmu ? PVR0_USE_MMU_MASK : 0) |
+         (cpu->cfg.endi ? PVR0_ENDI_MASK : 0) |
+         (version_code << PVR0_VERSION_SHIFT) |
+         (cpu->cfg.pvr == C_PVR_FULL ? PVR0_PVR_FULL_MASK : 0) |
+         cpu->cfg.pvr_user1);
+
+    cpu->cfg.pvr_regs[1] = cpu->cfg.pvr_user2;
+
+    cpu->cfg.pvr_regs[2] =
+        (PVR2_D_OPB_MASK |
+         PVR2_D_LMB_MASK |
+         PVR2_I_OPB_MASK |
+         PVR2_I_LMB_MASK |
+         PVR2_FPU_EXC_MASK |
+         (cpu->cfg.use_fpu ? PVR2_USE_FPU_MASK : 0) |
+         (cpu->cfg.use_fpu > 1 ? PVR2_USE_FPU2_MASK : 0) |
+         (cpu->cfg.use_hw_mul ? PVR2_USE_HW_MUL_MASK : 0) |
+         (cpu->cfg.use_hw_mul > 1 ? PVR2_USE_MUL64_MASK : 0) |
+         (cpu->cfg.use_barrel ? PVR2_USE_BARREL_MASK : 0) |
+         (cpu->cfg.use_div ? PVR2_USE_DIV_MASK : 0) |
+         (cpu->cfg.use_msr_instr ? PVR2_USE_MSR_INSTR : 0) |
+         (cpu->cfg.use_pcmp_instr ? PVR2_USE_PCMP_INSTR : 0) |
+         (cpu->cfg.dopb_bus_exception ? PVR2_DOPB_BUS_EXC_MASK : 0) |
+         (cpu->cfg.iopb_bus_exception ? PVR2_IOPB_BUS_EXC_MASK : 0) |
+         (cpu->cfg.div_zero_exception ? PVR2_DIV_ZERO_EXC_MASK : 0) |
+         (cpu->cfg.illegal_opcode_exception ? PVR2_ILL_OPCODE_EXC_MASK : 0) |
+         (cpu->cfg.unaligned_exceptions ? PVR2_UNALIGNED_EXC_MASK : 0) |
+         (cpu->cfg.opcode_0_illegal ? PVR2_OPCODE_0x0_ILL_MASK : 0));
+
+    cpu->cfg.pvr_regs[5] |=
+        cpu->cfg.dcache_writeback ? PVR5_DCACHE_WRITEBACK_MASK : 0;
+
+    cpu->cfg.pvr_regs[10] =
+        (0x0c000000 | /* Default to spartan 3a dsp family.  */
+         (cpu->cfg.addr_size - 32) << PVR10_ASIZE_SHIFT);
+
+    cpu->cfg.pvr_regs[11] = ((cpu->cfg.use_mmu ? PVR11_USE_MMU : 0) |
+                             16 << 17);
+
+    cpu->cfg.mmu = 3;
+    cpu->cfg.mmu_tlb_access = 3;
+    cpu->cfg.mmu_zones = 16;
+    cpu->cfg.addr_mask = MAKE_64BIT_MASK(0, cpu->cfg.addr_size);
 
     mcc->parent_realize(dev, errp);
 }
@@ -244,11 +250,6 @@ static void mb_cpu_initfn(Object *obj)
 #endif
 }
 
-static const VMStateDescription vmstate_mb_cpu = {
-    .name = "cpu",
-    .unmigratable = 1,
-};
-
 static Property mb_properties[] = {
     DEFINE_PROP_UINT32("base-vectors", MicroBlazeCPU, cfg.base_vectors, 0),
     DEFINE_PROP_BOOL("use-stack-protection", MicroBlazeCPU, cfg.stackprot,
@@ -321,14 +322,15 @@ static void mb_cpu_class_init(ObjectClass *oc, void *data)
     cc->cpu_exec_interrupt = mb_cpu_exec_interrupt;
     cc->dump_state = mb_cpu_dump_state;
     cc->set_pc = mb_cpu_set_pc;
+    cc->synchronize_from_tb = mb_cpu_synchronize_from_tb;
     cc->gdb_read_register = mb_cpu_gdb_read_register;
     cc->gdb_write_register = mb_cpu_gdb_write_register;
     cc->tlb_fill = mb_cpu_tlb_fill;
 #ifndef CONFIG_USER_ONLY
     cc->do_transaction_failed = mb_cpu_transaction_failed;
     cc->get_phys_page_debug = mb_cpu_get_phys_page_debug;
-#endif
     dc->vmsd = &vmstate_mb_cpu;
+#endif
     device_class_set_props(dc, mb_properties);
     cc->gdb_num_core_regs = 32 + 27;
 
diff --git a/target/microblaze/cpu.h b/target/microblaze/cpu.h
index d11b6fa995..297b36879a 100644
--- a/target/microblaze/cpu.h
+++ b/target/microblaze/cpu.h
@@ -264,32 +264,68 @@ struct CPUMBState {
 /* MSR_UM               (1 << 11) */
 /* MSR_VM               (1 << 13) */
 /* ESR_ESS_MASK         [11:5]    -- unwind into iflags for unaligned excp */
+#define D_FLAG		(1 << 12)  /* Bit in ESR.  */
 #define DRTI_FLAG	(1 << 16)
 #define DRTE_FLAG	(1 << 17)
 #define DRTB_FLAG	(1 << 18)
-#define D_FLAG		(1 << 19)  /* Bit in ESR.  */
 
 /* TB dependent CPUMBState.  */
-#define IFLAGS_TB_MASK  (D_FLAG | IMM_FLAG | DRTI_FLAG | DRTE_FLAG | DRTB_FLAG)
+#define IFLAGS_TB_MASK  (D_FLAG | BIMM_FLAG | IMM_FLAG | \
+                         DRTI_FLAG | DRTE_FLAG | DRTB_FLAG)
 #define MSR_TB_MASK     (MSR_UM | MSR_VM | MSR_EE)
 
     uint32_t iflags;
 
 #if !defined(CONFIG_USER_ONLY)
     /* Unified MMU.  */
-    struct microblaze_mmu mmu;
+    MicroBlazeMMU mmu;
 #endif
 
     /* Fields up to this point are cleared by a CPU reset */
     struct {} end_reset_fields;
 
     /* These fields are preserved on reset.  */
-
-    struct {
-        uint32_t regs[13];
-    } pvr;
 };
 
+/*
+ * Microblaze Configuration Settings
+ *
+ * Note that the structure is sorted by type and size to minimize holes.
+ */
+typedef struct {
+    char *version;
+
+    uint64_t addr_mask;
+
+    uint32_t base_vectors;
+    uint32_t pvr_user2;
+    uint32_t pvr_regs[13];
+
+    uint8_t addr_size;
+    uint8_t use_fpu;
+    uint8_t use_hw_mul;
+    uint8_t pvr_user1;
+    uint8_t pvr;
+    uint8_t mmu;
+    uint8_t mmu_tlb_access;
+    uint8_t mmu_zones;
+
+    bool stackprot;
+    bool use_barrel;
+    bool use_div;
+    bool use_msr_instr;
+    bool use_pcmp_instr;
+    bool use_mmu;
+    bool dcache_writeback;
+    bool endi;
+    bool dopb_bus_exception;
+    bool iopb_bus_exception;
+    bool illegal_opcode_exception;
+    bool opcode_0_illegal;
+    bool div_zero_exception;
+    bool unaligned_exceptions;
+} MicroBlazeCPUConfig;
+
 /**
  * MicroBlazeCPU:
  * @env: #CPUMBState
@@ -304,32 +340,7 @@ struct MicroBlazeCPU {
 
     CPUNegativeOffsetState neg;
     CPUMBState env;
-
-    /* Microblaze Configuration Settings */
-    struct {
-        bool stackprot;
-        uint32_t base_vectors;
-        uint8_t addr_size;
-        uint8_t use_fpu;
-        uint8_t use_hw_mul;
-        bool use_barrel;
-        bool use_div;
-        bool use_msr_instr;
-        bool use_pcmp_instr;
-        bool use_mmu;
-        bool dcache_writeback;
-        bool endi;
-        bool dopb_bus_exception;
-        bool iopb_bus_exception;
-        bool illegal_opcode_exception;
-        bool opcode_0_illegal;
-        bool div_zero_exception;
-        bool unaligned_exceptions;
-        uint8_t pvr_user1;
-        uint32_t pvr_user2;
-        char *version;
-        uint8_t pvr;
-    } cfg;
+    MicroBlazeCPUConfig cfg;
 };
 
 
@@ -418,4 +429,8 @@ static inline int cpu_mmu_index(CPUMBState *env, bool ifetch)
     return MMU_KERNEL_IDX;
 }
 
+#ifndef CONFIG_USER_ONLY
+extern const VMStateDescription vmstate_mb_cpu;
+#endif
+
 #endif
diff --git a/target/microblaze/gdbstub.c b/target/microblaze/gdbstub.c
index 08d6a0e807..be39fd4540 100644
--- a/target/microblaze/gdbstub.c
+++ b/target/microblaze/gdbstub.c
@@ -78,7 +78,7 @@ int mb_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n)
         break;
     case GDB_PVR0 ... GDB_PVR11:
         /* PVR12 is intentionally skipped */
-        val = env->pvr.regs[n - GDB_PVR0];
+        val = cpu->cfg.pvr_regs[n - GDB_PVR0];
         break;
     case GDB_EDR:
         val = env->edr;
@@ -132,10 +132,6 @@ int mb_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
     case GDB_BTR:
         env->btr = tmp;
         break;
-    case GDB_PVR0 ... GDB_PVR11:
-        /* PVR12 is intentionally skipped */
-        env->pvr.regs[n - GDB_PVR0] = tmp;
-        break;
     case GDB_EDR:
         env->edr = tmp;
         break;
diff --git a/target/microblaze/helper.c b/target/microblaze/helper.c
index 48547385b0..3d6ce1b31b 100644
--- a/target/microblaze/helper.c
+++ b/target/microblaze/helper.c
@@ -52,7 +52,7 @@ bool mb_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
 {
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
     CPUMBState *env = &cpu->env;
-    struct microblaze_mmu_lookup lu;
+    MicroBlazeMMULookup lu;
     unsigned int hit;
     int prot;
 
@@ -64,7 +64,7 @@ bool mb_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
         return true;
     }
 
-    hit = mmu_translate(&env->mmu, &lu, address, access_type, mmu_idx);
+    hit = mmu_translate(cpu, &lu, address, access_type, mmu_idx);
     if (likely(hit)) {
         uint32_t vaddr = address & TARGET_PAGE_MASK;
         uint32_t paddr = lu.paddr + vaddr - lu.vaddr;
@@ -111,144 +111,122 @@ void mb_cpu_do_interrupt(CPUState *cs)
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
     CPUMBState *env = &cpu->env;
     uint32_t t, msr = mb_cpu_read_msr(env);
+    bool set_esr;
 
     /* IMM flag cannot propagate across a branch and into the dslot.  */
-    assert(!((env->iflags & D_FLAG) && (env->iflags & IMM_FLAG)));
+    assert((env->iflags & (D_FLAG | IMM_FLAG)) != (D_FLAG | IMM_FLAG));
+    /* BIMM flag cannot be set without D_FLAG. */
+    assert((env->iflags & (D_FLAG | BIMM_FLAG)) != BIMM_FLAG);
+    /* RTI flags are private to translate. */
     assert(!(env->iflags & (DRTI_FLAG | DRTE_FLAG | DRTB_FLAG)));
-    env->res_addr = RES_ADDR_NONE;
+
     switch (cs->exception_index) {
-        case EXCP_HW_EXCP:
-            if (!(env->pvr.regs[0] & PVR0_USE_EXC_MASK)) {
-                qemu_log_mask(LOG_GUEST_ERROR, "Exception raised on system without exceptions!\n");
-                return;
-            }
-
-            env->regs[17] = env->pc + 4;
-            env->esr &= ~(1 << 12);
-
-            /* Exception breaks branch + dslot sequence?  */
-            if (env->iflags & D_FLAG) {
-                env->esr |= 1 << 12 ;
-                env->btr = env->btarget;
-            }
-
-            /* Disable the MMU.  */
-            t = (msr & (MSR_VM | MSR_UM)) << 1;
-            msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
-            msr |= t;
-            /* Exception in progress.  */
-            msr |= MSR_EIP;
-            mb_cpu_write_msr(env, msr);
-
-            qemu_log_mask(CPU_LOG_INT,
-                          "hw exception at pc=%x ear=%" PRIx64 " "
-                          "esr=%x iflags=%x\n",
-                          env->pc, env->ear,
-                          env->esr, env->iflags);
-            log_cpu_state_mask(CPU_LOG_INT, cs, 0);
-            env->iflags &= ~(IMM_FLAG | D_FLAG);
-            env->pc = cpu->cfg.base_vectors + 0x20;
-            break;
-
-        case EXCP_MMU:
+    case EXCP_HW_EXCP:
+        if (!(cpu->cfg.pvr_regs[0] & PVR0_USE_EXC_MASK)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "Exception raised on system without exceptions!\n");
+            return;
+        }
+
+        qemu_log_mask(CPU_LOG_INT,
+                      "INT: HWE at pc=%08x msr=%08x iflags=%x\n",
+                      env->pc, msr, env->iflags);
+
+        /* Exception breaks branch + dslot sequence?  */
+        set_esr = true;
+        env->esr &= ~D_FLAG;
+        if (env->iflags & D_FLAG) {
+            env->esr |= D_FLAG;
+            env->btr = env->btarget;
+        }
+
+        /* Exception in progress. */
+        msr |= MSR_EIP;
+        env->regs[17] = env->pc + 4;
+        env->pc = cpu->cfg.base_vectors + 0x20;
+        break;
+
+    case EXCP_MMU:
+        qemu_log_mask(CPU_LOG_INT,
+                      "INT: MMU at pc=%08x msr=%08x "
+                      "ear=%" PRIx64 " iflags=%x\n",
+                      env->pc, msr, env->ear, env->iflags);
+
+        /* Exception breaks branch + dslot sequence? */
+        set_esr = true;
+        env->esr &= ~D_FLAG;
+        if (env->iflags & D_FLAG) {
+            env->esr |= D_FLAG;
+            env->btr = env->btarget;
+            /* Reexecute the branch. */
+            env->regs[17] = env->pc - (env->iflags & BIMM_FLAG ? 8 : 4);
+        } else if (env->iflags & IMM_FLAG) {
+            /* Reexecute the imm. */
+            env->regs[17] = env->pc - 4;
+        } else {
             env->regs[17] = env->pc;
+        }
 
-            qemu_log_mask(CPU_LOG_INT,
-                          "MMU exception at pc=%x iflags=%x ear=%" PRIx64 "\n",
-                          env->pc, env->iflags, env->ear);
-
-            env->esr &= ~(1 << 12);
-            /* Exception breaks branch + dslot sequence?  */
-            if (env->iflags & D_FLAG) {
-                env->esr |= 1 << 12 ;
-                env->btr = env->btarget;
-
-                /* Reexecute the branch.  */
-                env->regs[17] -= 4;
-                /* was the branch immprefixed?.  */
-                if (env->iflags & BIMM_FLAG) {
-                    env->regs[17] -= 4;
-                    log_cpu_state_mask(CPU_LOG_INT, cs, 0);
-                }
-            } else if (env->iflags & IMM_FLAG) {
-                env->regs[17] -= 4;
-            }
-
-            /* Disable the MMU.  */
-            t = (msr & (MSR_VM | MSR_UM)) << 1;
-            msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
-            msr |= t;
-            /* Exception in progress.  */
-            msr |= MSR_EIP;
-            mb_cpu_write_msr(env, msr);
-
-            qemu_log_mask(CPU_LOG_INT,
-                          "exception at pc=%x ear=%" PRIx64 " iflags=%x\n",
-                          env->pc, env->ear, env->iflags);
-            log_cpu_state_mask(CPU_LOG_INT, cs, 0);
-            env->iflags &= ~(IMM_FLAG | D_FLAG);
-            env->pc = cpu->cfg.base_vectors + 0x20;
-            break;
-
-        case EXCP_IRQ:
-            assert(!(msr & (MSR_EIP | MSR_BIP)));
-            assert(msr & MSR_IE);
-            assert(!(env->iflags & D_FLAG));
-
-            t = (msr & (MSR_VM | MSR_UM)) << 1;
-
-#if 0
-#include "disas/disas.h"
-
-/* Useful instrumentation when debugging interrupt issues in either
-   the models or in sw.  */
-            {
-                const char *sym;
-
-                sym = lookup_symbol(env->pc);
-                if (sym
-                    && (!strcmp("netif_rx", sym)
-                        || !strcmp("process_backlog", sym))) {
-
-                    qemu_log("interrupt at pc=%x msr=%x %x iflags=%x sym=%s\n",
-                             env->pc, msr, t, env->iflags, sym);
-
-                    log_cpu_state(cs, 0);
-                }
-            }
-#endif
-            qemu_log_mask(CPU_LOG_INT,
-                          "interrupt at pc=%x msr=%x %x iflags=%x\n",
-                          env->pc, msr, t, env->iflags);
-
-            msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM | MSR_IE);
-            msr |= t;
-            mb_cpu_write_msr(env, msr);
-
-            env->regs[14] = env->pc;
-            env->pc = cpu->cfg.base_vectors + 0x10;
-            //log_cpu_state_mask(CPU_LOG_INT, cs, 0);
-            break;
-
-        case EXCP_HW_BREAK:
-            assert(!(env->iflags & IMM_FLAG));
-            assert(!(env->iflags & D_FLAG));
-            t = (msr & (MSR_VM | MSR_UM)) << 1;
-            qemu_log_mask(CPU_LOG_INT,
-                          "break at pc=%x msr=%x %x iflags=%x\n",
-                          env->pc, msr, t, env->iflags);
-            log_cpu_state_mask(CPU_LOG_INT, cs, 0);
-            msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
-            msr |= t;
-            msr |= MSR_BIP;
-            env->regs[16] = env->pc;
-            env->pc = cpu->cfg.base_vectors + 0x18;
-            mb_cpu_write_msr(env, msr);
-            break;
-        default:
-            cpu_abort(cs, "unhandled exception type=%d\n",
-                      cs->exception_index);
-            break;
+        /* Exception in progress. */
+        msr |= MSR_EIP;
+        env->pc = cpu->cfg.base_vectors + 0x20;
+        break;
+
+    case EXCP_IRQ:
+        assert(!(msr & (MSR_EIP | MSR_BIP)));
+        assert(msr & MSR_IE);
+        assert(!(env->iflags & (D_FLAG | IMM_FLAG)));
+
+        qemu_log_mask(CPU_LOG_INT,
+                      "INT: DEV at pc=%08x msr=%08x iflags=%x\n",
+                      env->pc, msr, env->iflags);
+        set_esr = false;
+
+        /* Disable interrupts.  */
+        msr &= ~MSR_IE;
+        env->regs[14] = env->pc;
+        env->pc = cpu->cfg.base_vectors + 0x10;
+        break;
+
+    case EXCP_HW_BREAK:
+        assert(!(env->iflags & (D_FLAG | IMM_FLAG)));
+
+        qemu_log_mask(CPU_LOG_INT,
+                      "INT: BRK at pc=%08x msr=%08x iflags=%x\n",
+                      env->pc, msr, env->iflags);
+        set_esr = false;
+
+        /* Break in progress. */
+        msr |= MSR_BIP;
+        env->regs[16] = env->pc;
+        env->pc = cpu->cfg.base_vectors + 0x18;
+        break;
+
+    default:
+        cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index);
+        /* not reached */
+    }
+
+    /* Save previous mode, disable mmu, disable user-mode. */
+    t = (msr & (MSR_VM | MSR_UM)) << 1;
+    msr &= ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM);
+    msr |= t;
+    mb_cpu_write_msr(env, msr);
+
+    env->res_addr = RES_ADDR_NONE;
+    env->iflags = 0;
+
+    if (!set_esr) {
+        qemu_log_mask(CPU_LOG_INT,
+                      "         to pc=%08x msr=%08x\n", env->pc, msr);
+    } else if (env->esr & D_FLAG) {
+        qemu_log_mask(CPU_LOG_INT,
+                      "         to pc=%08x msr=%08x esr=%04x btr=%08x\n",
+                      env->pc, msr, env->esr, env->btr);
+    } else {
+        qemu_log_mask(CPU_LOG_INT,
+                      "         to pc=%08x msr=%08x esr=%04x\n",
+                      env->pc, msr, env->esr);
     }
 }
 
@@ -257,12 +235,12 @@ hwaddr mb_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
     CPUMBState *env = &cpu->env;
     target_ulong vaddr, paddr = 0;
-    struct microblaze_mmu_lookup lu;
+    MicroBlazeMMULookup lu;
     int mmu_idx = cpu_mmu_index(env, false);
     unsigned int hit;
 
     if (mmu_idx != MMU_NOMMU_IDX) {
-        hit = mmu_translate(&env->mmu, &lu, addr, 0, 0);
+        hit = mmu_translate(cpu, &lu, addr, 0, 0);
         if (hit) {
             vaddr = addr & TARGET_PAGE_MASK;
             paddr = lu.paddr + vaddr - lu.vaddr;
diff --git a/target/microblaze/machine.c b/target/microblaze/machine.c
new file mode 100644
index 0000000000..acdb8d0474
--- /dev/null
+++ b/target/microblaze/machine.c
@@ -0,0 +1,106 @@
+/*
+ *  Microblaze VMState for qemu.
+ *
+ *  Copyright (c) 2020 Linaro, Ltd.
+ *
+ * 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 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 "cpu.h"
+#include "migration/cpu.h"
+
+
+static VMStateField vmstate_mmu_fields[] = {
+    VMSTATE_UINT64_2DARRAY(rams, MicroBlazeMMU, 2, TLB_ENTRIES),
+    VMSTATE_UINT8_ARRAY(tids, MicroBlazeMMU, TLB_ENTRIES),
+    VMSTATE_UINT32_ARRAY(regs, MicroBlazeMMU, 3),
+    VMSTATE_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_mmu = {
+    .name = "mmu",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = vmstate_mmu_fields,
+};
+
+static int get_msr(QEMUFile *f, void *opaque, size_t size,
+                   const VMStateField *field)
+{
+    CPUMBState *env = container_of(opaque, CPUMBState, msr);
+
+    mb_cpu_write_msr(env, qemu_get_be32(f));
+    return 0;
+}
+
+static int put_msr(QEMUFile *f, void *opaque, size_t size,
+                   const VMStateField *field, QJSON *vmdesc)
+{
+    CPUMBState *env = container_of(opaque, CPUMBState, msr);
+
+    qemu_put_be32(f, mb_cpu_read_msr(env));
+    return 0;
+}
+
+static const VMStateInfo vmstate_msr = {
+    .name = "msr",
+    .get = get_msr,
+    .put = put_msr,
+};
+
+static VMStateField vmstate_env_fields[] = {
+    VMSTATE_UINT32_ARRAY(regs, CPUMBState, 32),
+
+    VMSTATE_UINT32(pc, CPUMBState),
+    VMSTATE_SINGLE(msr, CPUMBState, 0, vmstate_msr, uint32_t),
+    VMSTATE_UINT32(esr, CPUMBState),
+    VMSTATE_UINT32(fsr, CPUMBState),
+    VMSTATE_UINT32(btr, CPUMBState),
+    VMSTATE_UINT32(edr, CPUMBState),
+    VMSTATE_UINT32(slr, CPUMBState),
+    VMSTATE_UINT32(shr, CPUMBState),
+    VMSTATE_UINT64(ear, CPUMBState),
+
+    VMSTATE_UINT32(btarget, CPUMBState),
+    VMSTATE_UINT32(imm, CPUMBState),
+    VMSTATE_UINT32(iflags, CPUMBState),
+
+    VMSTATE_UINT32(res_val, CPUMBState),
+    VMSTATE_UINTTL(res_addr, CPUMBState),
+
+    VMSTATE_STRUCT(mmu, CPUMBState, 0, vmstate_mmu, MicroBlazeMMU),
+
+    VMSTATE_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_env = {
+    .name = "env",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = vmstate_env_fields,
+};
+
+static VMStateField vmstate_cpu_fields[] = {
+    VMSTATE_CPU(),
+    VMSTATE_STRUCT(env, MicroBlazeCPU, 1, vmstate_env, CPUMBState),
+    VMSTATE_END_OF_LIST()
+};
+
+const VMStateDescription vmstate_mb_cpu = {
+    .name = "cpu",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = vmstate_cpu_fields,
+};
diff --git a/target/microblaze/meson.build b/target/microblaze/meson.build
index 639c3f73a8..05ee0ec163 100644
--- a/target/microblaze/meson.build
+++ b/target/microblaze/meson.build
@@ -11,7 +11,10 @@ microblaze_ss.add(files(
 ))
 
 microblaze_softmmu_ss = ss.source_set()
-microblaze_softmmu_ss.add(files('mmu.c'))
+microblaze_softmmu_ss.add(files(
+  'mmu.c',
+  'machine.c',
+))
 
 target_arch += {'microblaze': microblaze_ss}
 target_softmmu_arch += {'microblaze': microblaze_softmmu_ss}
diff --git a/target/microblaze/mmu.c b/target/microblaze/mmu.c
index 6e583d78d9..1dbbb271c4 100644
--- a/target/microblaze/mmu.c
+++ b/target/microblaze/mmu.c
@@ -35,7 +35,7 @@ static unsigned int tlb_decode_size(unsigned int f)
 static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
 {
     CPUState *cs = env_cpu(env);
-    struct microblaze_mmu *mmu = &env->mmu;
+    MicroBlazeMMU *mmu = &env->mmu;
     unsigned int tlb_size;
     uint32_t tlb_tag, end, t;
 
@@ -55,7 +55,7 @@ static void mmu_flush_idx(CPUMBState *env, unsigned int idx)
 
 static void mmu_change_pid(CPUMBState *env, unsigned int newpid) 
 {
-    struct microblaze_mmu *mmu = &env->mmu;
+    MicroBlazeMMU *mmu = &env->mmu;
     unsigned int i;
     uint32_t t;
 
@@ -73,10 +73,10 @@ static void mmu_change_pid(CPUMBState *env, unsigned int newpid)
 }
 
 /* rw - 0 = read, 1 = write, 2 = fetch.  */
-unsigned int mmu_translate(struct microblaze_mmu *mmu,
-                           struct microblaze_mmu_lookup *lu,
+unsigned int mmu_translate(MicroBlazeCPU *cpu, MicroBlazeMMULookup *lu,
                            target_ulong vaddr, int rw, int mmu_idx)
 {
+    MicroBlazeMMU *mmu = &cpu->env.mmu;
     unsigned int i, hit = 0;
     unsigned int tlb_ex = 0, tlb_wr = 0, tlb_zsel;
     uint64_t tlb_tag, tlb_rpn, mask;
@@ -115,13 +115,13 @@ unsigned int mmu_translate(struct microblaze_mmu *mmu,
             t0 = mmu->regs[MMU_R_ZPR] >> (30 - (tlb_zsel * 2));
             t0 &= 0x3;
 
-            if (tlb_zsel > mmu->c_mmu_zones) {
+            if (tlb_zsel > cpu->cfg.mmu_zones) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "tlb zone select out of range! %d\n", tlb_zsel);
                 t0 = 1; /* Ignore.  */
             }
 
-            if (mmu->c_mmu == 1) {
+            if (cpu->cfg.mmu == 1) {
                 t0 = 1; /* Zones are disabled.  */
             }
 
@@ -158,7 +158,7 @@ unsigned int mmu_translate(struct microblaze_mmu *mmu,
             tlb_rpn = d & TLB_RPN_MASK;
 
             lu->vaddr = tlb_tag;
-            lu->paddr = tlb_rpn & mmu->c_addr_mask;
+            lu->paddr = tlb_rpn & cpu->cfg.addr_mask;
             lu->size = tlb_size;
             lu->err = ERR_HIT;
             lu->idx = i;
@@ -176,10 +176,11 @@ done:
 /* Writes/reads to the MMU's special regs end up here.  */
 uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
 {
+    MicroBlazeCPU *cpu = env_archcpu(env);
     unsigned int i;
     uint32_t r = 0;
 
-    if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) {
+    if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
         qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
         return 0;
     }
@@ -192,7 +193,7 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
         /* Reads to HI/LO trig reads from the mmu rams.  */
         case MMU_R_TLBLO:
         case MMU_R_TLBHI:
-            if (!(env->mmu.c_mmu_tlb_access & 1)) {
+            if (!(cpu->cfg.mmu_tlb_access & 1)) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "Invalid access to MMU reg %d\n", rn);
                 return 0;
@@ -205,7 +206,7 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
             break;
         case MMU_R_PID:
         case MMU_R_ZPR:
-            if (!(env->mmu.c_mmu_tlb_access & 1)) {
+            if (!(cpu->cfg.mmu_tlb_access & 1)) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "Invalid access to MMU reg %d\n", rn);
                 return 0;
@@ -228,12 +229,14 @@ uint32_t mmu_read(CPUMBState *env, bool ext, uint32_t rn)
 
 void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
 {
+    MicroBlazeCPU *cpu = env_archcpu(env);
     uint64_t tmp64;
     unsigned int i;
+
     qemu_log_mask(CPU_LOG_MMU,
                   "%s rn=%d=%x old=%x\n", __func__, rn, v, env->mmu.regs[rn]);
 
-    if (env->mmu.c_mmu < 2 || !env->mmu.c_mmu_tlb_access) {
+    if (cpu->cfg.mmu < 2 || !cpu->cfg.mmu_tlb_access) {
         qemu_log_mask(LOG_GUEST_ERROR, "MMU access on MMU-less system\n");
         return;
     }
@@ -259,7 +262,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
             env->mmu.rams[rn & 1][i] = deposit64(tmp64, ext * 32, 32, v);
             break;
         case MMU_R_ZPR:
-            if (env->mmu.c_mmu_tlb_access <= 1) {
+            if (cpu->cfg.mmu_tlb_access <= 1) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "Invalid access to MMU reg %d\n", rn);
                 return;
@@ -273,7 +276,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
             env->mmu.regs[rn] = v;
             break;
         case MMU_R_PID:
-            if (env->mmu.c_mmu_tlb_access <= 1) {
+            if (cpu->cfg.mmu_tlb_access <= 1) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "Invalid access to MMU reg %d\n", rn);
                 return;
@@ -290,17 +293,17 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
             break;
         case MMU_R_TLBSX:
         {
-            struct microblaze_mmu_lookup lu;
+            MicroBlazeMMULookup lu;
             int hit;
 
-            if (env->mmu.c_mmu_tlb_access <= 1) {
+            if (cpu->cfg.mmu_tlb_access <= 1) {
                 qemu_log_mask(LOG_GUEST_ERROR,
                               "Invalid access to MMU reg %d\n", rn);
                 return;
             }
 
-            hit = mmu_translate(&env->mmu, &lu,
-                                v & TLB_EPN_MASK, 0, cpu_mmu_index(env, false));
+            hit = mmu_translate(cpu, &lu, v & TLB_EPN_MASK,
+                                0, cpu_mmu_index(env, false));
             if (hit) {
                 env->mmu.regs[MMU_R_TLBX] = lu.idx;
             } else {
@@ -314,7 +317,7 @@ void mmu_write(CPUMBState *env, bool ext, uint32_t rn, uint32_t v)
    }
 }
 
-void mmu_init(struct microblaze_mmu *mmu)
+void mmu_init(MicroBlazeMMU *mmu)
 {
     int i;
     for (i = 0; i < ARRAY_SIZE(mmu->regs); i++) {
diff --git a/target/microblaze/mmu.h b/target/microblaze/mmu.h
index 75e5301c79..7d0fbb8341 100644
--- a/target/microblaze/mmu.h
+++ b/target/microblaze/mmu.h
@@ -63,23 +63,16 @@
 
 #define TLB_ENTRIES    64
 
-struct microblaze_mmu
-{
+typedef struct {
     /* Data and tag brams.  */
     uint64_t rams[2][TLB_ENTRIES];
     /* We keep a separate ram for the tids to avoid the 48 bit tag width.  */
     uint8_t tids[TLB_ENTRIES];
     /* Control flops.  */
     uint32_t regs[3];
+} MicroBlazeMMU;
 
-    int c_mmu;
-    int c_mmu_tlb_access;
-    int c_mmu_zones;
-    uint64_t c_addr_mask; /* Mask to apply to physical addresses.  */
-};
-
-struct microblaze_mmu_lookup
-{
+typedef struct {
     uint32_t paddr;
     uint32_t vaddr;
     unsigned int size;
@@ -88,13 +81,12 @@ struct microblaze_mmu_lookup
     enum {
         ERR_PROT, ERR_MISS, ERR_HIT
     } err;
-};
+} MicroBlazeMMULookup;
 
-unsigned int mmu_translate(struct microblaze_mmu *mmu,
-                           struct microblaze_mmu_lookup *lu,
+unsigned int mmu_translate(MicroBlazeCPU *cpu, MicroBlazeMMULookup *lu,
                            target_ulong vaddr, int rw, int mmu_idx);
 uint32_t mmu_read(CPUMBState *env, bool ea, uint32_t rn);
 void mmu_write(CPUMBState *env, bool ea, uint32_t rn, uint32_t v);
-void mmu_init(struct microblaze_mmu *mmu);
+void mmu_init(MicroBlazeMMU *mmu);
 
 #endif
diff --git a/target/microblaze/op_helper.c b/target/microblaze/op_helper.c
index 4614e99db3..757f3ff04b 100644
--- a/target/microblaze/op_helper.c
+++ b/target/microblaze/op_helper.c
@@ -134,7 +134,7 @@ static void update_fpu_flags(CPUMBState *env, int flags, uintptr_t ra)
         raise = 1;
     }
     if (raise
-        && (env->pvr.regs[2] & PVR2_FPU_EXC_MASK)
+        && (env_archcpu(env)->cfg.pvr_regs[2] & PVR2_FPU_EXC_MASK)
         && (env->msr & MSR_EE)) {
         raise_fpu_exception(env, ra);
     }
diff --git a/target/microblaze/translate.c b/target/microblaze/translate.c
index a377818b5e..abfcc7e6c8 100644
--- a/target/microblaze/translate.c
+++ b/target/microblaze/translate.c
@@ -37,7 +37,12 @@
 
 /* is_jmp field values */
 #define DISAS_JUMP    DISAS_TARGET_0 /* only pc was modified dynamically */
-#define DISAS_UPDATE  DISAS_TARGET_1 /* cpu state was modified dynamically */
+#define DISAS_EXIT    DISAS_TARGET_1 /* all cpu state modified dynamically */
+
+/* cpu state besides pc was modified dynamically; update pc to next */
+#define DISAS_EXIT_NEXT DISAS_TARGET_2
+/* cpu state besides pc was modified dynamically; update pc to btarget */
+#define DISAS_EXIT_JUMP DISAS_TARGET_3
 
 static TCGv_i32 cpu_R[32];
 static TCGv_i32 cpu_pc;
@@ -55,7 +60,7 @@ static TCGv_i32 cpu_res_val;
 /* This is the state at translation time.  */
 typedef struct DisasContext {
     DisasContextBase base;
-    MicroBlazeCPU *cpu;
+    const MicroBlazeCPUConfig *cfg;
 
     /* TCG op of the current insn_start.  */
     TCGOp *insn_start;
@@ -65,7 +70,6 @@ typedef struct DisasContext {
 
     /* Decoder.  */
     uint32_t ext_imm;
-    unsigned int cpustate_changed;
     unsigned int tb_flags;
     unsigned int tb_flags_to_set;
     int mem_index;
@@ -91,8 +95,8 @@ static int typeb_imm(DisasContext *dc, int x)
 static void t_sync_flags(DisasContext *dc)
 {
     /* Synch the tb dependent flags between translator and runtime.  */
-    if ((dc->tb_flags ^ dc->base.tb->flags) & ~MSR_TB_MASK) {
-        tcg_gen_movi_i32(cpu_iflags, dc->tb_flags & ~MSR_TB_MASK);
+    if ((dc->tb_flags ^ dc->base.tb->flags) & IFLAGS_TB_MASK) {
+        tcg_gen_movi_i32(cpu_iflags, dc->tb_flags & IFLAGS_TB_MASK);
     }
 }
 
@@ -143,7 +147,7 @@ static void gen_goto_tb(DisasContext *dc, int n, target_ulong dest)
         tcg_gen_exit_tb(dc->base.tb, n);
     } else {
         tcg_gen_movi_i32(cpu_pc, dest);
-        tcg_gen_exit_tb(NULL, 0);
+        tcg_gen_lookup_and_goto_ptr();
     }
     dc->base.is_jmp = DISAS_NORETURN;
 }
@@ -155,7 +159,7 @@ static void gen_goto_tb(DisasContext *dc, int n, target_ulong dest)
 static bool trap_illegal(DisasContext *dc, bool cond)
 {
     if (cond && (dc->tb_flags & MSR_EE)
-        && dc->cpu->cfg.illegal_opcode_exception) {
+        && dc->cfg->illegal_opcode_exception) {
         gen_raise_hw_excp(dc, ESR_EC_ILLEGAL_OP);
     }
     return cond;
@@ -175,6 +179,21 @@ static bool trap_userspace(DisasContext *dc, bool cond)
     return cond_user;
 }
 
+/*
+ * Return true, and log an error, if the current insn is
+ * within a delay slot.
+ */
+static bool invalid_delay_slot(DisasContext *dc, const char *insn_type)
+{
+    if (dc->tb_flags & D_FLAG) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "Invalid insn in delay slot: %s at %08x\n",
+                      insn_type, (uint32_t)dc->base.pc_next);
+        return true;
+    }
+    return false;
+}
+
 static TCGv_i32 reg_for_read(DisasContext *dc, int reg)
 {
     if (likely(reg != 0)) {
@@ -272,7 +291,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
 
 #define DO_TYPEA_CFG(NAME, CFG, SE, FN) \
     static bool trans_##NAME(DisasContext *dc, arg_typea *a) \
-    { return dc->cpu->cfg.CFG && do_typea(dc, a, SE, FN); }
+    { return dc->cfg->CFG && do_typea(dc, a, SE, FN); }
 
 #define DO_TYPEA0(NAME, SE, FN) \
     static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \
@@ -280,7 +299,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
 
 #define DO_TYPEA0_CFG(NAME, CFG, SE, FN) \
     static bool trans_##NAME(DisasContext *dc, arg_typea0 *a) \
-    { return dc->cpu->cfg.CFG && do_typea0(dc, a, SE, FN); }
+    { return dc->cfg->CFG && do_typea0(dc, a, SE, FN); }
 
 #define DO_TYPEBI(NAME, SE, FNI) \
     static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
@@ -288,7 +307,7 @@ static bool do_typeb_val(DisasContext *dc, arg_typeb *arg, bool side_effects,
 
 #define DO_TYPEBI_CFG(NAME, CFG, SE, FNI) \
     static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
-    { return dc->cpu->cfg.CFG && do_typeb_imm(dc, a, SE, FNI); }
+    { return dc->cfg->CFG && do_typeb_imm(dc, a, SE, FNI); }
 
 #define DO_TYPEBV(NAME, SE, FN) \
     static bool trans_##NAME(DisasContext *dc, arg_typeb *a) \
@@ -496,6 +515,9 @@ DO_TYPEA_CFG(idivu, use_div, true, gen_idivu)
 
 static bool trans_imm(DisasContext *dc, arg_imm *arg)
 {
+    if (invalid_delay_slot(dc, "imm")) {
+        return true;
+    }
     dc->ext_imm = arg->imm << 16;
     tcg_gen_movi_i32(cpu_imm, dc->ext_imm);
     dc->tb_flags_to_set = IMM_FLAG;
@@ -661,7 +683,7 @@ static TCGv compute_ldst_addr_typea(DisasContext *dc, int ra, int rb)
         tcg_gen_movi_tl(ret, 0);
     }
 
-    if ((ra == 1 || rb == 1) && dc->cpu->cfg.stackprot) {
+    if ((ra == 1 || rb == 1) && dc->cfg->stackprot) {
         gen_helper_stackprot(cpu_env, ret);
     }
     return ret;
@@ -681,7 +703,7 @@ static TCGv compute_ldst_addr_typeb(DisasContext *dc, int ra, int imm)
         tcg_gen_movi_tl(ret, (uint32_t)imm);
     }
 
-    if (ra == 1 && dc->cpu->cfg.stackprot) {
+    if (ra == 1 && dc->cfg->stackprot) {
         gen_helper_stackprot(cpu_env, ret);
     }
     return ret;
@@ -690,7 +712,7 @@ static TCGv compute_ldst_addr_typeb(DisasContext *dc, int ra, int imm)
 #ifndef CONFIG_USER_ONLY
 static TCGv compute_ldst_addr_ea(DisasContext *dc, int ra, int rb)
 {
-    int addr_size = dc->cpu->cfg.addr_size;
+    int addr_size = dc->cfg->addr_size;
     TCGv ret = tcg_temp_new();
 
     if (addr_size == 32 || ra == 0) {
@@ -750,7 +772,7 @@ static bool do_load(DisasContext *dc, int rd, TCGv addr, MemOp mop,
 
     if (size > MO_8 &&
         (dc->tb_flags & MSR_EE) &&
-        dc->cpu->cfg.unaligned_exceptions) {
+        dc->cfg->unaligned_exceptions) {
         record_unaligned_ess(dc, rd, size, false);
         mop |= MO_ALIGN;
     }
@@ -896,7 +918,7 @@ static bool do_store(DisasContext *dc, int rd, TCGv addr, MemOp mop,
 
     if (size > MO_8 &&
         (dc->tb_flags & MSR_EE) &&
-        dc->cpu->cfg.unaligned_exceptions) {
+        dc->cfg->unaligned_exceptions) {
         record_unaligned_ess(dc, rd, size, true);
         mop |= MO_ALIGN;
     }
@@ -1063,6 +1085,9 @@ static bool do_branch(DisasContext *dc, int dest_rb, int dest_imm,
 {
     uint32_t add_pc;
 
+    if (invalid_delay_slot(dc, "branch")) {
+        return true;
+    }
     if (delay) {
         setup_dslot(dc, dest_rb < 0);
     }
@@ -1102,6 +1127,9 @@ static bool do_bcc(DisasContext *dc, int dest_rb, int dest_imm,
 {
     TCGv_i32 zero, next;
 
+    if (invalid_delay_slot(dc, "bcc")) {
+        return true;
+    }
     if (delay) {
         setup_dslot(dc, dest_rb < 0);
     }
@@ -1154,6 +1182,10 @@ static bool trans_brk(DisasContext *dc, arg_typea_br *arg)
     if (trap_userspace(dc, true)) {
         return true;
     }
+    if (invalid_delay_slot(dc, "brk")) {
+        return true;
+    }
+
     tcg_gen_mov_i32(cpu_pc, reg_for_read(dc, arg->rb));
     if (arg->rd) {
         tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next);
@@ -1161,7 +1193,7 @@ static bool trans_brk(DisasContext *dc, arg_typea_br *arg)
     tcg_gen_ori_i32(cpu_msr, cpu_msr, MSR_BIP);
     tcg_gen_movi_tl(cpu_res_addr, -1);
 
-    dc->base.is_jmp = DISAS_UPDATE;
+    dc->base.is_jmp = DISAS_EXIT;
     return true;
 }
 
@@ -1172,6 +1204,10 @@ static bool trans_brki(DisasContext *dc, arg_typeb_br *arg)
     if (trap_userspace(dc, imm != 0x8 && imm != 0x18)) {
         return true;
     }
+    if (invalid_delay_slot(dc, "brki")) {
+        return true;
+    }
+
     tcg_gen_movi_i32(cpu_pc, imm);
     if (arg->rd) {
         tcg_gen_movi_i32(cpu_R[arg->rd], dc->base.pc_next);
@@ -1202,7 +1238,7 @@ static bool trans_brki(DisasContext *dc, arg_typeb_br *arg)
                          ~(MSR_VMS | MSR_UMS | MSR_VM | MSR_UM));
     }
     tcg_gen_ori_i32(cpu_msr, cpu_msr, msr_to_set);
-    dc->base.is_jmp = DISAS_UPDATE;
+    dc->base.is_jmp = DISAS_EXIT;
 #endif
 
     return true;
@@ -1212,6 +1248,11 @@ static bool trans_mbar(DisasContext *dc, arg_mbar *arg)
 {
     int mbar_imm = arg->imm;
 
+    /* Note that mbar is a specialized branch instruction. */
+    if (invalid_delay_slot(dc, "mbar")) {
+        return true;
+    }
+
     /* Data access memory barrier.  */
     if ((mbar_imm & 2) == 0) {
         tcg_gen_mb(TCG_BAR_SC | TCG_MO_ALL);
@@ -1250,7 +1291,7 @@ static bool trans_mbar(DisasContext *dc, arg_mbar *arg)
      *
      * Therefore, choose to end the TB always.
      */
-    dc->cpustate_changed = 1;
+    dc->base.is_jmp = DISAS_EXIT_NEXT;
     return true;
 }
 
@@ -1259,6 +1300,10 @@ static bool do_rts(DisasContext *dc, arg_typeb_bc *arg, int to_set)
     if (trap_userspace(dc, to_set)) {
         return true;
     }
+    if (invalid_delay_slot(dc, "rts")) {
+        return true;
+    }
+
     dc->tb_flags_to_set |= to_set;
     setup_dslot(dc, true);
 
@@ -1280,7 +1325,7 @@ DO_RTS(rtsd, 0)
 static bool trans_zero(DisasContext *dc, arg_zero *arg)
 {
     /* If opcode_0_illegal, trap.  */
-    if (dc->cpu->cfg.opcode_0_illegal) {
+    if (dc->cfg->opcode_0_illegal) {
         trap_illegal(dc, true);
         return true;
     }
@@ -1302,19 +1347,6 @@ static void msr_read(DisasContext *dc, TCGv_i32 d)
     tcg_temp_free_i32(t);
 }
 
-#ifndef CONFIG_USER_ONLY
-static void msr_write(DisasContext *dc, TCGv_i32 v)
-{
-    dc->cpustate_changed = 1;
-
-    /* Install MSR_C.  */
-    tcg_gen_extract_i32(cpu_msr_c, v, 2, 1);
-
-    /* Clear MSR_C and MSR_CC; MSR_PVR is not writable, and is always clear. */
-    tcg_gen_andi_i32(cpu_msr, v, ~(MSR_C | MSR_CC | MSR_PVR));
-}
-#endif
-
 static bool do_msrclrset(DisasContext *dc, arg_type_msr *arg, bool set)
 {
     uint32_t imm = arg->imm;
@@ -1347,7 +1379,7 @@ static bool do_msrclrset(DisasContext *dc, arg_type_msr *arg, bool set)
         } else {
             tcg_gen_andi_i32(cpu_msr, cpu_msr, ~imm);
         }
-        dc->cpustate_changed = 1;
+        dc->base.is_jmp = DISAS_EXIT_NEXT;
     }
     return true;
 }
@@ -1380,7 +1412,13 @@ static bool trans_mts(DisasContext *dc, arg_mts *arg)
     TCGv_i32 src = reg_for_read(dc, arg->ra);
     switch (arg->rs) {
     case SR_MSR:
-        msr_write(dc, src);
+        /* Install MSR_C.  */
+        tcg_gen_extract_i32(cpu_msr_c, src, 2, 1);
+        /*
+         * Clear MSR_C and MSR_CC;
+         * MSR_PVR is not writable, and is always clear.
+         */
+        tcg_gen_andi_i32(cpu_msr, src, ~(MSR_C | MSR_CC | MSR_PVR));
         break;
     case SR_FSR:
         tcg_gen_st_i32(src, cpu_env, offsetof(CPUMBState, fsr));
@@ -1412,7 +1450,7 @@ static bool trans_mts(DisasContext *dc, arg_mts *arg)
         qemu_log_mask(LOG_GUEST_ERROR, "Invalid mts reg 0x%x\n", arg->rs);
         return true;
     }
-    dc->cpustate_changed = 1;
+    dc->base.is_jmp = DISAS_EXIT_NEXT;
     return true;
 #endif
 }
@@ -1501,7 +1539,8 @@ static bool trans_mfs(DisasContext *dc, arg_mfs *arg)
 
     case 0x2000 ... 0x200c:
         tcg_gen_ld_i32(dest, cpu_env,
-                       offsetof(CPUMBState, pvr.regs[arg->rs - 0x2000]));
+                       offsetof(MicroBlazeCPU, cfg.pvr_regs[arg->rs - 0x2000])
+                       - offsetof(MicroBlazeCPU, env));
         break;
     default:
         qemu_log_mask(LOG_GUEST_ERROR, "Invalid mfs reg 0x%x\n", arg->rs);
@@ -1521,7 +1560,6 @@ static void do_rti(DisasContext *dc)
     tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
 
     tcg_temp_free_i32(tmp);
-    dc->tb_flags &= ~DRTI_FLAG;
 }
 
 static void do_rtb(DisasContext *dc)
@@ -1534,7 +1572,6 @@ static void do_rtb(DisasContext *dc)
     tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
 
     tcg_temp_free_i32(tmp);
-    dc->tb_flags &= ~DRTB_FLAG;
 }
 
 static void do_rte(DisasContext *dc)
@@ -1548,7 +1585,6 @@ static void do_rte(DisasContext *dc)
     tcg_gen_or_i32(cpu_msr, cpu_msr, tmp);
 
     tcg_temp_free_i32(tmp);
-    dc->tb_flags &= ~DRTE_FLAG;
 }
 
 /* Insns connected to FSL or AXI stream attached devices.  */
@@ -1622,9 +1658,8 @@ static void mb_tr_init_disas_context(DisasContextBase *dcb, CPUState *cs)
     MicroBlazeCPU *cpu = MICROBLAZE_CPU(cs);
     int bound;
 
-    dc->cpu = cpu;
+    dc->cfg = &cpu->cfg;
     dc->tb_flags = dc->base.tb->flags;
-    dc->cpustate_changed = 0;
     dc->ext_imm = dc->base.tb->cs_base;
     dc->r0 = NULL;
     dc->r0_set = false;
@@ -1700,20 +1735,47 @@ static void mb_tr_translate_insn(DisasContextBase *dcb, CPUState *cs)
     dc->base.pc_next += 4;
 
     if (dc->jmp_cond != TCG_COND_NEVER && !(dc->tb_flags & D_FLAG)) {
-        if (dc->tb_flags & DRTI_FLAG) {
-            do_rti(dc);
-        } else if (dc->tb_flags & DRTB_FLAG) {
-            do_rtb(dc);
-        } else if (dc->tb_flags & DRTE_FLAG) {
-            do_rte(dc);
+        /*
+         * Finish any return-from branch.
+         */
+        uint32_t rt_ibe = dc->tb_flags & (DRTI_FLAG | DRTB_FLAG | DRTE_FLAG);
+        if (unlikely(rt_ibe != 0)) {
+            dc->tb_flags &= ~(DRTI_FLAG | DRTB_FLAG | DRTE_FLAG);
+            if (rt_ibe & DRTI_FLAG) {
+                do_rti(dc);
+            } else if (rt_ibe & DRTB_FLAG) {
+                do_rtb(dc);
+            } else {
+                do_rte(dc);
+            }
         }
-        dc->base.is_jmp = DISAS_JUMP;
-    }
 
-    /* Force an exit if the per-tb cpu state has changed.  */
-    if (dc->base.is_jmp == DISAS_NEXT && dc->cpustate_changed) {
-        dc->base.is_jmp = DISAS_UPDATE;
-        tcg_gen_movi_i32(cpu_pc, dc->base.pc_next);
+        /* Complete the branch, ending the TB. */
+        switch (dc->base.is_jmp) {
+        case DISAS_NORETURN:
+            /*
+             * E.g. illegal insn in a delay slot.  We've already exited
+             * and will handle D_FLAG in mb_cpu_do_interrupt.
+             */
+            break;
+        case DISAS_NEXT:
+            /*
+             * Normal insn a delay slot.
+             * However, the return-from-exception type insns should
+             * return to the main loop, as they have adjusted MSR.
+             */
+            dc->base.is_jmp = (rt_ibe ? DISAS_EXIT_JUMP : DISAS_JUMP);
+            break;
+        case DISAS_EXIT_NEXT:
+            /*
+             * E.g. mts insn in a delay slot.  Continue with btarget,
+             * but still return to the main loop.
+             */
+            dc->base.is_jmp = DISAS_EXIT_JUMP;
+            break;
+        default:
+            g_assert_not_reached();
+        }
     }
 }
 
@@ -1733,13 +1795,15 @@ static void mb_tr_tb_stop(DisasContextBase *dcb, CPUState *cs)
         gen_goto_tb(dc, 0, dc->base.pc_next);
         return;
 
-    case DISAS_UPDATE:
-        if (unlikely(cs->singlestep_enabled)) {
-            gen_raise_exception(dc, EXCP_DEBUG);
-        } else {
-            tcg_gen_exit_tb(NULL, 0);
-        }
-        return;
+    case DISAS_EXIT:
+        break;
+    case DISAS_EXIT_NEXT:
+        tcg_gen_movi_i32(cpu_pc, dc->base.pc_next);
+        break;
+    case DISAS_EXIT_JUMP:
+        tcg_gen_mov_i32(cpu_pc, cpu_btarget);
+        tcg_gen_discard_i32(cpu_btarget);
+        break;
 
     case DISAS_JUMP:
         if (dc->jmp_dest != -1 && !cs->singlestep_enabled) {
@@ -1774,13 +1838,20 @@ static void mb_tr_tb_stop(DisasContextBase *dcb, CPUState *cs)
         if (unlikely(cs->singlestep_enabled)) {
             gen_raise_exception(dc, EXCP_DEBUG);
         } else {
-            tcg_gen_exit_tb(NULL, 0);
+            tcg_gen_lookup_and_goto_ptr();
         }
         return;
 
     default:
         g_assert_not_reached();
     }
+
+    /* Finish DISAS_EXIT_* */
+    if (unlikely(cs->singlestep_enabled)) {
+        gen_raise_exception(dc, EXCP_DEBUG);
+    } else {
+        tcg_gen_exit_tb(NULL, 0);
+    }
 }
 
 static void mb_tr_disas_log(const DisasContextBase *dcb, CPUState *cs)
@@ -1848,11 +1919,6 @@ void mb_cpu_dump_state(CPUState *cs, FILE *f, int flags)
                  env->esr, env->fsr, env->btr, env->edr,
                  env->ear, env->slr, env->shr);
 
-    for (i = 0; i < 12; i++) {
-        qemu_fprintf(f, "rpvr%-2d=%08x%c",
-                     i, env->pvr.regs[i], i % 4 == 3 ? '\n' : ' ');
-    }
-
     for (i = 0; i < 32; i++) {
         qemu_fprintf(f, "r%2.2d=%08x%c",
                      i, env->regs[i], i % 4 == 3 ? '\n' : ' ');