Lots of RISC-V improvements.
[fw/openocd] / src / target / riscv / riscv-013.c
index 4acd4275470b89aad1f473717191e86596227484..5683e5a3f6af9d76b5bd54ca2f6167ffea7596b3 100644 (file)
@@ -64,6 +64,13 @@ static int read_memory(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, uint8_t *buffer);
 static int write_memory(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, const uint8_t *buffer);
+static int riscv013_test_sba_config_reg(struct target *target, target_addr_t legal_address,
+               uint32_t num_words, target_addr_t illegal_address, bool run_sbbusyerror_test);
+void write_memory_sba_simple(struct target *target, target_addr_t addr, uint32_t* write_data,
+               uint32_t write_size, uint32_t sbcs);
+void read_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs);
+static int     riscv013_test_compliance(struct target *target);
 
 /**
  * Since almost everything can be accomplish by scanning the dbus register, all
@@ -169,7 +176,7 @@ typedef struct {
 
        /* Number of run-test/idle cycles the target requests we do after each dbus
         * access. */
-       unsigned int dtmcontrol_idle;
+       unsigned int dtmcs_idle;
 
        /* This value is incremented every time a dbus access comes back as "busy".
         * It's used to determine how many run-test/idle cycles to feed the target
@@ -187,8 +194,6 @@ typedef struct {
         * go low. */
        unsigned int ac_busy_delay;
 
-       bool need_strict_step;
-
        bool abstract_read_csr_supported;
        bool abstract_write_csr_supported;
        bool abstract_read_fpr_supported;
@@ -351,7 +356,7 @@ static void decode_dmi(char *text, unsigned address, unsigned data)
        }
 }
 
