Skip to content

contrib/plugins: add MicroBlaze cycle-counting plugin (mb_cycles)#108

Open
thesamprice wants to merge 6 commits into
Xilinx:masterfrom
thesamprice:microblaze-mb-cycles-plugin
Open

contrib/plugins: add MicroBlaze cycle-counting plugin (mb_cycles)#108
thesamprice wants to merge 6 commits into
Xilinx:masterfrom
thesamprice:microblaze-mb-cycles-plugin

Conversation

@thesamprice

Copy link
Copy Markdown

Summary

  • Adds contrib/plugins/mb_cycles.c, a QEMU plugin for counting cycles on MicroBlaze targets using the UG984 §5 latency tables (C_AREA_OPTIMIZED selectable, default=0).
  • Adds a memory-mapped exit/counter device to hw/microblaze/petalogix_s3adsp1800_mmu.c at 0xFF000000 for use by bare-metal benchmark runtimes.

Plugin details

Two modes selectable at runtime:

  • stalls=off (default) — throughput model using QEMU_PLUGIN_INLINE_ADD_U64; minimal overhead
  • stalls=on — full RAW-stall model using per-instruction callbacks

FPU instructions (opcode 0x16) dispatch on instruction bits[9:7] for per-instruction latencies (fdiv=30 cycles, fsqrt=27, fadd/frsub/fint=4, fmul=4, flt=5). All three C_AREA_OPTIMIZED levels are supported via area_opt=0|1|2 plugin argument.

WIC (0x24, func bit[3]=1) correctly counted as 2 cycles.

Exit device (0xFF000000)

  • Write 0 → clean qemu_system_shutdown_request
  • Write non-zero → panic exit with code
  • Read +4 / +8 → low/high 32 bits of QEMU_CLOCK_VIRTUAL (ns); equals instruction count with -icount 0

Test plan

  • Build with meson / make for microblaze and microblazeel targets
  • Run bare-metal ELF under qemu-system-microblazeel -M petalogix-s3adsp1800 -plugin libmb_cycles.so
  • Verify cycle totals are non-zero and CPI is printed on exit

🤖 Generated with Claude Code

thesamprice and others added 6 commits June 27, 2026 20:01
Throughput model (stalls=off, default): counts 1 cycle per issued
instruction with opcode-class overrides from UG984 §5 (idiv=34,
fpu=6, mts/mfs=2).  Uses QEMU_PLUGIN_INLINE_ADD_U64 — minimal
overhead.

RAW-stall model (stalls=on): additionally inserts pipeline bubble
cycles when an instruction reads a register whose forwarded value is
not yet ready.  Pipeline model: C_AREA_OPTIMIZED=0 with forwarding:
ALU→ALU is 0 stalls; load-use (lw/lh/lb → use) is 1 stall; blocking
instructions (idiv, fpu) incur no extra stall because their base
throughput cost already covers the latency.

Uses full per-instruction callbacks in stall mode; throughput mode
keeps the INLINE_ADD_U64 path for benchmarking.

Options: verbose=on  prints per-opcode-class breakdown on exit.
         stalls=on   enables the RAW-stall pipeline model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
petalogix_s3adsp1800_mmu: add memory-mapped exit/counter device at 0xFF000000.
  - Write 0 → clean QEMU shutdown; nonzero → panic exit with code.
  - Reads at +4/+8 return lo/hi of QEMU virtual clock (ns), which with
    -icount 0 equals the instruction count.  Used by bare-metal benchmarks.

mb_cycles: per-instruction FPU latency dispatch and configurable area_opt.
  - opcode 0x16 (FPU) now dispatches on instruction bits[9:7] to give
    per-instruction latencies: fadd/frsub=4, fmul=4, fdiv=30, fcmp=4,
    flt=5, fint=4, fsqrt=27 (C_AREA_OPTIMIZED=0).
  - Full 3×8 latency table covers all three C_AREA_OPTIMIZED levels;
    select at runtime via area_opt=0|1|2 plugin argument.
  - WIC (opcode 0x24, func bit[3]=1) correctly counted as 2 cycles.
  - stall model: base_cyc packed into meta[31:24] at translation time,
    eliminating the table lookup from the hot callback path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ection

Branch taken/not-taken penalties (per UG984 §5):
  - Non-D conditional (bgti, bnei, …): 1 cycle not-taken, 3 cycles taken.
    Implemented via retroactive accounting: save fallthrough_pc at the end
    of each TB; if the next TB starts elsewhere, add 2 extra wcycles.
  - Non-D unconditional (bri, bra, …): always 3 cycles.
  - D-form (bgtid, bneid, rtsd, …): 1 cycle; delay slot counted separately
    at its own cost.  No branch-taken penalty (delay slot absorbs it).

Fix is_delayed_branch() D-bit detection:
  - 0x26 (br/brd): D was at bit 8 (wrong), now bit 20 (Ra field MSB).
  - 0x27 (beq/beqd): D was at bit 20 (wrong), now bit 25 (rD field MSB).
  - 0x2E (bri/brid): D was at bit 8 (wrong), now bit 20.
  - 0x2F (beqi/beqid): D was at bit 20 (wrong), now bit 25.

Also add:
  - Cross-TB load-use stall detection for loads at TB boundaries.
  - delay_slot_filled_count / delay_slot_nop_count metrics per benchmark.
  - stalls=on mode tracks pipeline_cycle and reg_ready per-register.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BSEFI (opcode 0x19, bit[14]=1) extracts bits [IMMW:IMMS] from rA, where
IMMW is the end-bit index (inclusive) and IMMS is the start bit.  The
correct width is (IMMW - IMMS + 1).

gen_bsefi was incorrectly passing imm_w directly as the length argument to
tcg_gen_extract_i32, treating it as a field width rather than an end-bit
index.  For bsefi r22, r19, 27, 24 (extract nibble 6), QEMU was extracting
27 bits from bit 24 instead of 4 bits — reading well past the h[] table
and returning garbage.

gen_bsifi already uses the correct (imm_w - imm_s + 1) convention; align
gen_bsefi to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit changed gen_bsefi to interpret bits[10:6] as an end-bit
index (imm_w - imm_s + 1) to match the (wrong) LLVM backend encoding.  That
made QEMU agree with our wrong toolchain but diverge from real hardware and
GAS.

Upstream GAS (AMD/Xilinx 2023 tc-microblaze.c) and upstream QEMU master both
store WIDTH directly in bits[10:6] for BSEFI.  BSIFI is the asymmetric one:
it stores end-bit = start+width-1 in bits[10:6].  The correct LLVM fix is in
tryBSEFI (see companion llvm-project commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-use detection

Detect the 1-cycle load-use stall at TB translation time by inspecting
adjacent instruction pairs, baking the penalty into a constant inline
add. Cross-TB pairs are resolved with one lightweight callback per TB.
Removes the stalls=on/off mode split; the weighted model now includes
load-use stalls by default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thesamprice added a commit to thesamprice/mb-tester that referenced this pull request Jul 7, 2026
Each modified submodule now tracks its own feature branch with our commits,
ready for upstream review. The master/main branches of each fork are left
at upstream state.

  qemu-xilinx   → branch: microblaze-mb-cycles-plugin   PR: Xilinx/qemu#108
  xilinx-gcc    → branch: microblaze-fix-macro-concat    PR: Xilinx/gcc#2
  xilinx-gcc-13 → branch: microblaze-multilib-le         PR: thesamprice/gcc#1
  picolibc      → branch: microblaze-cross-clang          PR: picolibc/picolibc#1299

llvm-project remains on microblaze/lld (still in development).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant