On Wed, 2026-09-09 at 11:48 -0300, Daniel Henrique Barboza wrote:
> From: Chao Liu <[email protected]>
> 
> RISC-V Debug Specification:
> https://github.com/riscv/riscv-debug-spec/releases/tag/1.0
> 
> Use a TB flag when dcsr.step is set (and we are not in Debug Mode).
> When the flag is on, build 1-insn TBs and do not chain to the next
> TB.
> Add a TB-exit helper that enters Debug Mode with cause=step and sets
> dpc to the next pc, then stops with EXCP_DEBUG.
> 
> If dcsr.stepie is 0, do not take interrupts while stepping. Treat WFI
> as a nop so the hart does not sleep during a step.
> 
> PS: This patch references Max Chou's handling of ext_tb_flags.
> https://lore.kernel.org/qemu-devel/[email protected]/
> 
> Signed-off-by: Chao Liu <[email protected]>
> Reviewed-by: Daniel Henrique Barboza
> <[email protected]>
> Tested-by: Tao Tang <[email protected]>
> Signed-off-by: Daniel Henrique Barboza
> <[email protected]>

Reviewed-by: Alistair Francis <[email protected]>

Alistair

> ---
>  include/exec/translation-block.h |  4 ++--
>  target/riscv/cpu.h               |  2 ++
>  target/riscv/helper.h            |  1 +
>  target/riscv/tcg/cpu_helper.c    |  6 ++++++
>  target/riscv/tcg/op_helper.c     | 20 ++++++++++++++++++++
>  target/riscv/tcg/tcg-cpu.c       |  5 +++++
>  target/riscv/tcg/translate.c     | 15 +++++++++++++--
>  7 files changed, 49 insertions(+), 4 deletions(-)
> 
> diff --git a/include/exec/translation-block.h
> b/include/exec/translation-block.h
> index 40cc699031..ee15608c89 100644
> --- a/include/exec/translation-block.h
> +++ b/include/exec/translation-block.h
> @@ -64,8 +64,8 @@ struct TranslationBlock {
>       * x86: the original user, the Code Segment virtual base,
>       * arm: an extension of tb->flags,
>       * s390x: instruction data for EXECUTE,
> -     * sparc: the next pc of the instruction queue (for delay
> slots).
> -     * riscv: an extension of tb->flags,
> +     * sparc: the next pc of the instruction queue (for delay
> slots),
> +     * riscv: an extension of tb->flags.
>       */
>      uint64_t cs_base;
>  
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index 82eb61a18c..db49ad7682 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -755,6 +755,8 @@ FIELD(TB_FLAGS, PM_SIGNEXTEND, 31, 1)
>  FIELD(EXT_TB_FLAGS, MISA_EXT, 0, 32)
>  FIELD(EXT_TB_FLAGS, ALTFMT, 32, 1)
>  FIELD(EXT_TB_FLAGS, BIG_ENDIAN, 33, 1)
> +/* sdext single-step needs a TB flag to build 1-insn TBs */
> +FIELD(EXT_TB_FLAGS, SDEXT_STEP, 34, 1)
>  
>  #ifdef TARGET_RISCV32
>  #define riscv_cpu_mxl(env)  ((void)(env), MXL_RV32)
> diff --git a/target/riscv/helper.h b/target/riscv/helper.h
> index 0d9ae15e3e..5193440e1c 100644
> --- a/target/riscv/helper.h
> +++ b/target/riscv/helper.h
> @@ -142,6 +142,7 @@ DEF_HELPER_1(tlb_flush_all, void, env)
>  DEF_HELPER_4(ctr_add_entry, void, env, tl, tl, tl)
>  /* Native Debug */
>  DEF_HELPER_1(itrigger_match, void, env)
> +DEF_HELPER_1(sdext_step, void, env)
>  DEF_HELPER_2(sdext_ebreak, void, env, tl)
>  #endif
>  
> diff --git a/target/riscv/tcg/cpu_helper.c
> b/target/riscv/tcg/cpu_helper.c
> index e6d948991c..a7344b1be2 100644
> --- a/target/riscv/tcg/cpu_helper.c
> +++ b/target/riscv/tcg/cpu_helper.c
> @@ -500,6 +500,12 @@ bool riscv_cpu_exec_interrupt(CPUState *cs, int
> interrupt_request)
>      if (interrupt_request & mask) {
>          RISCVCPU *cpu = RISCV_CPU(cs);
>          CPURISCVState *env = &cpu->env;
> +
> +        if (cpu->cfg.ext_sdext && !env->debug_mode &&
> +            (env->dcsr & DCSR_STEP) && !(env->dcsr & DCSR_STEPIE)) {
> +            return false;
> +        }
> +
>          int interruptno = riscv_cpu_local_irq_pending(env);
>          if (interruptno >= 0) {
>              cs->exception_index = RISCV_EXCP_INT_FLAG | interruptno;
> diff --git a/target/riscv/tcg/op_helper.c
> b/target/riscv/tcg/op_helper.c
> index 5db4f6aacd..489aac28e7 100644
> --- a/target/riscv/tcg/op_helper.c
> +++ b/target/riscv/tcg/op_helper.c
> @@ -489,6 +489,22 @@ target_ulong helper_dret(CPURISCVState *env)
>  #endif
>  }
>  
> +void helper_sdext_step(CPURISCVState *env)
> +{
> +#ifndef CONFIG_USER_ONLY
> +    CPUState *cs = env_cpu(env);
> +
> +    if (!riscv_cpu_cfg(env)->ext_sdext || env->debug_mode ||
> +        !(env->dcsr & DCSR_STEP)) {
> +        return;
> +    }
> +
> +    riscv_cpu_enter_debug_mode(env, env->pc, DCSR_CAUSE_STEP);
> +    cs->exception_index = EXCP_DEBUG;
> +    cpu_loop_exit_restore(cs, GETPC());
> +#endif
> +}
> +
>  void helper_sdext_ebreak(CPURISCVState *env, target_ulong pc)
>  {
>      CPUState *cs = env_cpu(env);
> @@ -621,6 +637,10 @@ void helper_wfi(CPURISCVState *env)
>                 (prv_u || (prv_s && get_field(env->hstatus,
> HSTATUS_VTW)))) {
>          riscv_raise_exception(env,
> RISCV_EXCP_VIRT_INSTRUCTION_FAULT, GETPC());
>      } else {
> +        if (riscv_cpu_cfg(env)->ext_sdext && !env->debug_mode &&
> +            (env->dcsr & DCSR_STEP)) {
> +            return;
> +        }
>          cs->halted = 1;
>          cs->exception_index = EXCP_HLT;
>          cpu_loop_exit(cs);
> diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
> index 959a1e89c8..16d26cee5c 100644
> --- a/target/riscv/tcg/tcg-cpu.c
> +++ b/target/riscv/tcg/tcg-cpu.c
> @@ -196,6 +196,11 @@ static TCGTBCPUState
> riscv_get_tb_cpu_state(CPUState *cs)
>      ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, MISA_EXT, env-
> >misa_ext);
>      ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, BIG_ENDIAN,
>                             mo_endian_env(env) == MO_BE);
> +#ifndef CONFIG_USER_ONLY
> +    if (cpu->cfg.ext_sdext && !env->debug_mode && (env->dcsr &
> DCSR_STEP)) {
> +        ext_flags = FIELD_DP64(ext_flags, EXT_TB_FLAGS, SDEXT_STEP,
> 1);
> +    }
> +#endif
>  
>      return (TCGTBCPUState){
>          .pc = env->xl == MXL_RV32 ? env->pc & UINT32_MAX : env->pc,
> diff --git a/target/riscv/tcg/translate.c
> b/target/riscv/tcg/translate.c
> index 9684dbe752..1a38d6aeb5 100644
> --- a/target/riscv/tcg/translate.c
> +++ b/target/riscv/tcg/translate.c
> @@ -112,6 +112,8 @@ typedef struct DisasContext {
>      bool ztso;
>      /* Use icount trigger for native debug */
>      bool itrigger;
> +    /* Enter Debug Mode after next instruction (sdext single-step).
> */
> +    bool sdext_step;
>      /* FRM is known to contain a valid value. */
>      bool frm_valid;
>      bool insn_start_updated;
> @@ -286,6 +288,9 @@ static void lookup_and_goto_ptr(DisasContext
> *ctx)
>      if (ctx->itrigger) {
>          gen_helper_itrigger_match(tcg_env);
>      }
> +    if (ctx->sdext_step) {
> +        gen_helper_sdext_step(tcg_env);
> +    }
>  #endif
>      tcg_gen_lookup_and_goto_ptr();
>  }
> @@ -296,6 +301,9 @@ static void exit_tb(DisasContext *ctx)
>      if (ctx->itrigger) {
>          gen_helper_itrigger_match(tcg_env);
>      }
> +    if (ctx->sdext_step) {
> +        gen_helper_sdext_step(tcg_env);
> +    }
>  #endif
>      tcg_gen_exit_tb(NULL, 0);
>  }
> @@ -309,7 +317,8 @@ static void gen_goto_tb(DisasContext *ctx,
> unsigned tb_slot_idx,
>        * Under itrigger, instruction executes one by one like
> singlestep,
>        * direct block chain benefits will be small.
>        */
> -    if (translator_use_goto_tb(&ctx->base, dest) && !ctx->itrigger)
> {
> +    if (translator_use_goto_tb(&ctx->base, dest) &&
> +        !ctx->itrigger && !ctx->sdext_step) {
>          /*
>           * For pcrel, the pc must always be up-to-date on entry to
>           * the linked TB, so that it can use simple additions for
> all
> @@ -1350,6 +1359,7 @@ static void
> riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
>      ctx->bcfi_enabled = FIELD_EX32(tb_flags, TB_FLAGS,
> BCFI_ENABLED);
>      ctx->fcfi_lp_expected = FIELD_EX32(tb_flags, TB_FLAGS,
> FCFI_LP_EXPECTED);
>      ctx->fcfi_enabled = FIELD_EX32(tb_flags, TB_FLAGS,
> FCFI_ENABLED);
> +    ctx->sdext_step = FIELD_EX64(ext_tb_flags, EXT_TB_FLAGS,
> SDEXT_STEP);
>      ctx->zero = tcg_constant_tl(0);
>      ctx->virt_inst_excp = false;
>      ctx->decoders = cpu->decoders;
> @@ -1402,7 +1412,8 @@ static void
> riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>  
>      /* Only the first insn within a TB is allowed to cross a page
> boundary. */
>      if (ctx->base.is_jmp == DISAS_NEXT) {
> -        if (ctx->itrigger || !translator_is_same_page(&ctx->base,
> ctx->base.pc_next)) {
> +        if (ctx->itrigger || ctx->sdext_step ||
> +            !translator_is_same_page(&ctx->base, ctx->base.pc_next))
> {
>              ctx->base.is_jmp = DISAS_TOO_MANY;
>          } else {
>              unsigned page_ofs = ctx->base.pc_next &
> ~TARGET_PAGE_MASK;

Reply via email to