-static void dump_field(const struct scan_field *field)
+static void dump_field(int idle, const struct scan_field *field)
 {
        static const char * const op_string[] = {"-", "r", "w", "?"};
        static const char * const status_string[] = {"+", "?", "F", "b"};
@@ -371,8 +376,8 @@ static void dump_field(const struct scan_field *field)
 
        log_printf_lf(LOG_LVL_DEBUG,
                        __FILE__, __LINE__, "scan",
-                       "%db %s %08x @%02x -> %s %08x @%02x",
-                       field->num_bits,
+                       "%db %di %s %08x @%02x -> %s %08x @%02x",
+                       field->num_bits, idle,
                        op_string[out_op], out_data, out_address,
                        status_string[in_op], in_data, in_address);
 
@@ -390,16 +395,7 @@ static void dump_field(const struct scan_field *field)
 
 static void select_dmi(struct target *target)
 {
-       static uint8_t ir_dmi[1] = {DTM_DMI};
-       struct scan_field field = {
-               .num_bits = target->tap->ir_length,
-               .out_value = ir_dmi,
-               .in_value = NULL,
-               .check_value = NULL,
-               .check_mask = NULL
-       };
-
-       jtag_add_ir_scan(target->tap, &field, TAP_IDLE);
+       jtag_add_ir_scan(target->tap, &select_dbus, TAP_IDLE);
 }
 
 static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
@@ -436,8 +432,8 @@ static void increase_dmi_busy_delay(struct target *target)
 {
        riscv013_info_t *info = get_info(target);
        info->dmi_busy_delay += info->dmi_busy_delay / 10 + 1;
-       LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
-                       info->dtmcontrol_idle, info->dmi_busy_delay,
+       LOG_DEBUG("dtmcs_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
+                       info->dtmcs_idle, info->dmi_busy_delay,
                        info->ac_busy_delay);
 
        dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
@@ -452,14 +448,27 @@ static dmi_status_t dmi_scan(struct target *target, uint32_t *address_in,
                bool exec)
 {
        riscv013_info_t *info = get_info(target);
-       uint8_t in[8] = {0};
-       uint8_t out[8];
+       RISCV_INFO(r);
+       unsigned num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH;
+       size_t num_bytes = (num_bits + 7) / 8;
+       uint8_t in[num_bytes];
+       uint8_t out[num_bytes];
        struct scan_field field = {
-               .num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH,
+               .num_bits = num_bits,
                .out_value = out,
                .in_value = in
        };
 
+       if (r->reset_delays_wait >= 0) {
+               r->reset_delays_wait--;
+               if (r->reset_delays_wait < 0) {
+                       info->dmi_busy_delay = 0;
+                       info->ac_busy_delay = 0;
+               }
+       }
+
+       memset(in, 0, num_bytes);
+
        assert(info->abits != 0);
 
        buf_set_u32(out, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, op);
@@ -488,19 +497,25 @@ static dmi_status_t dmi_scan(struct target *target, uint32_t *address_in,
        if (address_in)
                *address_in = buf_get_u32(in, DTM_DMI_ADDRESS_OFFSET, info->abits);
 
-       dump_field(&field);
+       dump_field(idle_count, &field);
 
        return buf_get_u32(in, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
 }
 
-static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
-               uint32_t address, uint32_t data_out, int timeout_sec)
+/* If dmi_busy_encountered is non-NULL, this function will use it to tell the
+ * caller whether DMI was ever busy during this call. */
+static int dmi_op_timeout(struct target *target, uint32_t *data_in,
+               bool *dmi_busy_encountered, int dmi_op, uint32_t address,
+               uint32_t data_out, int timeout_sec, bool exec)
 {
        select_dmi(target);
 
        dmi_status_t status;
        uint32_t address_in;
 
+       if (dmi_busy_encountered)
+               *dmi_busy_encountered = false;
+
        const char *op_name;
        switch (dmi_op) {
                case DMI_OP_NOP:
@@ -522,9 +537,11 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
         * stays busy, it is actually due to the previous access. */
        while (1) {
                status = dmi_scan(target, NULL, NULL, dmi_op, address, data_out,
-                               false);
+                               exec);
                if (status == DMI_STATUS_BUSY) {
                        increase_dmi_busy_delay(target);
+                       if (dmi_busy_encountered)
+                               *dmi_busy_encountered = true;
                } else if (status == DMI_STATUS_SUCCESS) {
                        break;
                } else {
@@ -573,11 +590,12 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
        return ERROR_OK;
 }
 
-static int dmi_op(struct target *target, uint32_t *data_in, int dmi_op,
-               uint32_t address, uint32_t data_out)
+static int dmi_op(struct target *target, uint32_t *data_in,
+               bool *dmi_busy_encountered, int dmi_op, uint32_t address,
+               uint32_t data_out, bool exec)
 {
-       int result = dmi_op_timeout(target, data_in, dmi_op, address, data_out,
-                       riscv_command_timeout_sec);
+       int result = dmi_op_timeout(target, data_in, dmi_busy_encountered, dmi_op,
+                       address, data_out, riscv_command_timeout_sec, exec);
        if (result == ERROR_TIMEOUT_REACHED) {
                LOG_ERROR("DMI operation didn't complete in %d seconds. The target is "
                                "either really slow or broken. You could increase the "
@@ -590,19 +608,29 @@ static int dmi_op(struct target *target, uint32_t *data_in, int dmi_op,
 
 static int dmi_read(struct target *target, uint32_t *value, uint32_t address)
 {
-       return dmi_op(target, value, DMI_OP_READ, address, 0);
+       return dmi_op(target, value, NULL, DMI_OP_READ, address, 0, false);
+}
+
+static int dmi_read_exec(struct target *target, uint32_t *value, uint32_t address)
+{
+       return dmi_op(target, value, NULL, DMI_OP_READ, address, 0, true);
 }
 
 static int dmi_write(struct target *target, uint32_t address, uint32_t value)
 {
-       return dmi_op(target, NULL, DMI_OP_WRITE, address, value);
+       return dmi_op(target, NULL, NULL, DMI_OP_WRITE, address, value, false);
+}
+
+static int dmi_write_exec(struct target *target, uint32_t address, uint32_t value)
+{
+       return dmi_op(target, NULL, NULL, DMI_OP_WRITE, address, value, true);
 }
 
 int dmstatus_read_timeout(struct target *target, uint32_t *dmstatus,
                bool authenticated, unsigned timeout_sec)
 {
-       int result = dmi_op_timeout(target, dmstatus, DMI_OP_READ, DMI_DMSTATUS, 0,
-                       timeout_sec);
+       int result = dmi_op_timeout(target, dmstatus, NULL, DMI_OP_READ,
+                       DMI_DMSTATUS, 0, timeout_sec, false);
        if (result != ERROR_OK)
                return result;
        if (authenticated && !get_field(*dmstatus, DMI_DMSTATUS_AUTHENTICATED)) {
@@ -625,8 +653,8 @@ static void increase_ac_busy_delay(struct target *target)
 {
        riscv013_info_t *info = get_info(target);
        info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
-       LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
-                       info->dtmcontrol_idle, info->dmi_busy_delay,
+       LOG_DEBUG("dtmcs_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
+                       info->dtmcs_idle, info->dmi_busy_delay,
                        info->ac_busy_delay);
 }
 
@@ -687,8 +715,25 @@ static int wait_for_idle(struct target *target, uint32_t *abstractcs)
 static int execute_abstract_command(struct target *target, uint32_t command)
 {
        RISCV013_INFO(info);
-       LOG_DEBUG("command=0x%x", command);
-       dmi_write(target, DMI_COMMAND, command);
+       if (debug_level >= LOG_LVL_DEBUG) {
+               switch (get_field(command, DMI_COMMAND_CMDTYPE)) {
+                       case 0:
+                               LOG_DEBUG("command=0x%x; access register, size=%d, postexec=%d, "
+                                               "transfer=%d, write=%d, regno=0x%x",
+                                               command,
+                                               8 << get_field(command, AC_ACCESS_REGISTER_SIZE),
+                                               get_field(command, AC_ACCESS_REGISTER_POSTEXEC),
+                                               get_field(command, AC_ACCESS_REGISTER_TRANSFER),
+                                               get_field(command, AC_ACCESS_REGISTER_WRITE),
+                                               get_field(command, AC_ACCESS_REGISTER_REGNO));
+                               break;
+                       default:
+                               LOG_DEBUG("command=0x%x", command);
+                               break;
+               }
+       }
+
+       dmi_write_exec(target, DMI_COMMAND, command);
 
        uint32_t abstractcs = 0;
        wait_for_idle(target, &abstractcs);
@@ -744,10 +789,10 @@ static int write_abstract_arg(struct target *target, unsigned index,
 }
 
 /**
- * @size in bits
+ * @par size in bits
  */
-static uint32_t access_register_command(uint32_t number, unsigned size,
-               uint32_t flags)
+static uint32_t access_register_command(struct target *target, uint32_t number,
+               unsigned size, uint32_t flags)
 {
        uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);
        switch (size) {
@@ -770,8 +815,13 @@ static uint32_t access_register_command(uint32_t number, unsigned size,
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                command = set_field(command, AC_ACCESS_REGISTER_REGNO,
                                number - GDB_REGNO_CSR0);
-       } else {
-               assert(0);
+       } else if (number >= GDB_REGNO_COUNT) {
+               /* Custom register. */
+               assert(target->reg_cache->reg_list[number].arch_info);
+               riscv_reg_info_t *reg_info = target->reg_cache->reg_list[number].arch_info;
+               assert(reg_info);
+               command = set_field(command, AC_ACCESS_REGISTER_REGNO,
+                               0xc000 + reg_info->custom_number);
        }
 
        command |= flags;
@@ -791,7 +841,7 @@ static int register_read_abstract(struct target *target, uint64_t *value,
                        !info->abstract_read_csr_supported)
                return ERROR_FAIL;
 
-       uint32_t command = access_register_command(number, size,
+       uint32_t command = access_register_command(target, number, size,
                        AC_ACCESS_REGISTER_TRANSFER);
 
        int result = execute_abstract_command(target, command);
@@ -826,7 +876,7 @@ static int register_write_abstract(struct target *target, uint32_t number,
                        !info->abstract_write_csr_supported)
                return ERROR_FAIL;
 
-       uint32_t command = access_register_command(number, size,
+       uint32_t command = access_register_command(target, number, size,
                        AC_ACCESS_REGISTER_TRANSFER |
                        AC_ACCESS_REGISTER_WRITE);
 
@@ -1087,7 +1137,7 @@ static int register_write_direct(struct target *target, unsigned number,
        RISCV013_INFO(info);
        RISCV_INFO(r);
 
-       LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
+       LOG_DEBUG("{%d} reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
                        number, value);
 
        int result = register_write_abstract(target, number, value,
@@ -1095,7 +1145,6 @@ static int register_write_direct(struct target *target, unsigned number,
        if (result == ERROR_OK && target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, value);
-               reg->valid = true;
        }
        if (result == ERROR_OK || info->progbufsize + r->impebreak < 2 ||
                        !riscv_is_halted(target))
@@ -1154,7 +1203,6 @@ static int register_write_direct(struct target *target, unsigned number,
        if (exec_out == ERROR_OK && target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, value);
-               reg->valid = true;
        }
 
        if (use_scratch)
@@ -1174,24 +1222,12 @@ static int register_read(struct target *target, uint64_t *value, uint32_t number
                *value = 0;
                return ERROR_OK;
        }
-       if (target->reg_cache &&
-                       (number <= GDB_REGNO_XPR31 ||
-                        (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31))) {
-               /* Only check the cache for registers that we know won't spontaneously
-                * change. */
-               struct reg *reg = &target->reg_cache->reg_list[number];
-               if (reg && reg->valid) {
-                       *value = buf_get_u64(reg->value, 0, reg->size);
-                       return ERROR_OK;
-               }
-       }
        int result = register_read_direct(target, value, number);
        if (result != ERROR_OK)
                return ERROR_FAIL;
        if (target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, *value);
-               reg->valid = true;
        }
        return ERROR_OK;
 }
@@ -1284,7 +1320,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t
        }
 
        if (result == ERROR_OK) {
-               LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
+               LOG_DEBUG("{%d} reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
                                number, *value);
        }
 
@@ -1321,6 +1357,7 @@ static void deinit_target(struct target *target)
        LOG_DEBUG("riscv_deinit_target()");
        riscv_info_t *info = (riscv_info_t *) target->arch_info;
        free(info->version_specific);
+       /* TODO: free register arch_info */
        info->version_specific = NULL;
 }
 
@@ -1347,17 +1384,7 @@ static int examine(struct target *target)
 
        riscv013_info_t *info = get_info(target);
        info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
-       info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
-
-       uint32_t dmstatus;
-       if (dmstatus_read(target, &dmstatus, false) != ERROR_OK)
-               return ERROR_FAIL;
-       LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
-       if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
-               LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
-                               "(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
-               return ERROR_FAIL;
-       }
+       info->dtmcs_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
 
        /* Reset the Debug Module. */
        dm013_info_t *dm = get_dm(target);
@@ -1379,6 +1406,16 @@ static int examine(struct target *target)
                return ERROR_FAIL;
        }
 
+       uint32_t dmstatus;
+       if (dmstatus_read(target, &dmstatus, false) != ERROR_OK)
+               return ERROR_FAIL;
+       LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
+       if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
+               LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
+                               "(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
+               return ERROR_FAIL;
+       }
+
        uint32_t hartsel =
                (get_field(dmcontrol, DMI_DMCONTROL_HARTSELHI) <<
                 DMI_DMCONTROL_HARTSELLO_LENGTH) |
@@ -1454,7 +1491,8 @@ static int examine(struct target *target)
                        dmi_write(target, DMI_DMCONTROL,
                                        set_hartsel(DMI_DMCONTROL_DMACTIVE | DMI_DMCONTROL_ACKHAVERESET, i));
 
-               if (!riscv_is_halted(target)) {
+               bool halted = riscv_is_halted(target);
+               if (!halted) {
                        if (riscv013_halt_current_hart(target) != ERROR_OK) {
                                LOG_ERROR("Fatal: Hart %d failed to halt during examine()", i);
                                return ERROR_FAIL;
@@ -1484,6 +1522,9 @@ static int examine(struct target *target)
                 * really slow simulators. */
                LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
                                r->misa[i]);
+
+               if (!halted)
+                       riscv013_resume_current_hart(target);
        }
 
        LOG_DEBUG("Enumerated %d harts", r->hart_count);
@@ -1493,11 +1534,6 @@ static int examine(struct target *target)
                return ERROR_FAIL;
        }
 
-       /* Resumes all the harts, so the debugger can later pause them. */
-       /* TODO: Only do this if the harts were halted to start with. */
-       riscv_resume_all_harts(target);
-       target->state = TARGET_RUNNING;
-
        target_set_examined(target);
 
        /* Some regression suites rely on seeing 'Examined RISC-V core' to know
@@ -1579,6 +1615,8 @@ static int init_target(struct command_context *cmd_ctx,
        generic_info->authdata_write = &riscv013_authdata_write;
        generic_info->dmi_read = &dmi_read;
        generic_info->dmi_write = &dmi_write;
+       generic_info->test_sba_config_reg = &riscv013_test_sba_config_reg;
+       generic_info->test_compliance = &riscv013_test_compliance;
        generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
        if (!generic_info->version_specific)
                return ERROR_FAIL;
@@ -1722,7 +1760,7 @@ static int deassert_reset(struct target *target)
 }
 
 /**
- * @size in bytes
+ * @par size in bytes
  */
 static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
 {
@@ -1750,13 +1788,38 @@ static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
 
 static int execute_fence(struct target *target)
 {
-       struct riscv_program program;
-       riscv_program_init(&program, target);
-       riscv_program_fence(&program);
-       int result = riscv_program_exec(&program, target);
-       if (result != ERROR_OK)
-               LOG_ERROR("Unable to execute fence");
-       return result;
+       int old_hartid = riscv_current_hartid(target);
+
+       /* FIXME: For non-coherent systems we need to flush the caches right
+        * here, but there's no ISA-defined way of doing that. */
+       {
+               struct riscv_program program;
+               riscv_program_init(&program, target);
+               riscv_program_fence_i(&program);
+               riscv_program_fence(&program);
+               int result = riscv_program_exec(&program, target);
+               if (result != ERROR_OK)
+                       LOG_DEBUG("Unable to execute pre-fence");
+       }
+
+       for (int i = 0; i < riscv_count_harts(target); ++i) {
+               if (!riscv_hart_enabled(target, i))
+                       continue;
+
+               riscv_set_current_hartid(target, i);
+
+               struct riscv_program program;
+               riscv_program_init(&program, target);
+               riscv_program_fence_i(&program);
+               riscv_program_fence(&program);
+               int result = riscv_program_exec(&program, target);
+               if (result != ERROR_OK)
+                       LOG_DEBUG("Unable to execute fence on hart %d", i);
+       }
+
+       riscv_set_current_hartid(target, old_hartid);
+
+       return ERROR_OK;
 }
 
 static void log_memory_access(target_addr_t address, uint64_t value,
@@ -2015,89 +2078,76 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address,
        return ERROR_OK;
 }
 
+static int batch_run(const struct target *target, struct riscv_batch *batch)
+{
+       RISCV013_INFO(info);
+       RISCV_INFO(r);
+       if (r->reset_delays_wait >= 0) {
+               r->reset_delays_wait -= batch->used_scans;
+               if (r->reset_delays_wait <= 0) {
+                       batch->idle_count = 0;
+                       info->dmi_busy_delay = 0;
+                       info->ac_busy_delay = 0;
+               }
+       }
+       return riscv_batch_run(batch);
+}
+
 /**
  * Read the requested memory, taking care to execute every read exactly once,
  * even if cmderr=busy is encountered.
  */
-static int read_memory_progbuf(struct target *target, target_addr_t address,
+static int read_memory_progbuf_inner(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, uint8_t *buffer)
 {
        RISCV013_INFO(info);
 
        int result = ERROR_OK;
 
-       LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
-                       size, address);
-
-       select_dmi(target);
-
-       /* s0 holds the next address to write to
-        * s1 holds the next data value to write
-        */
-       uint64_t s0, s1;
-       if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK)
-               return ERROR_FAIL;
-       if (register_read(target, &s1, GDB_REGNO_S1) != ERROR_OK)
+       /* Write address to S0, and execute buffer. */
+       result = register_write_direct(target, GDB_REGNO_S0, address);
+       if (result != ERROR_OK)
+               goto error;
+       uint32_t command = access_register_command(target, GDB_REGNO_S1,
+                       riscv_xlen(target),
+                       AC_ACCESS_REGISTER_TRANSFER | AC_ACCESS_REGISTER_POSTEXEC);
+       if (execute_abstract_command(target, command) != ERROR_OK)
                return ERROR_FAIL;
 
-       if (execute_fence(target) != ERROR_OK)
-               return ERROR_FAIL;
+       /* First read has just triggered. Result is in s1. */
 
-       /* Write the program (load, increment) */
-       struct riscv_program program;
-       riscv_program_init(&program, target);
-       switch (size) {
-               case 1:
-                       riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               case 2:
-                       riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               case 4:
-                       riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               default:
-                       LOG_ERROR("Unsupported size: %d", size);
+       if (count == 1) {
+               uint64_t value;
+               if (register_read_direct(target, &value, GDB_REGNO_S1) != ERROR_OK)
                        return ERROR_FAIL;
+               write_to_buf(buffer, value, size);
+               log_memory_access(address, value, size, true);
+               return ERROR_OK;
        }
-       riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
 
-       if (riscv_program_ebreak(&program) != ERROR_OK)
-               return ERROR_FAIL;
-       riscv_program_write(&program);
-
-       /* Write address to S0, and execute buffer. */
-       result = register_write_direct(target, GDB_REGNO_S0, address);
-       if (result != ERROR_OK)
+       if (dmi_write(target, DMI_ABSTRACTAUTO,
+                       1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET) != ERROR_OK)
                goto error;
-       uint32_t command = access_register_command(GDB_REGNO_S1, riscv_xlen(target),
-                               AC_ACCESS_REGISTER_TRANSFER |
-                               AC_ACCESS_REGISTER_POSTEXEC);
-       result = execute_abstract_command(target, command);
-       if (result != ERROR_OK)
+       /* Read garbage from dmi_data0, which triggers another execution of the
+        * program. Now dmi_data0 contains the first good result, and s1 the next
+        * memory value. */
+       if (dmi_read_exec(target, NULL, DMI_DATA0) != ERROR_OK)
                goto error;
 
-       /* First read has just triggered. Result is in s1. */
-
-       dmi_write(target, DMI_ABSTRACTAUTO,
-                       1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
-
        /* read_addr is the next address that the hart will read from, which is the
         * value in s0. */
-       riscv_addr_t read_addr = address + size;
-       /* The next address that we need to receive data for. */
-       riscv_addr_t receive_addr = address;
+       riscv_addr_t read_addr = address + 2 * size;
        riscv_addr_t fin_addr = address + (count * size);
-       unsigned skip = 1;
        while (read_addr < fin_addr) {
-               LOG_DEBUG("read_addr=0x%" PRIx64 ", receive_addr=0x%" PRIx64
-                               ", fin_addr=0x%" PRIx64, read_addr, receive_addr, fin_addr);
+               LOG_DEBUG("read_addr=0x%" PRIx64 ", fin_addr=0x%" PRIx64, read_addr,
+                               fin_addr);
                /* The pipeline looks like this:
                 * memory -> s1 -> dm_data0 -> debugger
-                * It advances every time the debugger reads dmdata0.
-                * So at any time the debugger has just read mem[s0 - 3*size],
-                * dm_data0 contains mem[s0 - 2*size]
-                * s1 contains mem[s0-size] */
+                * Right now:
+                * s0 contains read_addr
+                * s1 contains mem[read_addr-size]
+                * dm_data0 contains[read_addr-size*2]
+                */
 
                LOG_DEBUG("creating burst to read from 0x%" PRIx64
                                " up to 0x%" PRIx64, read_addr, fin_addr);
@@ -2114,10 +2164,11 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                break;
                }
 
-               riscv_batch_run(batch);
+               batch_run(target, batch);
 
                /* Wait for the target to finish performing the last abstract command,
-                * and update our copy of cmderr. */
+                * and update our copy of cmderr. If we see that DMI is busy here,
+                * dmi_busy_delay will be incremented. */
                uint32_t abstractcs;
                if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
                        return ERROR_FAIL;
@@ -2126,9 +2177,8 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                return ERROR_FAIL;
                info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
 
-               unsigned cmderr = info->cmderr;
                riscv_addr_t next_read_addr;
-               uint32_t dmi_data0 = -1;
+               unsigned ignore_last = 0;
                switch (info->cmderr) {
                        case CMDERR_NONE:
                                LOG_DEBUG("successful (partial?) memory read");
@@ -2137,38 +2187,12 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                        case CMDERR_BUSY:
                                LOG_DEBUG("memory read resulted in busy response");
 
-                               /*
-                                * If you want to exercise this code path, apply the following patch to spike:
---- a/riscv/debug_module.cc
-+++ b/riscv/debug_module.cc
-@@ -1,3 +1,5 @@
-+#include <unistd.h>
-+
- #include <cassert>
-
- #include "debug_module.h"
-@@ -398,6 +400,15 @@ bool debug_module_t::perform_abstract_command()
-       // Since the next instruction is what we will use, just use nother NOP
-       // to get there.
-       write32(debug_abstract, 1, addi(ZERO, ZERO, 0));
-+
-+      if (abstractauto.autoexecdata &&
-+          program_buffer[0] == 0x83 &&
-+          program_buffer[1] == 0x24 &&
-+          program_buffer[2] == 0x04 &&
-+          program_buffer[3] == 0 &&
-+          rand() < RAND_MAX / 10) {
-+        usleep(1000000);
-+      }
-     } else {
-       write32(debug_abstract, 1, ebreak());
-     }
-                                */
                                increase_ac_busy_delay(target);
                                riscv013_clear_abstract_error(target);
 
                                dmi_write(target, DMI_ABSTRACTAUTO, 0);
 
+                               uint32_t dmi_data0;
                                /* This is definitely a good version of the value that we
                                 * attempted to read when we discovered that the target was
                                 * busy. */
@@ -2177,23 +2201,30 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                        goto error;
                                }
 
-                               /* Clobbers DMI_DATA0. */
+                               /* See how far we got, clobbering dmi_data0. */
                                result = register_read_direct(target, &next_read_addr,
                                                GDB_REGNO_S0);
                                if (result != ERROR_OK) {
                                        riscv_batch_free(batch);
                                        goto error;
                                }
+                               write_to_buf(buffer + next_read_addr - 2 * size - address, dmi_data0, size);
+                               log_memory_access(next_read_addr - 2 * size, dmi_data0, size, true);
+
                                /* Restore the command, and execute it.
                                 * Now DMI_DATA0 contains the next value just as it would if no
                                 * error had occurred. */
-                               dmi_write(target, DMI_COMMAND, command);
+                               dmi_write_exec(target, DMI_COMMAND, command);
+                               next_read_addr += size;
 
                                dmi_write(target, DMI_ABSTRACTAUTO,
                                                1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
+
+                               ignore_last = 1;
+
                                break;
                        default:
-                               LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
+                               LOG_DEBUG("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
                                riscv013_clear_abstract_error(target);
                                riscv_batch_free(batch);
                                result = ERROR_FAIL;
@@ -2201,34 +2232,43 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                }
 
                /* Now read whatever we got out of the batch. */
+               dmi_status_t status = DMI_STATUS_SUCCESS;
                for (size_t i = 0; i < reads; i++) {
-                       if (read_addr >= next_read_addr)
-                               break;
-
-                       read_addr += size;
-
-                       if (skip > 0) {
-                               skip--;
+                       riscv_addr_t receive_addr = read_addr + (i-2) * size;
+                       assert(receive_addr < address + size * count);
+                       if (receive_addr < address)
                                continue;
-                       }
+                       if (receive_addr > next_read_addr - (3 + ignore_last) * size)
+                               break;
 
-                       riscv_addr_t offset = receive_addr - address;
                        uint64_t dmi_out = riscv_batch_get_dmi_read(batch, i);
+                       status = get_field(dmi_out, DTM_DMI_OP);
+                       if (status != DMI_STATUS_SUCCESS) {
+                               /* If we're here because of busy count, dmi_busy_delay will
+                                * already have been increased and busy state will have been
+                                * cleared in dmi_read(). */
+                               /* In at least some implementations, we issue a read, and then
+                                * can get busy back when we try to scan out the read result,
+                                * and the actual read value is lost forever. Since this is
+                                * rare in any case, we return error here and rely on our
+                                * caller to reread the entire block. */
+                               LOG_WARNING("Batch memory read encountered DMI error %d. "
+                                               "Falling back on slower reads.", status);
+                               riscv_batch_free(batch);
+                               result = ERROR_FAIL;
+                               goto error;
+                       }
                        uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
+                       riscv_addr_t offset = receive_addr - address;
                        write_to_buf(buffer + offset, value, size);
                        log_memory_access(receive_addr, value, size, true);
 
                        receive_addr += size;
                }
-               riscv_batch_free(batch);
 
-               if (cmderr == CMDERR_BUSY) {
-                       riscv_addr_t offset = receive_addr - address;
-                       write_to_buf(buffer + offset, dmi_data0, size);
-                       log_memory_access(receive_addr, dmi_data0, size, true);
-                       read_addr += size;
-                       receive_addr += size;
-               }
+               read_addr = next_read_addr;
+
+               riscv_batch_free(batch);
        }
 
        dmi_write(target, DMI_ABSTRACTAUTO, 0);
@@ -2237,10 +2277,9 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                /* Read the penultimate word. */
                uint32_t value;
                if (dmi_read(target, &value, DMI_DATA0) != ERROR_OK)
-                       goto error;
-               write_to_buf(buffer + receive_addr - address, value, size);
-               log_memory_access(receive_addr, value, size, true);
-               receive_addr += size;
+                       return ERROR_FAIL;
+               write_to_buf(buffer + size * (count-2), value, size);
+               log_memory_access(address + size * (count-2), value, size, true);
        }
 
        /* Read the last word. */
@@ -2248,16 +2287,95 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
        result = register_read_direct(target, &value, GDB_REGNO_S1);
        if (result != ERROR_OK)
                goto error;
-       write_to_buf(buffer + receive_addr - address, value, size);
-       log_memory_access(receive_addr, value, size, true);
+       write_to_buf(buffer + size * (count-1), value, size);
+       log_memory_access(address + size * (count-1), value, size, true);
 
-       riscv_set_register(target, GDB_REGNO_S0, s0);
-       riscv_set_register(target, GDB_REGNO_S1, s1);
        return ERROR_OK;
 
 error:
        dmi_write(target, DMI_ABSTRACTAUTO, 0);
 
+       return result;
+}
+
+/**
+ * Read the requested memory, silently handling memory access errors.
+ */
+static int read_memory_progbuf(struct target *target, target_addr_t address,
+               uint32_t size, uint32_t count, uint8_t *buffer)
+{
+       int result = ERROR_OK;
+
+       LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
+                       size, address);
+
+       select_dmi(target);
+
+       memset(buffer, 0, count*size);
+
+       /* s0 holds the next address to write to
+        * s1 holds the next data value to write
+        */
+       uint64_t s0, s1;
+       if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK)
+               return ERROR_FAIL;
+       if (register_read(target, &s1, GDB_REGNO_S1) != ERROR_OK)
+               return ERROR_FAIL;
+
+       if (execute_fence(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       /* Write the program (load, increment) */
+       struct riscv_program program;
+       riscv_program_init(&program, target);
+       switch (size) {
+               case 1:
+                       riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               case 2:
+                       riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               case 4:
+                       riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               default:
+                       LOG_ERROR("Unsupported size: %d", size);
+                       return ERROR_FAIL;
+       }
+       riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
+
+       if (riscv_program_ebreak(&program) != ERROR_OK)
+               return ERROR_FAIL;
+       riscv_program_write(&program);
+
+       result = read_memory_progbuf_inner(target, address, size, count, buffer);
+
+       if (result != ERROR_OK) {
+               /* The full read did not succeed, so we will try to read each word individually. */
+               /* This will not be fast, but reading outside actual memory is a special case anyway. */
+               /* It will make the toolchain happier, especially Eclipse Memory View as it reads ahead. */
+               target_addr_t address_i = address;
+               uint32_t size_i = size;
+               uint32_t count_i = 1;
+               uint8_t *buffer_i = buffer;
+
+               for (uint32_t i = 0; i < count; i++, address_i += size_i, buffer_i += size_i) {
+                       /* TODO: This is much slower than it needs to be because we end up
+                        * writing the address to read for every word we read. */
+                       result = read_memory_progbuf_inner(target, address_i, size_i, count_i, buffer_i);
+
+                       /* The read of a single word failed, so we will just return 0 for that instead */
+                       if (result != ERROR_OK) {
+                               LOG_DEBUG("error reading single word of %d bytes from 0x%" TARGET_PRIxADDR,
+                                               size_i, address_i);
+
+                               uint64_t value_i = 0;
+                               write_to_buf(buffer_i, value_i, size_i);
+                       }
+               }
+               result = ERROR_OK;
+       }
+
        riscv_set_register(target, GDB_REGNO_S0, s0);
        riscv_set_register(target, GDB_REGNO_S1, s1);
        return result;
@@ -2558,7 +2676,8 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
 
                                /* Write and execute command that moves value into S1 and
                                 * executes program buffer. */
-                               uint32_t command = access_register_command(GDB_REGNO_S1, 32,
+                               uint32_t command = access_register_command(target,
+                                               GDB_REGNO_S1, 32,
                                                AC_ACCESS_REGISTER_POSTEXEC |
                                                AC_ACCESS_REGISTER_TRANSFER |
                                                AC_ACCESS_REGISTER_WRITE);
@@ -2580,7 +2699,7 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
                        }
                }
 
-               result = riscv_batch_run(batch);
+               result = batch_run(target, batch);
                riscv_batch_free(batch);
                if (result != ERROR_OK)
                        goto error;
@@ -2590,33 +2709,34 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
                 * to be incremented if necessary. */
 
                uint32_t abstractcs;
-               if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
+               bool dmi_busy_encountered;
+               if (dmi_op(target, &abstractcs, &dmi_busy_encountered, DMI_OP_READ,
+                                       DMI_ABSTRACTCS, 0, false) != ERROR_OK)
                        goto error;
                while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
                        if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
                                return ERROR_FAIL;
                info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
-               switch (info->cmderr) {
-                       case CMDERR_NONE:
-                               LOG_DEBUG("successful (partial?) memory write");
-                               break;
-                       case CMDERR_BUSY:
-                               LOG_DEBUG("memory write resulted in busy response");
-                               riscv013_clear_abstract_error(target);
-                               increase_ac_busy_delay(target);
-
-                               dmi_write(target, DMI_ABSTRACTAUTO, 0);
-                               result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
-                               if (result != ERROR_OK)
-                                       goto error;
-                               setup_needed = true;
-                               break;
-
-                       default:
-                               LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
-                               riscv013_clear_abstract_error(target);
-                               result = ERROR_FAIL;
+               if (info->cmderr == CMDERR_NONE && !dmi_busy_encountered) {
+                       LOG_DEBUG("successful (partial?) memory write");
+               } else if (info->cmderr == CMDERR_BUSY || dmi_busy_encountered) {
+                       if (info->cmderr == CMDERR_BUSY)
+                               LOG_DEBUG("Memory write resulted in abstract command busy response.");
+                       else if (dmi_busy_encountered)
+                               LOG_DEBUG("Memory write resulted in DMI busy response.");
+                       riscv013_clear_abstract_error(target);
+                       increase_ac_busy_delay(target);
+
+                       dmi_write(target, DMI_ABSTRACTAUTO, 0);
+                       result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
+                       if (result != ERROR_OK)
                                goto error;
+                       setup_needed = true;
+               } else {
+                       LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
+                       riscv013_clear_abstract_error(target);
+                       result = ERROR_FAIL;
+                       goto error;
                }
        }
 
@@ -2696,7 +2816,7 @@ static int riscv013_get_register(struct target *target,
        int result = ERROR_OK;
        if (rid == GDB_REGNO_PC) {
                result = register_read(target, value, GDB_REGNO_DPC);
-               LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, *value);
+               LOG_DEBUG("read PC from DPC: 0x%" PRIx64, *value);
        } else if (rid == GDB_REGNO_PRIV) {
                uint64_t dcsr;
                result = register_read(target, &dcsr, GDB_REGNO_DCSR);
@@ -2720,7 +2840,7 @@ static int riscv013_set_register(struct target *target, int hid, int rid, uint64
        if (rid <= GDB_REGNO_XPR31) {
                return register_write_direct(target, rid, value);
        } else if (rid == GDB_REGNO_PC) {
-               LOG_DEBUG("writing PC to DPC: 0x%016" PRIx64, value);
+               LOG_DEBUG("writing PC to DPC: 0x%" PRIx64, value);
                register_write_direct(target, GDB_REGNO_DPC, value);
                uint64_t actual_value;
                register_read_direct(target, &actual_value, GDB_REGNO_DPC);
@@ -2864,6 +2984,7 @@ static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
                 * already set when we connected. Force enumeration now, which has the
                 * side effect of clearing any triggers we did not set. */
                riscv_enumerate_triggers(target);
+               LOG_DEBUG("{%d} halted because of trigger", target->coreid);
                return RISCV_HALT_TRIGGER;
        case CSR_DCSR_CAUSE_STEP:
                return RISCV_HALT_SINGLESTEP;
@@ -2924,6 +3045,357 @@ void riscv013_fill_dmi_nop_u64(struct target *target, char *buf)
        buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, 0);
 }
 
+/* Helper function for riscv013_test_sba_config_reg */
+static int get_max_sbaccess(struct target *target)
+{
+       RISCV013_INFO(info);
+
+       uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128);
+       uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64);
+       uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32);
+       uint32_t sbaccess16 = get_field(info->sbcs, DMI_SBCS_SBACCESS16);
+       uint32_t sbaccess8 = get_field(info->sbcs, DMI_SBCS_SBACCESS8);
+
+       if (sbaccess128)
+               return 4;
+       else if (sbaccess64)
+               return 3;
+       else if (sbaccess32)
+               return 2;
+       else if (sbaccess16)
+               return 1;
+       else if (sbaccess8)
+               return 0;
+       else
+               return -1;
+}
+
+static uint32_t get_num_sbdata_regs(struct target *target)
+{
+       RISCV013_INFO(info);
+
+       uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128);
+       uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64);
+       uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32);
+
+       if (sbaccess128)
+               return 4;
+       else if (sbaccess64)
+               return 2;
+       else if (sbaccess32)
+               return 1;
+       else
+               return 0;
+}
+
+static int riscv013_test_sba_config_reg(struct target *target,
+               target_addr_t legal_address, uint32_t num_words,
+               target_addr_t illegal_address, bool run_sbbusyerror_test)
+{
+       LOG_INFO("Testing System Bus Access as defined by RISC-V Debug Spec v0.13");
+
+       uint32_t tests_failed = 0;
+
+       uint32_t rd_val;
+       uint32_t sbcs_orig;
+       dmi_read(target, &sbcs_orig, DMI_SBCS);
+
+       uint32_t sbcs = sbcs_orig;
+       bool test_passed;
+
+       int max_sbaccess = get_max_sbaccess(target);
+
+       if (max_sbaccess == -1) {
+               LOG_ERROR("System Bus Access not supported in this config.");
+               return ERROR_FAIL;
+       }
+
+       if (get_field(sbcs, DMI_SBCS_SBVERSION) != 1) {
+               LOG_ERROR("System Bus Access unsupported SBVERSION (%d). Only version 1 is supported.",
+                               get_field(sbcs, DMI_SBCS_SBVERSION));
+               return ERROR_FAIL;
+       }
+
+       uint32_t num_sbdata_regs = get_num_sbdata_regs(target);
+
+       uint32_t rd_buf[num_sbdata_regs];
+
+       /* Test 1: Simple write/read test */
+       test_passed = true;
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 0);
+       dmi_write(target, DMI_SBCS, sbcs);
+
+       uint32_t test_patterns[4] = {0xdeadbeef, 0xfeedbabe, 0x12345678, 0x08675309};
+       for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) {
+               sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               uint32_t compare_mask = (sbaccess == 0) ? 0xff : (sbaccess == 1) ? 0xffff : 0xffffffff;
+
+               for (uint32_t i = 0; i < num_words; i++) {
+                       uint32_t addr = legal_address + (i << sbaccess);
+                       uint32_t wr_data[num_sbdata_regs];
+                       for (uint32_t j = 0; j < num_sbdata_regs; j++)
+                               wr_data[j] = test_patterns[j] + i;
+                       write_memory_sba_simple(target, addr, wr_data, num_sbdata_regs, sbcs);
+               }
+
+               for (uint32_t i = 0; i < num_words; i++) {
+                       uint32_t addr = legal_address + (i << sbaccess);
+                       read_memory_sba_simple(target, addr, rd_buf, num_sbdata_regs, sbcs);
+                       for (uint32_t j = 0; j < num_sbdata_regs; j++) {
+                               if (((test_patterns[j]+i)&compare_mask) != (rd_buf[j]&compare_mask)) {
+                                       LOG_ERROR("System Bus Access Test 1: Error reading non-autoincremented address %x,"
+                                                       "expected val = %x, read val = %x", addr, test_patterns[j]+i, rd_buf[j]);
+                                       test_passed = false;
+                                       tests_failed++;
+                               }
+                       }
+               }
+       }
+       if (test_passed)
+               LOG_INFO("System Bus Access Test 1: Simple write/read test PASSED.");
+
+       /* Test 2: Address autoincrement test */
+       target_addr_t curr_addr;
+       target_addr_t prev_addr;
+       test_passed = true;
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 1);
+       dmi_write(target, DMI_SBCS, sbcs);
+
+       for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) {
+               sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               dmi_write(target, DMI_SBADDRESS0, legal_address);
+               read_sbcs_nonbusy(target, &sbcs);
+               curr_addr = legal_address;
+               for (uint32_t i = 0; i < num_words; i++) {
+                       prev_addr = curr_addr;
+                       read_sbcs_nonbusy(target, &sbcs);
+                       curr_addr = sb_read_address(target);
+                       if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) {
+                               LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x.", sbaccess);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+                       dmi_write(target, DMI_SBDATA0, i);
+               }
+
+               read_sbcs_nonbusy(target, &sbcs);
+
+               dmi_write(target, DMI_SBADDRESS0, legal_address);
+
+               uint32_t val;
+               sbcs = set_field(sbcs, DMI_SBCS_SBREADONDATA, 1);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &val, DMI_SBDATA0); /* Dummy read to trigger first system bus read */
+               curr_addr = legal_address;
+               for (uint32_t i = 0; i < num_words; i++) {
+                       prev_addr = curr_addr;
+                       read_sbcs_nonbusy(target, &sbcs);
+                       curr_addr = sb_read_address(target);
+                       if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) {
+                               LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x", sbaccess);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+                       dmi_read(target, &val, DMI_SBDATA0);
+                       read_sbcs_nonbusy(target, &sbcs);
+                       if (i != val) {
+                               LOG_ERROR("System Bus Access Test 2: Error reading auto-incremented address,"
+                                               "expected val = %x, read val = %x.", i, val);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+               }
+       }
+       if (test_passed)
+               LOG_INFO("System Bus Access Test 2: Address auto-increment test PASSED.");
+
+       /* Test 3: Read from illegal address */
+       read_memory_sba_simple(target, illegal_address, rd_buf, 1, sbcs_orig);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 3: Illegal address read test PASSED.");
+               else
+                       LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to clear to 0.");
+       } else {
+               LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to set error code.");
+       }
+
+       /* Test 4: Write to illegal address */
+       write_memory_sba_simple(target, illegal_address, test_patterns, 1, sbcs_orig);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 4: Illegal address write test PASSED.");
+               else {
+                       LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to clear to 0.");
+                       tests_failed++;
+               }
+       } else {
+               LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to set error code.");
+               tests_failed++;
+       }
+
+       /* Test 5: Write with unsupported sbaccess size */
+       uint32_t sbaccess128 = get_field(sbcs_orig, DMI_SBCS_SBACCESS128);
+
+       if (sbaccess128) {
+               LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED, all sbaccess sizes supported.");
+       } else {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 4);
+
+               write_memory_sba_simple(target, legal_address, test_patterns, 1, sbcs);
+
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 4) {
+                       sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 4);
+                       dmi_write(target, DMI_SBCS, sbcs);
+                       dmi_read(target, &rd_val, DMI_SBCS);
+                       if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                               LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED.");
+                       else {
+                               LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to clear to 0.");
+                               tests_failed++;
+                       }
+               } else {
+                       LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to set error code.");
+                       tests_failed++;
+               }
+       }
+
+       /* Test 6: Write to misaligned address */
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 1);
+
+       write_memory_sba_simple(target, legal_address+1, test_patterns, 1, sbcs);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 3) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 3);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 6: SBCS address alignment error test PASSED");
+               else {
+                       LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to clear to 0.");
+                       tests_failed++;
+               }
+       } else {
+               LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to set error code.");
+               tests_failed++;
+       }
+
+       /* Test 7: Set sbbusyerror, only run this case in simulation as it is likely
+        * impossible to hit otherwise */
+       if (run_sbbusyerror_test) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBREADONADDR, 1);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               for (int i = 0; i < 16; i++)
+                       dmi_write(target, DMI_SBDATA0, 0xdeadbeef);
+
+               for (int i = 0; i < 16; i++)
+                       dmi_write(target, DMI_SBADDRESS0, legal_address);
+
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBBUSYERROR)) {
+                       sbcs = set_field(sbcs_orig, DMI_SBCS_SBBUSYERROR, 1);
+                       dmi_write(target, DMI_SBCS, sbcs);
+                       dmi_read(target, &rd_val, DMI_SBCS);
+                       if (get_field(rd_val, DMI_SBCS_SBBUSYERROR) == 0)
+                               LOG_INFO("System Bus Access Test 7: SBCS sbbusyerror test PASSED.");
+                       else {
+                               LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to clear to 0.");
+                               tests_failed++;
+                       }
+               } else {
+                       LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to set error code.");
+                       tests_failed++;
+               }
+       }
+
+       if (tests_failed == 0) {
+               LOG_INFO("ALL TESTS PASSED");
+               return ERROR_OK;
+       } else {
+               LOG_ERROR("%d TESTS FAILED", tests_failed);
+               return ERROR_FAIL;
+       }
+
+}
+
+void write_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *write_data, uint32_t write_size, uint32_t sbcs)
+{
+       RISCV013_INFO(info);
+
+       uint32_t rd_sbcs;
+       uint32_t masked_addr;
+
+       uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE);
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       uint32_t sbcs_no_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 0);
+       dmi_write(target, DMI_SBCS, sbcs_no_readonaddr);
+
+       for (uint32_t i = 0; i < sba_size/32; i++) {
+               masked_addr = (addr >> 32*i) & 0xffffffff;
+
+               if (i != 3)
+                       dmi_write(target, DMI_SBADDRESS0+i, masked_addr);
+               else
+                       dmi_write(target, DMI_SBADDRESS3, masked_addr);
+       }
+
+       /* Write SBDATA registers starting with highest address, since write to
+        * SBDATA0 triggers write */
+       for (int i = write_size-1; i >= 0; i--)
+               dmi_write(target, DMI_SBDATA0+i, write_data[i]);
+}
+
+void read_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs)
+{
+       RISCV013_INFO(info);
+
+       uint32_t rd_sbcs;
+       uint32_t masked_addr;
+
+       uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE);
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       uint32_t sbcs_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 1);
+       dmi_write(target, DMI_SBCS, sbcs_readonaddr);
+
+       /* Write addresses starting with highest address register */
+       for (int i = sba_size/32-1; i >= 0; i--) {
+               masked_addr = (addr >> 32*i) & 0xffffffff;
+
+               if (i != 3)
+                       dmi_write(target, DMI_SBADDRESS0+i, masked_addr);
+               else
+                       dmi_write(target, DMI_SBADDRESS3, masked_addr);
+       }
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       for (uint32_t i = 0; i < read_size; i++)
+               dmi_read(target, &(rd_buf[i]), DMI_SBDATA0+i);
+}
+
 int riscv013_dmi_write_u64_bits(struct target *target)
 {
        RISCV013_INFO(info);
@@ -2934,16 +3406,8 @@ static int maybe_execute_fence_i(struct target *target)
 {
        RISCV013_INFO(info);
        RISCV_INFO(r);
-       if (info->progbufsize + r->impebreak >= 2) {
-               struct riscv_program program;
-               riscv_program_init(&program, target);
-               if (riscv_program_fence_i(&program) != ERROR_OK)
-                       return ERROR_FAIL;
-               if (riscv_program_exec(&program, target) != ERROR_OK) {
-                       LOG_ERROR("Failed to execute fence.i");
-                       return ERROR_FAIL;
-               }
-       }
+       if (info->progbufsize + r->impebreak >= 3)
+               return execute_fence(target);
        return ERROR_OK;
 }
 
@@ -3034,3 +3498,459 @@ void riscv013_clear_abstract_error(struct target *target)
        /* Clear the error status. */
        dmi_write(target, DMI_ABSTRACTCS, abstractcs & DMI_ABSTRACTCS_CMDERR);
 }
