// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The microcontroller debug unit of RM0433 rev 9 section 60.5.8, written against the `dbgmcu` layout.
//! Every citation is RM0433 rev 9 unless it names another document.
//!
//! The `DBGMCU` is ST's own extension of the Cortex-M7's debug infrastructure. The core reaches it at
//! `0x5C00_1000`, and a debugger reaches the same registers on the APB-D bus at `0xE00E_1000`, which
//! the emulator's debug stub stands in for. It controls the clocks while a core sleeps, stops or
//! stands by under debug, and which peripherals freeze when the core halts in debug. This is the
//! same part of the chip as the F427's, and the model is its kind: the registers store, and nothing
//! behind them can act, see the second table.
//!
//! **What the model handles and what falls to the register file.** It has no behavior beyond the file's.
//! The registers answer from the values the model keeps in the file: the file applies the reset values,
//! which `memory_map.rs` corrects for the identity code, and the access types.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | Every register at its manual value, `DBGMCU_IDC` corrected to this part's revision, and the other five at zero. `memory_map.rs` carries the correction, since a reset value belongs to the instance | 60.5.8 | `every_register_answers_its_manual_reset_value` |
//! | `DBGMCU_IDC` `DEV_ID` | The device identifier `0x450` of the STM32H742, H743, H753 and H750, read-only | 60.5.8 | `every_register_answers_its_manual_reset_value`, `software_cannot_write_the_read_only_idcode` |
//! | `DBGMCU_IDC` `REV_ID` | The silicon revision, `0x2003` for revision V, picked in `memory_map.rs` since a reset value belongs to the instance. ChibiOS and ArduPilot read none of it on this part | 60.5.8, ES0392 rev 15 Table 2 | `every_register_answers_its_manual_reset_value` |
//! | `DBGMCU_CR` `DBGSLEEP_D1`, `DBGSTOP_D1`, `DBGSTBY_D1` | Store what a debugger or the firmware writes. What they would do, keep the clocks running while the core is in a low-power mode under debug, is a mode this model has no way to enter, see the second table | 60.5.8 | `dbgmcu_cr_stores_every_defined_bit` |
//! | `DBGMCU_CR` `TRACECLKEN`, `D1DBGCKEN`, `D3DBGCKEN`, `TRGOEN` | Store what is written. No trace port, debug clock domain or trigger pin answers behind them, see the second table | 60.5.8 | `dbgmcu_cr_stores_every_defined_bit` |
//! | `DBGMCU_APB3FZ1` | The `WWDG1` freeze bit stores what is written | 60.5.8 | `every_freeze_register_stores_the_bits_the_manual_gives_it` |
//! | `DBGMCU_APB1LFZ1` | Every freeze bit stores what is written, `DBG_TIM12` among them, which ChibiOS sets when TIM12 is the system timer | 60.5.8 | `every_freeze_register_stores_the_bits_the_manual_gives_it`, `chibios_freezes_its_system_timer_in_debug` |
//! | `DBGMCU_APB2FZ1` | Every freeze bit stores what is written | 60.5.8 | `every_freeze_register_stores_the_bits_the_manual_gives_it` |
//! | `DBGMCU_APB4FZ1` | Every freeze bit stores what is written | 60.5.8 | `every_freeze_register_stores_the_bits_the_manual_gives_it` |
//! | System reset | The registers of `DBGMCU_CR` and the four freeze registers are not reset by a system reset, "only by a power on reset". [`KEPT_BY_A_SYSTEM_RESET`] lists them, for the part's reset to carry over | 60.5.8 | the part's reset |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | `DBGSLEEP_D1`, `DBGSTOP_D1` and `DBGSTBY_D1` keeping the clocks and the supply while the core sleeps, stops or stands by | 60.5.8 | The core enters Sleep, Stop or Standby inside the emulator, which sends the runtime no event for it, as `pwr.rs` of the STM32F427 records for the same reason. The bits store and read back |
//! | A freeze bit keeping its peripheral counting, or stopping it, while a debugger halts the core | 60.5.8 | Virtual time is one clock the whole target shares, driven by the instruction count. A debugger halt stops that count for every model at once, so every counter is frozen for the whole halt whatever its bit says. At power-on every bit is clear, which says the peripherals keep running under a halt, so the halt itself is a departure from the reset default. Letting an unset bit mean something would need a second, per-instance clock |
//! | A timer's outputs disabled when `DBG_TIMx` freezes it | 60.5.8, 17.3.21 | The same gap: the halt isn't modeled, so nothing this would gate is either |
//! | The trace clock, the D1 and D3 debug clock domains and the bidirectional trigger pin | 60.5.8 | No trace port, no debug domain split and no trigger pin exist behind the bits. They are stored |
//! | `DBGSLEEP_D2`, `DBGSTOP_D2`, `DBGSTBY_D2`, `DBGSTOP_D3` and `DBGSTBY_D3` of the file | 60.5.8 | ST's file names bits the manual reserves, 60.5.8 giving `DBGMCU_CR` bits 19:3 as reserved. They belong to the dual-core parts, and are stored like any reserved bit |
//! | The page's typos: `DBG_IIWDG1` for `DBG_IWDG1`, and `DBG_LPTIM3`'s text naming LPTIM2 | 60.5.8 | The file's field names are kept. Bit 10 of `DBGMCU_APB4FZ1` is LPTIM3's |
//! | Reserved bits of the registers reading back what was written | 60.5.8 | No field covers a reserved bit in any register of this part, so a register's whole-register write rule is what lands there. It's the same for every reserved bit of every register this part models, and ArduPilot writes none of these |
//! | A narrower access than the word | 60.5.8 | The register file lands a byte or half-word store in its lane regardless |
//! | The debugger's own address, `0xE00E_1000` | 60.5.8 | The debug stub reaches the core, and what it asks of this unit is the bus's |
//! | `DBGMCU_IDC` readable and `DBGMCU_CR` writable "under system reset" | 60.5.8 | Already true without a line of code. A rigless reset restarts the core between one access and the next rather than holding it through an interval |
//! | ES0392 rev 15 | ES0392 rev 15 | No item touches this instance. The whole errata chapter was read for it. Its one mention of the `DBG_RTC` effect, the RTC's alarm flag set again when the core stops in debug, is an item of the RTC |
//!
//! **The revision this part reports.** `memory_map.rs` corrects `DBGMCU_IDC`'s reset value against
//! section 60.5.8 and gives its reason there, since a reset value belongs to the instance.
use crate::device::Device;
use crate::soc::stm32h743::generated::dbgmcu::Register;
/// The registers a system reset leaves alone: all but the identity code. Section 60.5.8: "The DBGMCU
/// registers are not reset by a system reset, only by a power on reset." The part copies their values
/// across a reset, as the identity code reads the same in any case.
pub const KEPT_BY_A_SYSTEM_RESET: [Register; 5] = [
Register::Cr,
Register::Apb3fz1,
Register::Apb1lfz1,
Register::Apb2fz1,
Register::Apb4fz1,
];
/// The microcontroller debug unit of one STM32H743.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Dbg;
impl Device for Dbg {
type Register = Register;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mcu::{Operate, Single};
use crate::soc::stm32h743::builder;
use crate::soc::stm32h743::generated::DBGMCU;
use crate::soc::stm32h743::generated::dbgmcu::{apb1lfz1, cr};
use crate::soc::{Layout, Width};
use crate::time::VirtualNanos;
const NOW: VirtualNanos = VirtualNanos(0);
fn dbgmcu() -> Single<Dbg> {
Single::new(builder(), "DBGMCU", Dbg).expect("the bus builds")
}
fn address(register: Register) -> u64 {
DBGMCU.base + u64::from(register.spec().offset)
}
fn read(soc: &mut Single<Dbg>, register: Register) -> u64 {
soc.read(NOW, address(register), Width::W32).unwrap()
}
fn write(soc: &mut Single<Dbg>, register: Register, value: u64) {
soc.write(NOW, address(register), Width::W32, value)
.unwrap();
}
/// The bits of every field of `register` the file defines.
fn defined(register: Register) -> u64 {
register
.spec()
.fields
.iter()
.fold(0, |bits, field| bits | field.mask())
}
#[test]
fn every_register_answers_its_manual_reset_value() {
let mut soc = dbgmcu();
assert_eq!(
read(&mut soc, Register::Idc),
0x2003_6450,
"revision V's REV_ID and 0x450's DEV_ID, 60.5.8"
);
assert_eq!(
Register::Idc.spec().fields[0].get(read(&mut soc, Register::Idc)),
0x450,
"DEV_ID"
);
for register in KEPT_BY_A_SYSTEM_RESET {
assert_eq!(read(&mut soc, register), 0, "{}", register.spec().name);
}
}
#[test]
fn software_cannot_write_the_read_only_idcode() {
let mut soc = dbgmcu();
write(&mut soc, Register::Idc, 0);
assert_eq!(read(&mut soc, Register::Idc), 0x2003_6450);
}
#[test]
fn dbgmcu_cr_stores_every_defined_bit() {
let mut soc = dbgmcu();
let every_bit = defined(Register::Cr);
write(&mut soc, Register::Cr, every_bit);
assert_eq!(read(&mut soc, Register::Cr), every_bit);
for field in [
cr::DBGSLEEP_D1,
cr::DBGSTOP_D1,
cr::DBGSTBY_D1,
cr::TRACECLKEN,
cr::D1DBGCKEN,
cr::D3DBGCKEN,
cr::TRGOEN,
] {
assert!(field.is_set(read(&mut soc, Register::Cr)), "{}", field.name);
}
write(&mut soc, Register::Cr, 0);
assert_eq!(read(&mut soc, Register::Cr), 0);
}
#[test]
fn every_freeze_register_stores_the_bits_the_manual_gives_it() {
let mut soc = dbgmcu();
for register in [
Register::Apb3fz1,
Register::Apb1lfz1,
Register::Apb2fz1,
Register::Apb4fz1,
] {
let every_bit = defined(register);
assert_ne!(every_bit, 0, "{}", register.spec().name);
write(&mut soc, register, every_bit);
assert_eq!(
read(&mut soc, register),
every_bit,
"{}",
register.spec().name
);
for field in register.spec().fields {
assert!(field.is_set(read(&mut soc, register)), "{}", field.name);
}
write(&mut soc, register, 0);
assert_eq!(read(&mut soc, register), 0);
}
}
/// ChibiOS's `STM32_DBGMCU_TIM12_STOP()` is `DBGMCU->APB1LFZ1 |= DBGMCU_APB1LFZ1_DBG_TIM12`, which the
/// system timer's driver runs when the board makes TIM12 the timer, as the `MatekH743` does.
#[test]
fn chibios_freezes_its_system_timer_in_debug() {
let mut soc = dbgmcu();
let current = read(&mut soc, Register::Apb1lfz1);
write(
&mut soc,
Register::Apb1lfz1,
current | apb1lfz1::DBG_TIM12.mask(),
);
let frozen = read(&mut soc, Register::Apb1lfz1);
assert!(apb1lfz1::DBG_TIM12.is_set(frozen), "DBG_TIM12, 60.5.8");
assert_eq!(
frozen,
apb1lfz1::DBG_TIM12.mask(),
"and no other timer is frozen"
);
}
}