# Hexagon support

IDA disassembles and analyzes Qualcomm Hexagon (QDSP6) code: the VLIW DSP found in Snapdragon modem, audio, sensor, compute, and NPU subsystems. The processor module covers the scalar ISA from V4 through V79, the HVX vector extension (V60 and later), and the HMX matrix extension. "Hexagon" is a property of the code, not of a file format: it is decoded by the processor module once a loader has placed the bytes in the database. The [loading section](#loading-hexagon-code) covers how that happens.

{% hint style="info" %}
Hexagon support is new and still settling. IDA 9.4 ships the disassembler and the analysis layer: cross-references, stack frames, calling-convention support, and switch recovery. The Hexagon decompiler is not part of IDA 9.4.
{% endhint %}

Hexagon is unlike most architectures IDA targets, so this page starts with the one idea that the rest depends on: the instruction packet. The later sections cover registers, memory addressing, and the display options that control how all of this appears in the listing.

## The packet model

Hexagon does not execute one instruction at a time. It executes a *packet*: a group of one to four 32-bit instructions that issue together. Every instruction in a packet reads its source registers at the same instant, the instructions execute in parallel, and their results are written at the end of the packet. Branch decisions are also taken at packet end. One instruction's result is not visible to another instruction in the same packet, with one exception covered below (`.new` values).

This changes what a single line of disassembly means. On a sequential machine, the instruction above you has already run. In a Hexagon packet, the instruction above you runs at the same time as the one you are reading. IDA shows packets explicitly so that this is never ambiguous.

The packet boundary is encoded in two bits of every instruction word, bits 15:14, called the parse bits:

| Parse bits | Meaning                                                                                                                                                                                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `01`       | Not the last instruction; the packet continues                                                                                                                                                                     |
| `11`       | Last instruction of the packet                                                                                                                                                                                     |
| `10`       | A hardware-loop-end marker. It does not end the packet: it sits on the first instruction (inner loop, `:endloop0`) and/or the second (outer loop, `:endloop1`), and the packet still ends on a later `11` or `00`. |
| `00`       | A constant extender (when the instruction class is 0) or a duplex (otherwise); a duplex ends the packet                                                                                                            |

IDA reads these bits during analysis, marks the first and last instruction of each packet, resolves `.new` references, and records the loop-end markers. A packet is at most 16 bytes, so the boundary is always found within four words.

### How packets appear in the listing

A packet is wrapped in braces. With the default options, each brace sits on its own line and the instructions are indented beneath it:

```
{
    r5 = add(r5, #1)
    r6 = memw(r5 + #0xc)
}
```

Single-instruction packets are braced the same way, which keeps the packet structure uniform down the whole listing:

```
{
    jumpr r31
}
```

Brace placement is configurable. You can put the opening brace inline with the first instruction, drop the braces around single-instruction packets, or prefix continuation lines with a pipe instead of spaces. See [display and annotation options](#display-and-annotation-options).

### Parallel execution and .new values

Because a packet's writes land together at the end, an instruction normally reads the *old* value of any register that another instruction in the same packet is writing. The `.new` qualifier is the exception: it lets an instruction read a value produced earlier in the same packet, before that value reaches the register file.

```
{
    p0 = cmp.eq(r6, r6)
    if (p0.new) r5:4 = combine(r6, r7)
}
```

Here the compare produces `p0` and the conditional move consumes `p0.new` from the same packet. The same applies to register producers feeding a new-value store, such as `memw(r0) = r2.new`. IDA resolves the producer during analysis and, by default, marks it. The related qualifiers `.cur` and `.tmp` (used by HVX loads) are rendered the same way, as register postfixes.

### Duplex instructions

For code density, two sub-instructions can be packed into a single 32-bit word, called a duplex. A duplex is always the last word of its packet. The two halves are drawn from a restricted set of operations (small loads, small stores, and common ALU operations) and use a compressed register encoding that reaches a subset of the general registers.

By default IDA shows the two sub-instructions on separate lines, so a duplex reads like any other two-instruction packet:

```
{
    r21 = add(r21, #1)
    jumpr r31
}
```

If you prefer the assembler's single-line form, turn off the duplex line break and the two halves are joined with a semicolon: `r21 = add(r21, #1); jumpr r31`.

### Constant extenders

Hexagon immediates are narrow. To carry a full 32-bit constant, an instruction is preceded by a constant extender word (`immext`) that supplies the high bits. The extender is not a real operation; it only widens the immediate of the instruction that follows it.

IDA folds the extender into the instruction it extends and shows the resulting 32-bit constant with a doubled hash:

```
{
    r6 = ##0x11223344
}
```

The single `#` marks an ordinary immediate; the doubled `##` marks one that was extended. The extender word does not get its own line.

### Hardware loops

Hexagon has zero-overhead hardware loops, set up with `loop0`/`loop1` and bounded by a packet whose parse bits mark a loop end instead of a branch. There are two loop levels, controlled by the `sa0`/`lc0` and `sa1`/`lc1` control registers. IDA marks the loop-end packet with a suffix on the closing brace:

```
{
    r0 = add(r0, #0x2b)
    r6 = memub(r21 + #0xe)
} :endloop1
```

The marker is `:endloop0` for the inner loop, `:endloop1` for the outer loop, and `:endloop01` when both end on the same packet.

### Parallel branches

A packet can contain more than one branch. All of the branch predicates are evaluated at packet start, in parallel. If several would be taken, the branch in the lower slot wins and the others are discarded by the hardware; a single target is selected.

Treating such a packet as a sequence of separate conditional branches misrepresents it. A sequential reading implies that the second branch is reached only when the first one falls through, and it attaches a path predicate to each branch that does not match the hardware. The module instead models the packet as one multiway split. It orders the branches by slot priority and derives a path predicate for each edge that accounts for the higher-priority branches: the edge taken at slot 1 carries `!pred(slot0) && pred(slot1)`, and the fall-through edge is the conjunction of every branch's negation. Code references are attached at the packet's start address, not at the individual branch instructions, so the control-flow graph shows one packet-level set of successors.

By default IDA flags these packets visually. The braces are doubled, each instruction can carry its execution slot, and the resolved path predicates are shown as a comment on the closing brace:

```
{{
    [S0] if (p0) jump:t branch_a
    [S1] if (!p1) jump:t branch_b
    [S2] r20 = add(r20, #1)
}} /* p0->branch_a, !p0 && !p1->branch_b */
```

The double braces and slot labels are controlled by separate options. The packet-aware control-flow graph (the part that changes how successors and cross-references are computed, not how the packet is drawn) is a distinct option that is off by default; enable *use packet-aware parallel branch analysis* to turn it on.

The slot shown in the listing is derived from each instruction's position in the packet, not from a full slot-assignment scheduler, so treat it as an approximation of the architectural slot.

## Registers

Hexagon register names are written in lowercase in the listing. The module models the full register file:

| Class            | Registers               | Notes                                                                                                                                                                   |
| ---------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| General purpose  | `r0`–`r31`              | `sp` is `r29`, `fp` is `r30`, `lr` is `r31`. 64-bit values use even/odd pairs written `r1:0` through `r31:30`.                                                          |
| Scalar predicate | `p0`–`p3`               | Conditional execution and compare results. The four predicates are also accessible as the predicate-pair control register, shown as `p3:0`.                             |
| Control          | `c0`–`c31`              | Hardware-loop registers `sa0`/`lc0`/`sa1`/`lc1`, addressing modifiers `m0`/`m1`, the global pointer `gp`, the program counter `pc`, and the user status register `usr`. |
| HVX vector       | `v0`–`v31`              | 128-byte vectors. Pairs are written `v1:0`, quads `v3:0`. Element types are postfixes: `.b`, `.h`, `.w`, the unsigned forms, and the float forms `.sf`/`.hf`/`.bf`.     |
| HVX predicate    | `q0`–`q3`               | Vector masks produced by vector compares.                                                                                                                               |
| System and guest | `s0`–`s127`, `g0`–`g31` | Privileged (monitor) and virtualization state.                                                                                                                          |

A leading `!` negates a predicate (`if (!p0) ...`); a trailing `++` marks a post-incremented address register.

## Memory addressing modes

Hexagon has a wide set of addressing modes. The access size is part of the mnemonic: `memb`/`memub` for signed and unsigned bytes, `memh`/`memuh` for half-words, `memw` for words, `memd` for double-words, and `vmem`/`vmemu` for aligned and unaligned HVX vectors. The addressing form is shown inside the parentheses:

| Mode                        | Example                                           |
| --------------------------- | ------------------------------------------------- |
| Absolute                    | `memw(##0x1000)`                                  |
| Absolute with register set  | `memw(r1 = ##0x1000)`                             |
| Base + offset               | `memw(r1 + #8)`                                   |
| Base + scaled index         | `memw(r1 + r2<<#2)`                               |
| GP-relative                 | `memw(gp + #0x100)`                               |
| Post-increment by immediate | `memw(r1++ #4)`                                   |
| Post-increment by modifier  | `memw(r1++ m0)`                                   |
| Circular                    | `memw(r1++ #4:circ(m0))`, `memw(r1++ I:circ(m0))` |
| Bit-reversed                | `memw(r1++ m0:brev)`                              |
| Locked (atomic)             | `memw_locked(r1)`, `memd_locked(r1, p0)`          |

Circular and bit-reversed modes use the `m0`/`m1` modifier registers, which is why those addressing forms reference a control register. Acquire/release and non-temporal accesses carry an infix or suffix on the mnemonic, such as `memw_aq` or `:nt`.

## Display and annotation options

The Hexagon module exposes two groups of options in its processor options dialog (**Options → General… → Analysis → Processor specific analysis options**). The display options control packet layout; the annotation options control the extra information the module derives and shows. The tables below give each option, its default, and what it does.

Display options:

| Option                                    | Default | Effect                                                                                            |
| ----------------------------------------- | ------- | ------------------------------------------------------------------------------------------------- |
| Open brace on its own line                | on      | Places the opening packet brace `{` on its own line instead of inline with the first instruction. |
| Close brace on its own line               | on      | Places the closing brace `}` on its own line.                                                     |
| Braces for single-instruction packets     | on      | Shows braces even when a packet holds one instruction.                                            |
| Duplex sub-instructions on separate lines | on      | Splits a duplex onto two lines; when off, the halves are joined with `;`.                         |
| Pipe prefix for continuation lines        | off     | Prefixes continuation lines with \`                                                               |

Annotation options:

| Option                                        | Default | Effect                                                                           |
| --------------------------------------------- | ------- | -------------------------------------------------------------------------------- |
| Mark `.new` producers                         | on      | Marks the instruction that produces a `.new` value consumed in the same packet.  |
| Mark extender usage                           | on      | Shows that an immediate was widened by a constant extender.                      |
| Show hardware loop markers                    | on      | Shows `loop`/`endloop` structure.                                                |
| Stack argument comments                       | on      | Simplifies redundant stack-variable displacements and annotates stack arguments. |
| Show slot assignment labels                   | on      | Shows `[S0]`–`[S3]` slot labels on instructions in parallel-branch packets.      |
| Use double braces for parallel-branch packets | on      | Draws `{{ }}` around packets that contain parallel branches.                     |
| Show resolved path predicates                 | on      | Shows the per-edge path predicates for parallel-branch packets.                  |
| Show `:mem_noshuf` inference                  | off     | Shows the inferred no-shuffle suffix on vector accesses.                         |
| Show predicate `.new` timing                  | off     | Annotates predicates generated and consumed within one packet.                   |
| Show memory ordering                          | off     | Comments acquire, release, locked, and non-temporal semantics.                   |
| Resolve PC-relative targets                   | off     | Shows the absolute address of a PC-relative operand.                             |
| Validate packet structure                     | off     | Checks packet legality and flags problems.                                       |
| Explain branch hints                          | off     | Comments the meaning of the `:t` and `:nt` branch hints.                         |
| Use packet-aware parallel branch analysis     | off     | Builds packet-level multiway control flow for parallel-branch packets.           |
| Log decompiler callback decisions             | off     | Writes diagnostic callback decisions to the Output window.                       |

The display defaults follow the layout used by the LLVM Hexagon assembler: braces on their own lines, braces around every packet, and duplex halves split across lines.

## Loading Hexagon code

A Hexagon ELF identifies itself with the machine type `EM_QDSP6` (164). IDA reads this from the ELF header and selects the QDSP6 processor and the Hexagon relocation handler with no manual choice. The ISA version is taken from the ELF flags and shown at load time, so you can see whether a binary targets, say, V66 or V73.

Hexagon code also arrives embedded in Qualcomm MBN firmware. MBN is a container format, not a Hexagon one: the same loader extracts ARM and AArch64 images as readily as Hexagon ones, and a single container can hold several. For the container format and the loading procedure, see [Loading Qualcomm MBN containers](/9.4/core/disassembler/how-tos/loading-qualcomm-mbn.md).

## See also

* [Loading Qualcomm MBN containers](/9.4/core/disassembler/how-tos/loading-qualcomm-mbn.md)
* [Supported processors](/9.4/core/disassembler/specification/supported-processors.md)
* [Segment address space](/9.4/core/disassembler/concepts/segment-address-space.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hex-rays.com/9.4/core/disassembler/concepts/hexagon.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