+
+#define COMPLIANCE_TEST(b, message) \
+{                                   \
+       int pass = 0;               \
+       if (b) {                    \
+               pass = 1;           \
+               passed_tests++;     \
+       }                           \
+       LOG_INFO("%s test %d (%s)\n", (pass) ? "PASSED" : "FAILED",  total_tests, message); \
+       assert(pass);               \
+       total_tests++;              \
+}
+
+#define COMPLIANCE_MUST_PASS(b) COMPLIANCE_TEST(ERROR_OK == (b), "Regular calls must return ERROR_OK")
+
+#define COMPLIANCE_READ(target, addr, value) COMPLIANCE_MUST_PASS(dmi_read(target, addr, value))
+#define COMPLIANCE_WRITE(target, addr, value) COMPLIANCE_MUST_PASS(dmi_write(target, addr, value))
+
+#define COMPLIANCE_CHECK_RO(target, addr)                               \
+{                                                                       \
+       uint32_t orig;                                                      \
+       uint32_t inverse;                                                   \
+       COMPLIANCE_READ(target, &orig, addr);                               \
+       COMPLIANCE_WRITE(target, addr, ~orig);                              \
+       COMPLIANCE_READ(target, &inverse, addr);                            \
+       COMPLIANCE_TEST(orig == inverse, "Register must be read-only");     \
+}
+
+int riscv013_test_compliance(struct target *target)
+{
+       LOG_INFO("Testing Compliance against RISC-V Debug Spec v0.13");
+
+       if (!riscv_rtos_enabled(target)) {
+               LOG_ERROR("Please run with -rtos riscv to run compliance test.");
+               return ERROR_FAIL;
+       }
+
+       int total_tests = 0;
+       int passed_tests = 0;
+
+       uint32_t dmcontrol_orig = DMI_DMCONTROL_DMACTIVE;
+       uint32_t dmcontrol;
+       uint32_t testvar;
+       uint32_t testvar_read;
+       riscv_reg_t value;
+       RISCV013_INFO(info);
+
+       /* All the bits of HARTSEL are covered by the examine sequence. */
+
+       /* hartreset */
+       /* This field is optional. Either we can read and write it to 1/0,
+       or it is tied to 0. This check doesn't really do anything, but
+       it does attempt to set the bit to 1 and then back to 0, which needs to
+       work if its implemented. */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 1));
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 0));
+       COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL);
+       COMPLIANCE_TEST((get_field(dmcontrol, DMI_DMCONTROL_HARTRESET) == 0),
+                       "DMCONTROL.hartreset can be 0 or RW.");
+
+       /* hasel */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 1));
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 0));
+       COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL);
+       COMPLIANCE_TEST((get_field(dmcontrol, DMI_DMCONTROL_HASEL) == 0),
+                       "DMCONTROL.hasel can be 0 or RW.");
+       /* TODO: test that hamask registers exist if hasel does. */
+
+       /* haltreq */
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+       /* This bit is not actually readable according to the spec, so nothing to check.*/
+
+       /* DMSTATUS */
+       COMPLIANCE_CHECK_RO(target, DMI_DMSTATUS);
+
+       /* resumereq */
+       /* This bit is not actually readable according to the spec, so nothing to check.*/
+       COMPLIANCE_MUST_PASS(riscv_resume_all_harts(target));
+
+       /* Halt all harts again so the test can continue.*/
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+
+       /* HARTINFO: Read-Only. This is per-hart, so need to adjust hartsel. */
+       uint32_t hartinfo;
+       COMPLIANCE_READ(target, &hartinfo, DMI_HARTINFO);
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+
+               COMPLIANCE_CHECK_RO(target, DMI_HARTINFO);
+
+               /* $dscratch CSRs */
+               uint32_t nscratch = get_field(hartinfo, DMI_HARTINFO_NSCRATCH);
+               for (unsigned int d = 0; d < nscratch; d++) {
+                       riscv_reg_t testval, testval_read;
+                       /* Because DSCRATCH is not guaranteed to last across PB executions, need to put
+                       this all into one PB execution. Which may not be possible on all implementations.*/
+                       if (info->progbufsize >= 5) {
+                               for (testval = 0x0011223300112233;
+                                                testval != 0xDEAD;
+                                                testval = testval == 0x0011223300112233 ? ~testval : 0xDEAD) {
+                                       COMPLIANCE_TEST(register_write_direct(target, GDB_REGNO_S0, testval) == ERROR_OK,
+                                                       "Need to be able to write S0 in order to test DSCRATCH.");
+                                       struct riscv_program program32;
+                                       riscv_program_init(&program32, target);
+                                       riscv_program_csrw(&program32, GDB_REGNO_S0, GDB_REGNO_DSCRATCH + d);
+                                       riscv_program_csrr(&program32, GDB_REGNO_S1, GDB_REGNO_DSCRATCH + d);
+                                       riscv_program_fence(&program32);
+                                       riscv_program_ebreak(&program32);
+                                       COMPLIANCE_TEST(riscv_program_exec(&program32, target) == ERROR_OK,
+                                                       "Accessing DSCRATCH with program buffer should succeed.");
+                                       COMPLIANCE_TEST(register_read_direct(target, &testval_read, GDB_REGNO_S1) == ERROR_OK,
+                                                       "Need to be able to read S1 in order to test DSCRATCH.");
+                                       if (riscv_xlen(target) > 32) {
+                                               COMPLIANCE_TEST(testval == testval_read,
+                                                               "All DSCRATCH registers in HARTINFO must be R/W.");
+                                       } else {
+                                               COMPLIANCE_TEST(testval_read == (testval & 0xFFFFFFFF),
+                                                               "All DSCRATCH registers in HARTINFO must be R/W.");
+                                       }
+                               }
+                       }
+               }
+               /* TODO: dataaccess */
+               if (get_field(hartinfo, DMI_HARTINFO_DATAACCESS)) {
+                       /* TODO: Shadowed in memory map. */
+                       /* TODO: datasize */
+                       /* TODO: dataaddr */
+               } else {
+                       /* TODO: Shadowed in CSRs. */
+                       /* TODO: datasize */
+                       /* TODO: dataaddr */
+               }
+
+       }
+
+       /* HALTSUM -- TODO: More than 32 harts. Would need to loop over this to set hartsel */
+       /* TODO: HALTSUM2, HALTSUM3 */
+       /* HALTSUM0 */
+       uint32_t expected_haltsum0 = 0;
+       for (int i = 0; i < MIN(riscv_count_harts(target), 32); i++)
+               expected_haltsum0 |= (1 << i);
+
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0,
+                       "HALTSUM0 should report summary of up to 32 halted harts");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0xffffffff);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0x0);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O");
+
+       /* HALTSUM1 */
+       uint32_t expected_haltsum1 = 0;
+       for (int i = 0; i < MIN(riscv_count_harts(target), 1024); i += 32)
+               expected_haltsum1 |= (1 << (i/32));
+
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1,
+                       "HALTSUM1 should report summary of up to 1024 halted harts");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0xffffffff);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0x0);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O");
+
+       /* TODO: HAWINDOWSEL */
+
+       /* TODO: HAWINDOW */
+
+       /* ABSTRACTCS */
+
+       uint32_t abstractcs;
+       COMPLIANCE_READ(target, &abstractcs, DMI_ABSTRACTCS);
+
+       /* Check that all reported Data Words are really R/W */
+       for (int invert = 0; invert < 2; invert++) {
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar);
+               }
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+                       COMPLIANCE_TEST(testvar_read == testvar, "All reported DATA words must be R/W");
+               }
+       }
+
+       /* Check that all reported ProgBuf words are really R/W */
+       for (int invert = 0; invert < 2; invert++) {
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar);
+               }
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+                       COMPLIANCE_TEST(testvar_read == testvar, "All reported PROGBUF words must be R/W");
+               }
+       }
+
+       /* TODO: Cause and clear all error types */
+
+       /* COMMAND
+       According to the spec, this register is only W, so can't really check the read result.
+       But at any rate, this is not legal and should cause an error. */
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0xAAAAAAAA);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \
+                       "Illegal COMMAND should result in UNSUPPORTED");
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0x55555555);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \
+                       "Illegal COMMAND should result in UNSUPPORTED");
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+
+       /* Basic Abstract Commands */
+       for (unsigned int i = 1; i < 32; i = i << 1) {
+               riscv_reg_t testval =   i | ((i + 1ULL) << 32);
+               riscv_reg_t testval_read;
+               COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_ZERO + i, testval),
+                               "GPR Writes should be supported.");
+               COMPLIANCE_MUST_PASS(write_abstract_arg(target, 0, 0xDEADBEEFDEADBEEF, 64));
+               COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &testval_read, GDB_REGNO_ZERO + i),
+                               "GPR Reads should be supported.");
+               if (riscv_xlen(target) > 32) {
+                       /* Dummy comment to satisfy linter, since removing the brances here doesn't actually compile. */
+                       COMPLIANCE_TEST(testval == testval_read, "GPR Reads and writes should be supported.");
+               } else {
+                       /* Dummy comment to satisfy linter, since removing the brances here doesn't actually compile. */
+                       COMPLIANCE_TEST((testval & 0xFFFFFFFF) == testval_read, "GPR Reads and writes should be supported.");
+               }
+       }
+
+       /* ABSTRACTAUTO
+       See which bits are actually writable */
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+       uint32_t abstractauto;
+       uint32_t busy;
+       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0x0);
+       if (abstractauto > 0) {
+               /* This mechanism only works when you have a reasonable sized progbuf, which is not
+               a true compliance requirement. */
+               if (info->progbufsize >= 3) {
+
+                       testvar = 0;
+                       COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_S0, 0),
+                                       "Need to be able to write S0 to test ABSTRACTAUTO");
+                       struct riscv_program program;
+                       COMPLIANCE_MUST_PASS(riscv_program_init(&program, target));
+                       /* This is also testing that WFI() is a NOP during debug mode. */
+                       COMPLIANCE_MUST_PASS(riscv_program_insert(&program, wfi()));
+                       COMPLIANCE_MUST_PASS(riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, 1));
+                       COMPLIANCE_MUST_PASS(riscv_program_ebreak(&program));
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0x0);
+                       COMPLIANCE_MUST_PASS(riscv_program_exec(&program, target));
+                       testvar++;
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+                       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+                       uint32_t autoexec_data = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECDATA);
+                       uint32_t autoexec_progbuf = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECPROGBUF);
+                       for (unsigned int i = 0; i < 12; i++) {
+                               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+                               do {
+                                       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+                                       busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY);
+                               } while (busy);
+                               if (autoexec_data & (1 << i)) {
+                                       COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT),
+                                                       "AUTOEXEC may be writable up to DATACOUNT bits.");
+                                       testvar++;
+                               }
+                       }
+                       for (unsigned int i = 0; i < 16; i++) {
+                               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+                               do {
+                                       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+                                       busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY);
+                               } while (busy);
+                               if (autoexec_progbuf & (1 << i)) {
+                                       COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE),
+                                                       "AUTOEXEC may be writable up to PROGBUFSIZE bits.");
+                                       testvar++;
+                               }
+                       }
+
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0);
+                       COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &value, GDB_REGNO_S0),
+                                       "Need to be able to read S0 to test ABSTRACTAUTO");
+
+                       COMPLIANCE_TEST(testvar == value,
+                                       "ABSTRACTAUTO should cause COMMAND to run the expected number of times.");
+               }
+       }
+
+       /* Single-Step each hart. */
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+               COMPLIANCE_MUST_PASS(riscv013_on_step(target));
+               COMPLIANCE_MUST_PASS(riscv013_step_current_hart(target));
+               COMPLIANCE_TEST(riscv_halt_reason(target, hartsel) == RISCV_HALT_SINGLESTEP,
+                               "Single Step should result in SINGLESTEP");
+       }
+
+       /* Core Register Tests */
+       uint64_t bogus_dpc = 0xdeadbeef;
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+
+               /* DCSR Tests */
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0x0));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR));
+               COMPLIANCE_TEST(value != 0,     "Not all bits in DCSR are writable by Debugger");
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0xFFFFFFFF));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR));
+               COMPLIANCE_TEST(value != 0,     "At least some bits in DCSR must be 1");
+
+               /* DPC. Note that DPC is sign-extended. */
+               riscv_reg_t dpcmask = 0xFFFFFFFCUL;
+               riscv_reg_t dpc;
+
+               if (riscv_xlen(target) > 32)
+                       dpcmask |= (0xFFFFFFFFULL << 32);
+
+               if (riscv_supports_extension(target, riscv_current_hartid(target), 'C'))
+                       dpcmask |= 0x2;
+
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, dpcmask));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC));
+               COMPLIANCE_TEST(dpcmask == dpc,
+                               "DPC must be sign-extended to XLEN and writable to all-1s (except the least significant bits)");
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, 0));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC));
+               COMPLIANCE_TEST(dpc == 0, "DPC must be writable to 0.");
+               if (hartsel == 0)
+                       bogus_dpc = dpc; /* For a later test step */
+       }
+
+       /* NDMRESET
+       Asserting non-debug module reset should not reset Debug Module state.
+       But it should reset Hart State, e.g. DPC should get a different value.
+       Also make sure that DCSR reports cause of 'HALT' even though previously we single-stepped.
+       */
+
+       /* Write some registers. They should not be impacted by ndmreset. */
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0xFFFFFFFF);
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar);
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar);
+       }
+
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+
+       /* Pulse reset. */
+       target->reset_halt = true;
+       COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, 0));
+       COMPLIANCE_TEST(ERROR_OK == assert_reset(target), "Must be able to assert NDMRESET");
+       COMPLIANCE_TEST(ERROR_OK == deassert_reset(target), "Must be able to deassert NDMRESET");
+
+       /* Verify that most stuff is not affected by ndmreset. */
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR)  == CMDERR_NOT_SUPPORTED,
+                       "NDMRESET should not affect DMI_ABSTRACTCS");
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO);
+       COMPLIANCE_TEST(testvar_read == abstractauto, "NDMRESET should not affect DMI_ABSTRACTAUTO");
+
+       /* Clean up to avoid future test failures */
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0);
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+               COMPLIANCE_TEST(testvar_read == testvar, "PROGBUF words must not be affected by NDMRESET");
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+               COMPLIANCE_TEST(testvar_read == testvar, "DATA words must not be affected by NDMRESET");
+       }
+
+       /* Verify that DPC *is* affected by ndmreset. Since we don't know what it *should* be,
+       just verify that at least it's not the bogus value anymore. */
+
+       COMPLIANCE_TEST(bogus_dpc != 0xdeadbeef, "BOGUS DPC should have been set somehow (bug in compliance test)");
+       COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DPC));
+       COMPLIANCE_TEST(bogus_dpc != value, "NDMRESET should move DPC to reset value.");
+
+       COMPLIANCE_TEST(riscv_halt_reason(target, 0) == RISCV_HALT_INTERRUPT,
+                       "After NDMRESET halt, DCSR should report cause of halt");
+
+       /* DMACTIVE -- deasserting DMACTIVE should reset all the above values. */
+
+       /* Toggle dmactive */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, 0);
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR)  == 0, "ABSTRACTCS.cmderr should reset to 0");
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO);
+       COMPLIANCE_TEST(testvar_read == 0, "ABSTRACTAUTO should reset to 0");
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+               COMPLIANCE_TEST(testvar_read == 0, "PROGBUF words should reset to 0");
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+               COMPLIANCE_TEST(testvar_read == 0, "DATA words should reset to 0");
+       }
+
+       /*
+       * TODO:
+       * DCSR.cause priorities
+       * DCSR.stoptime/stopcycle
+       * DCSR.stepie
+       * DCSR.ebreak
+       * DCSR.prv
+       */
+
+       /* Halt every hart for any follow-up tests*/
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+
+       uint32_t failed_tests = total_tests - passed_tests;
+       if (total_tests == passed_tests) {
+               LOG_INFO("ALL TESTS PASSED\n");
+               return ERROR_OK;
+       } else {
+               LOG_INFO("%d TESTS FAILED\n", failed_tests);
+               return ERROR_FAIL;
+       }
+}