universalisos/docs/xtensa-debug-architecture.md
Fábio Coutada 98ed638f3c WIP: emergency commit — ESP32 GDB stub, boot chain work, docs, AGENTS.md rules
All uncommitted work from ESP32 GDB stub development session.
Includes:
- GDB stub (uos_gdbstub.c/.h/_entry.S)
- Startup vector table rewrite
- ESP32 HAL integration files
- Boot chain design docs
- AGENTS.md absolute rules (commit before refactor, no unauthorized changes)
- All prior deepseek session work

This commit prevents further data loss. No claims of correctness.
2026-07-17 01:36:58 +01:00

532 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Xtensa LX6 (ESP32) Debug Exception Architecture — GDB Stub Reference
**Target:** ESP32 (Xtensa LX6, core `esp32_v3_49_prod`, XEA2, CALL0 ABI)
**Purpose:** Definitive reference for building a bare-metal GDB stub
**Sources:** `core-isa.h`, `corebits.h`, `specreg.h`, `xt_specreg.h`, `xtensa_vectors.S`, IDF `esp_gdbstub`, `xt_utils.h`
---
## 1. Debug Exception Mechanism
### How `break` triggers a debug exception
The `break` instruction is a 3-byte opcode. When executed, it raises a **debug exception** at **interrupt level 6** (`XCHAL_DEBUGLEVEL = 6`). Debug exceptions do NOT go through the normal EXCCAUSE/vector mechanism for level 15; they vector to a **dedicated debug exception vector**.
Key facts:
- **`break s, t`** — the two immediate operands (s, t) are information-only (available to the debug handler via DEBUGCAUSE). `break 0, 0` is the standard software breakpoint.
- **`break.n`** — a 16-bit (narrow) variant of break, flagged separately in DEBUGCAUSE.
- Debug exceptions are taken at **INTLEVEL 6**, which is higher than all normal interrupts (levels 15) but below NMI (level 7).
- The **EXCCAUSE** register is NOT set by the debug exception itself for `break` — DEBUGCAUSE records the reason. (However, the IDF panic handler manually writes EXCCAUSE to `PANIC_RSN_DEBUGEXCEPTION` for its own bookkeeping.)
### Registers used on debug exception entry
| Register | SR# | Purpose |
|----------|-----|---------|
| **EPC6** | 182 | Exception PC — saved PC of the instruction that triggered (or the next instruction for ICOUNT step) |
| **EPS6** | 198 | Exception PS — saved PS (processor state) register |
| **EXCSAVE6** | 214 | Scratch save register for level 6 — conventionally used to save `a0` (return address) at vector entry |
The hardware automatically saves:
- `PC → EPC6`
- `PS → EPS6`
Then vectors to the debug exception vector address.
### What EXCSAVE6 is used for
At the debug vector entry, `a0` is the only register you can use (no stack frame yet). The standard convention (from `xtensa_vectors.S`) is:
```asm
_DebugExceptionVector:
wsr a0, EXCSAVE6 /* save a0 — only safe scratch at level 6 */
j xt_debugexception /* jump to handler */
```
`EXCSAVE6` is the handler's dedicated scratch slot to preserve the interrupted code's `a0` before it sets up a stack frame.
### EXCCAUSE for debug exceptions
The debug exception does **not** use the standard EXCCAUSE values (063). Instead, the reason is in **DEBUGCAUSE** (SR 233). However, when the panic handler is invoked, it manually sets:
```c
EXCCAUSE = PANIC_RSN_DEBUGEXCEPTION // (an IDF-specific value, not an ISA EXCCAUSE)
```
---
## 2. Special Register Numbers (ESP32 / LX6)
All register numbers confirmed from `specreg.h` / `xt_specreg.h`:
### Debug-specific registers
| Register | SR Number | Symbol | R/W | Description |
|----------|-----------|--------|-----|-------------|
| **IBREAKENABLE** | **96** | `IBREAKENABLE` | RW | Bitmask: bit n enables IBREAKA_n |
| **IBREAKA_0** | **128** | `IBREAKA_0` | RW | Instruction breakpoint address 0 |
| **IBREAKA_1** | **129** | `IBREAKA_1` | RW | Instruction breakpoint address 1 |
| **DBREAKA_0** | **144** | `DBREAKA_0` | RW | Data breakpoint (watchpoint) address 0 |
| **DBREAKA_1** | **145** | `DBREAKA_1` | RW | Data breakpoint address 1 |
| **DBREAKC_0** | **160** | `DBREAKC_0` | RW | Data breakpoint control 0 |
| **DBREAKC_1** | **161** | `DBREAKC_1` | RW | Data breakpoint control 1 |
| **DEBUGCAUSE** | **233** | `DEBUGCAUSE` | RO | Debug exception cause (set by hardware on debug exception) |
| **ICOUNT** | **236** | `ICOUNT` | RW | Instruction count register (for single-stepping) |
| **ICOUNTLEVEL** | **237** | `ICOUNTLEVEL` | RW | Interrupt level at which ICOUNT triggers a debug exception |
### Level-6 exception state registers
| Register | SR Number | Symbol | Description |
|----------|-----------|--------|-------------|
| **EPC_6** | **182** | `EPC_6` | Exception PC for level 6 (debug) |
| **EPS_6** | **198** | `EPS_6` | Exception PS for level 6 (debug) |
| **EXCSAVE_6** | **214** | `EXCSAVE_6` | Scratch save for level 6 |
| **DEPC** | **192** | `DEPC` | *(Alias for EPC at debug level in some docs; ESP32 uses EPC_6)* |
### Other relevant registers
| Register | SR Number | Symbol | Description |
|----------|-----------|--------|-------------|
| **CONFIGID0** | **176** | `CONFIGID0` | RO — processor config ID (hi 32 bits) |
| **CONFIGID1** | **208** | `CONFIGID1` | RO — processor config ID (lo 32 bits) |
| **PS** | **230** | `PS` | Processor state register |
| **VECBASE** | **231** | `VECBASE` | Vector base address register |
| **EXCCAUSE** | **232** | `EXCCAUSE` | Exception cause register |
| **EXCVADDR** | **238** | `EXCVADDR` | Exception virtual address |
| **CCOUNT** | **234** | `CCOUNT` | Cycle count register |
| **PRID** | **235** | `PRID` | Processor ID |
### EPC/EPS/EXCSAVE series base values
```
EPC series base: 176 → EPC_1 = 177, EPC_2 = 178, ..., EPC_6 = 182, EPC_7 = 183
EPS series base: 192 → EPS_2 = 194, EPS_3 = 195, ..., EPS_6 = 198, EPS_7 = 199
EXCSAVE series base: 208 → EXCSAVE_1 = 209, ..., EXCSAVE_6 = 214, EXCSAVE_7 = 215
```
Note: There is no EPC_0 or EPS_0 or EPS_1 (level 0 and level 1 exceptions use different mechanisms — level 1 uses EPC_1/EPS_1... wait, actually: EPC_1=177, but EPS_1 does not exist in the table because level 1 exception PS is saved/restored differently in XEA2).
### Hardware config values
```c
XCHAL_NUM_IBREAK = 2 /* 2 instruction breakpoints */
XCHAL_NUM_DBREAK = 2 /* 2 data watchpoints */
XCHAL_DEBUGLEVEL = 6 /* debug interrupt level */
XCHAL_NMILEVEL = 7 /* NMI level */
XCHAL_NUM_INTLEVELS = 6 /* levels 1-6 (not counting level 0) */
XCHAL_EXCM_LEVEL = 3 /* level masked by PS.EXCM */
XCHAL_HW_CONFIGID0 = 0xC2BCFFFE
XCHAL_HW_CONFIGID1 = 0x1CC5FE96
```
---
## 3. DEBUGCAUSE Register (SR 233)
Read-only. Set by hardware on entry to the debug exception vector. Bit layout from `corebits.h`:
| Bit | Mask | Name | Meaning |
|-----|------|------|---------|
| 0 | 0x01 | **ICOUNT** | ICOUNT reached zero (single-step triggered) |
| 1 | 0x02 | **IBREAK** | Instruction breakpoint match |
| 2 | 0x04 | **DBREAK** | Data breakpoint (watchpoint) match |
| 3 | 0x08 | **BREAK** | `break` instruction (3-byte) executed |
| 4 | 0x10 | **BREAKN** | `break.n` instruction (2-byte narrow) executed |
| 5 | 0x20 | **DEBUGINT** | External debug interrupt (OCD) |
Reading: `rsr a0, DEBUGCAUSE` gives the OR of all active causes.
### Interpreting DEBUGCAUSE in a GDB stub
```
DEBUGCAUSE & 0x01 → ICOUNT → single-step completed
DEBUGCAUSE & 0x02 → IBREAK → hardware breakpoint hit
DEBUGCAUSE & 0x04 → DBREAK → watchpoint hit (load/store)
DEBUGCAUSE & 0x08 → BREAK → software breakpoint (break 0,0) hit
DEBUGCAUSE & 0x10 → BREAKN → narrow software breakpoint hit
DEBUGCAUSE & 0x20 → DEBUGINT→ OCD external debug interrupt
```
For a software breakpoint placed by GDB (`break 0,0`), DEBUGCAUSE bit 3 (BREAK) is set.
---
## 4. Vector Table Layout
ESP32 uses relocatable vectors (`XCHAL_HAVE_VECBASE = 1`). All vectors are offset from **VECBASE** (SR 231).
### Vector offsets in VECBASE
| Offset | Vector | Symbol |
|--------|--------|--------|
| 0x000 | Window overflow 4 | `_WindowOverflow4` |
| 0x040 | Window underflow 4 | `_WindowUnderflow4` |
| 0x080 | Window overflow 8 | |
| 0x0C0 | Window underflow 8 | |
| 0x100 | Window overflow 12 | |
| 0x140 | Window underflow 12 | |
| 0x180 | **Level 2 interrupt** | `_Level2InterruptVector` |
| 0x1C0 | **Level 3 interrupt** | `_Level3InterruptVector` |
| 0x200 | **Level 4 interrupt** | `_Level4InterruptVector` |
| 0x240 | **Level 5 interrupt** | `_Level5InterruptVector` |
| **0x280** | **Level 6 interrupt (= DEBUG)** | `_DebugExceptionVector` |
| 0x2C0 | Level 7 interrupt (= NMI) | `_NMIExceptionVector` |
| 0x300 | **Kernel exception** | `_KernelExceptionVector` |
| 0x340 | **User exception** | `_UserExceptionVector` |
| 0x3C0 | **Double exception** | `_DoubleExceptionVector` |
### Debug exception vector
```
XCHAL_DEBUG_VECOFS = 0x00000280
XCHAL_DEBUG_VECTOR_VADDR = 0x40000280 (at reset VECBASE = 0x40000000)
XCHAL_INTLEVEL6_VECOFS = 0x00000280 (same as DEBUG — level 6 IS debug)
```
**Critical:** On the ESP32, **interrupt level 6 IS the debug level**. There are no regular interrupts at level 6 (`XCHAL_INTLEVEL6_MASK = 0x00000000`). The debug exception vector at offset `0x280` from VECBASE is the entry point for ALL debug exceptions.
### Reset VECBASE value
```
XCHAL_VECBASE_RESET_VADDR = 0x40000000 (IRAM0 — instruction RAM)
```
### Debug vector code (from `xtensa_vectors.S`)
```asm
.section .DebugExceptionVector.text, "ax"
.global _DebugExceptionVector
.align 4
_DebugExceptionVector:
wsr a0, EXCSAVE6 /* preserve a0 in EXCSAVE6 */
j xt_debugexception /* jump to handler (not call — a0 is saved) */
```
The vector is minimal — it saves `a0` to `EXCSAVE6` and jumps to the full handler. The handler then:
1. Sets up a stack frame
2. Saves EPC6, EPS6 to the frame
3. Reads DEBUGCAUSE to determine why
4. Handles the debug event
5. Restores context and executes `rfi 6`
---
## 5. PS Register (SR 230) — Full Bit Layout
From `corebits.h`:
```
31 19 18 17 16 15 9 8 6 5 4 3 0
+-----------------------+-----+-------+--------+-------+-----+----+--------+
| reserved | WOE | CALLINC| reserved| OWB | UM |EXCM| INTLEVEL|
+-----------------------+-----+-------+--------+-------+-----+----+--------+
```
| Bits | Mask | Field | Description |
|------|------|-------|-------------|
| [3:0] | 0x0000000F | **INTLEVEL** | Current interrupt level (015). Interrupts at or below this level are masked. |
| [4] | 0x00000010 | **EXCM** | Exception mode. When set, masks interrupts to `XCHAL_EXCM_LEVEL` (= 3 on ESP32). |
| [5] | 0x00000020 | **UM** | User mode (1 = user/kernel mode distinction; 0 = privileged) |
| [7:6] | 0x000000C0 | **RING** | Current protection ring (03). ESP32 has `XCHAL_MMU_RINGS = 1`, so always 0. |
| [11:8] | 0x00000F00 | **OWB** | Old Window Base (saved WINDOWBASE on exception entry for windowed ABI) |
| [17:16] | 0x00030000 | **CALLINC** | Call increment (windowed ABI — how many registers the current call frame uses: 4, 8, or 12) |
| [18] | 0x00040000 | **WOE** | Window Overflow Enable (1 = window exceptions enabled; 0 = disabled) |
### CALL0 ABI considerations
For the CALL0 ABI (no windowed registers):
- `WOE = 0` — window overflow exceptions disabled
- `CALLINC = 0` — no window call increment
- `OWB` — unused/irrelevant
ABI-specific PS values for setting up a handler:
```c
// CALL0 ABI:
#define PS_GDBSTUB_HANDLER PS_INTLEVEL(5) | PS_UM // = 0x25
// The IDF gdbstub uses: PS_INTLEVEL(5) | PS_UM | PS_WOE // but WOE is harmless if WOE=0 for CALL0
// For user exception exit (CALL0):
#define PS_FOR_C_CALL PS_INTLEVEL(XCHAL_DEBUGLEVEL - 2) | PS_UM // INTLEVEL(4) | UM = 0x24
```
### INTLEVEL semantics
- `INTLEVEL = 0`: All interrupts enabled
- `INTLEVEL = N`: Masks interrupts at level ≤ N
- `INTLEVEL = 6`: Debug level — masks everything except NMI and debug exceptions
- `INTLEVEL = 7`: NMI level — masks everything
When a debug exception is taken, PS is saved to EPS6 and the new PS.INTLEVEL is set to 6.
### Important PS note for rfi 6
Before executing `rfi 6`, EPS6 must contain the PS value you want to restore. The INTLEVEL in EPS6 determines the post-return interrupt mask. Typically, you restore the original (pre-exception) PS from your saved frame.
---
## 6. The `rfi 6` Instruction (Return from Interrupt)
### Syntax
```
rfi n
```
where `n` is the interrupt level (17). For debug exceptions: **`rfi 6`**.
### Semantics
`rfi n` performs an atomic return-from-interrupt:
1. **Restores PS** from `EPS_n` (for n=6: from `EPS6`, SR 198)
2. **Sets PC** from `EPC_n` (for n=6: from `EPC6`, SR 182)
3. Begins execution at the restored PC
4. The new PS.INTLEVEL takes effect (unmasking interrupts as appropriate)
### What must be in EPC6/EPS6 before `rfi 6`
| Register | Must contain |
|----------|-------------|
| **EPC6** (SR 182) | The **PC to resume at**. For a breakpoint: the address of the instruction *after* the `break` (or the same address if you want to re-execute). For a single-step: the PC of the next instruction to execute. |
| **EPS6** (SR 198) | The **PS to restore** — the original pre-exception PS (interrupt level, UM, EXCM, etc. of the interrupted code). |
### Critical detail for software breakpoints
When `break 0,0` fires, `EPC6` points to the **address of the break instruction itself** (or the next instruction depending on the implementation). The IDF gdbstub reads EPC6 into the frame and adjusts it:
```asm
rsr a0, (EPC + XCHAL_DEBUGLEVEL) /* read EPC6 */
s32i a0, sp, XT_STK_PC /* save to frame */
```
On return, the handler writes the (possibly modified) PC back to EPC6 before `rfi 6`:
```asm
l32i a0, sp, XT_STK_PC
wsr a0, (EPC + XCHAL_DEBUGLEVEL) /* restore EPC6 */
...
rfi 6
```
**If you placed a software breakpoint (`break 0,0`) in flash/IRAM, you MUST:**
1. Restore the original instruction bytes before resuming
2. Set EPC6 to the address of that (now-restored) instruction
3. Execute `rfi 6`
Otherwise the CPU will re-hit the breakpoint immediately.
### rsync requirement
After `wsr` to PS, you should execute `rsync` before relying on the new PS value. After `wsr` to other registers, `isync` may be needed before the change takes effect on the instruction stream.
---
## 7. ICOUNT Single-Step Mechanism
### How it works
**ICOUNT** (SR 236) is a signed 32-bit counter that increments on every instruction executed at an interrupt level **≥ ICOUNTLEVEL**. When ICOUNT transitions from negative to zero (i.e., reaches zero), a **debug exception is generated** if the current interrupt level ≥ ICOUNTLEVEL.
### The magic value: ICOUNT = 0xFFFFFFFE (= -2)
```
ICOUNT = 0xFFFFFFFE (= signed -2)
ICOUNTLEVEL = N
```
**Sequence of events:**
1. Set `ICOUNTLEVEL = N` — only count instructions executed at INTLEVEL ≥ N
2. Set `ICOUNT = 0xFFFFFFFE` — start at -2
3. Execute `rfi 6` to return to the target code
4. **First instruction** executes: ICOUNT increments to `0xFFFFFFFF` (-1)
5. **Second instruction** executes: ICount increments to `0x00000000` (0) — **this triggers the debug exception**
Wait — this would step TWO instructions. But the IDF code says "do one step." Let me clarify:
**Actually, the correct interpretation:** The debug exception fires when ICOUNT *would increment to zero*, meaning the exception is taken *before* executing the instruction that would make it zero. So:
- ICOUNT = -2, after `rfi 6`:
- Instruction 1 executes → ICOUNT becomes -1
- Instruction 2 is about to execute → ICOUNT would become 0 → **debug exception fires INSTEAD of executing instruction 2**
So **one instruction executes**, then the debug exception fires. EPC6 points to the instruction that was *about* to execute (the second one), i.e., the **next instruction after the step**.
**Correction/Refinement (from IDF code):**
The IDF `esp_gdbstub_do_step()` does:
```c
void esp_gdbstub_do_step(esp_gdbstub_frame_t *frame)
{
uint32_t level = s_scratch.regfile.ps;
level &= 0x7; // current INTLEVEL from saved PS
level += 1; // set ICOUNTLEVEL one above current
WSR(ICOUNTLEVEL, level);
WSR(ICOUNT, -2); // 0xFFFFFFFE
}
```
The `-2` value means: execute **one more instruction** after returning, then trap.
### ICOUNTLEVEL selection
`ICOUNTLEVEL` must be set such that the instruction to be stepped runs at a level ≥ ICOUNTLEVEL. The IDF sets it to `(current PS.INTLEVEL + 1)`.
**Why +1?** Because when the code resumes after `rfi 6`, it runs at the PS.INTLEVEL saved in EPS6. If ICOUNTLEVEL equals that INTLEVEL, the counter would NOT increment (instructions only count when INTLEVEL ≥ ICOUNTLEVEL... actually instructions count when the current level is **≤** ICOUNTLEVEL per the ISA — see clarification below).
**ISA rule (from Xtensa ISA Reference):** ICOUNT increments on each instruction executed at an interrupt level **less than or equal to** ICOUNTLEVEL. So to count all normal code (INTLEVEL 05), set ICOUNTLEVEL ≥ the code's INTLEVEL.
The IDF sets `ICOUNTLEVEL = current_intlevel + 1` to ensure:
- The stepped instruction (at the code's original INTLEVEL) is counted
- But interrupts at higher levels are not accidentally counted
### Clearing single-step
```c
void esp_gdbstub_clear_step(void)
{
WSR(ICOUNT, 0); // Set to 0 — but since 0 is the trigger, this would immediately fire
WSR(ICOUNTLEVEL, 0); // Set level to 0 — no instructions execute at level ≤ 0...
// actually INTLEVEL 0 means count nothing useful
}
```
Setting `ICOUNTLEVEL = 0` effectively disables stepping because no executed instruction has INTLEVEL ≤ 0 (except... level 0 code itself, but the check is "INTLEVEL ≤ ICOUNTLEVEL" — if both are 0, it would count).
**The reliable way to disable:** Set `ICOUNTLEVEL = 0` AND `ICOUNT = 0`. When ICOUNTLEVEL is 0, only level-0 instructions increment ICOUNT. When ICOUNT is already 0, it stays at 0 and the debug exception condition (transition through zero) doesn't re-fire because there's no increment happening.
Actually, the precise ISA semantics: a debug exception due to ICOUNT occurs when **ICOUNT transitions from non-zero to zero** during an increment. Setting ICOUNT=0 directly (via wsr) does **not** trigger the exception — only the hardware increment path does.
### Summary of single-step setup
```
To single-step exactly ONE instruction:
1. Read the target code's PS (from EPS6 / saved frame)
2. Extract INTLEVEL: level = ps & 0xF
3. Set ICOUNTLEVEL = level (count instructions at this level or below)
4. Set ICOUNT = 0xFFFFFFFE (-2)
5. Execute rfi 6 to resume
6. One instruction executes, ICOUNT → -1 → next increment would be 0 → debug exception
7. EPC6 now points to the instruction AFTER the one that executed
```
---
## Quick Reference: Building the GDB Stub
### Software breakpoint (insert)
Replace the target instruction with `break 0, 0` (3-byte encoding). Save the original bytes for restoration.
### Software breakpoint (handle)
```asm
; In debug exception handler:
rsr a0, DEBUGCAUSE
bbci a0, 3, not_a_break ; bit 3 = BREAK
; It's a software breakpoint
; EPC6 points at/near the break instruction
; Restore original instruction, report to GDB
```
### Hardware breakpoint (IBREAK)
```c
// Set breakpoint at addr using IBREAKA0:
wsr(IBREAKA_0, addr);
uint32_t en = rsr(IBREAKENABLE);
en |= (1 << 0); // enable bit 0
wsr(IBREAKENABLE, en);
```
### Watchpoint (DBREAK)
```c
// Set watchpoint at addr, size bytes:
uint32_t dbreakc = 0x3F; // mask: all 6 bits = full 32-bit
dbreakc <<= (ffs(size) - 1); // shift for byte select
dbreakc &= 0x3F;
if (on_read) dbreakc |= (1 << 30); // DBREAKC_LOADBREAK
if (on_write) dbreakc |= (1 << 31); // DBREAKC_STOREBREAK
wsr(DBREAKA_0, addr);
wsr(DBREAKC_0, dbreakc);
```
### DBREAKC register bit layout
| Bits | Field | Description |
|------|-------|-------------|
| [31] | STOREBREAK | Trigger on store |
| [30] | LOADBREAK | Trigger on load |
| [5:0] | MASK | Byte select mask (0x3F = all bytes; shift left by log2(size)) |
### Debug exception handler skeleton (CALL0 ABI)
```asm
_DebugExceptionVector:
wsr a0, EXCSAVE6 ; save a0
j _gdbstub_debug_handler ; jump to handler
_gdbstub_debug_handler:
; Save context (a0 already in EXCSAVE6)
mov a0, sp
addi sp, sp, -FRAME_SIZE
s32i a0, sp, OFF_SP
rsr a0, EPS6 ; save PS
s32i a0, sp, OFF_PS
rsr a0, EPC6 ; save PC
s32i a0, sp, OFF_PC
rsr a0, EXCSAVE6 ; recover original a0
s32i a0, sp, OFF_A0
rsr a0, DEBUGCAUSE ; get debug cause
s32i a0, sp, OFF_DEBUGCAUSE
; ... save remaining regs, call C handler ...
; Set up PS for C handler execution (allow level 5 interrupts, clear EXCM)
movi a0, PS_INTLEVEL(5) | PS_UM ; = 0x25 for CALL0
wsr a0, PS
rsync
; Call C handler with frame pointer
mov a2, sp ; arg0 = frame pointer (CALL0 ABI)
call0 gdbstub_debug_entry
; Restore and return
l32i a0, sp, OFF_PC
wsr a0, EPC6 ; restore PC (may be modified by GDB)
l32i a0, sp, OFF_PS
wsr a0, EPS6 ; restore PS
; ... restore remaining regs ...
l32i a0, sp, OFF_SP
addi sp, sp, FRAME_SIZE
rfi 6 ; return from debug exception
```
---
## Key Constants Summary
| Constant | Value | Source |
|----------|-------|--------|
| `XCHAL_DEBUGLEVEL` | **6** | core-isa.h:405 |
| `XCHAL_NMILEVEL` | **7** | core-isa.h:407 |
| `XCHAL_NUM_IBREAK` | **2** | core-isa.h:613 |
| `XCHAL_NUM_DBREAK` | **2** | core-isa.h:614 |
| Debug vector offset | **0x280** | core-isa.h:588 |
| `XCHAL_VECBASE_RESET_VADDR` | **0x40000000** | core-isa.h:549 |
| `XCHAL_EXCM_LEVEL` | **3** | core-isa.h:351 |
| CONFIGID0 | **0xC2BCFFFE** | core-isa.h:187 |
| CONFIGID1 | **0x1CC5FE96** | core-isa.h:188 |
### Register quick-access (for `rsr`/`wsr`)
```
EPC_6 = 182 EPS_6 = 198 EXCSAVE_6 = 214
DEBUGCAUSE = 233 ICOUNT = 236 ICOUNTLEVEL = 237
IBREAKENABLE = 96 IBREAKA_0 = 128 IBREAKA_1 = 129
DBREAKA_0 = 144 DBREAKA_1 = 145 DBREAKC_0 = 160 DBREAKC_1 = 161
PS = 230 VECBASE = 231 EXCCAUSE = 232
CONFIGID0 = 176 CONFIGID1 = 208
```
---
*Document generated from ESP-IDF v5.5.4 HAL source and Xtensa LX6 configuration headers.*