Skip to content

Commit

Permalink
implement Debug for readable blocks and registers
Browse files Browse the repository at this point in the history
  • Loading branch information
liebman committed Jun 1, 2023
1 parent e6a6d15 commit be920c0
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Fix dangling implicit derives
- Fix escaping <> and & characters in doc attributes
- Add `interrupt_link_section` config parameter for controlling the `#[link_section = "..."]` attribute of `__INTERRUPTS`
- Add option to implement Debug for readable registers (#716)

## [v0.28.0] - 2022-12-25

Expand Down
13 changes: 13 additions & 0 deletions src/generate/peripheral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,18 @@ fn register_or_cluster_block(
}
}

let mut derive_debug = TokenStream::new();
if config.impl_debug {
if let Some(feature_name) = &config.impl_debug_feature {
derive_debug.extend(quote! {
#[cfg_attr(feature = #feature_name, derive(Debug))]
});
} else {
derive_debug.extend(quote! {
#[derive(Debug)]
});
}
}
let name = if let Some(name) = name {
name.to_constant_case_ident(span)
} else {
Expand All @@ -663,6 +675,7 @@ fn register_or_cluster_block(
Ok(quote! {
///Register block
#[repr(C)]
#derive_debug
pub struct #name {
#rbfs
}
Expand Down
149 changes: 146 additions & 3 deletions src/generate/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,14 @@ pub fn render_register_mod(
}

let mut r_impl_items = TokenStream::new();
let mut r_debug_impl = TokenStream::new();
let mut w_impl_items = TokenStream::new();
let mut zero_to_modify_fields_bitmap = 0;
let mut one_to_modify_fields_bitmap = 0;

let open = Punct::new('{', Spacing::Alone);
let close = Punct::new('}', Spacing::Alone);

if let Some(cur_fields) = register.fields.as_ref() {
// filter out all reserved fields, as we should not generate code for
// them
Expand All @@ -243,6 +247,15 @@ pub fn render_register_mod(
.collect();

if !cur_fields.is_empty() {
if config.impl_debug {
r_debug_impl.extend(render_register_mod_debug(
register,
&access,
&cur_fields,
config,
))
}

(
r_impl_items,
w_impl_items,
Expand All @@ -261,16 +274,57 @@ pub fn render_register_mod(
config,
)?;
}
} else if !access.can_read() || register.read_action.is_some() {
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "(not readable)")
}
}
});
} else {
// no register fields are defined so implement Debug to get entire register value
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for R {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.bits())
}
}
});
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.read().fmt(f)
}
}
});
}

let open = Punct::new('{', Spacing::Alone);
let close = Punct::new('}', Spacing::Alone);

if can_read && !r_impl_items.is_empty() {
mod_items.extend(quote! {
impl R #open #r_impl_items #close
});
}
if !r_debug_impl.is_empty() {
mod_items.extend(quote! {
#r_debug_impl
});
}

if can_write {
mod_items.extend(quote! {
Expand Down Expand Up @@ -386,6 +440,95 @@ pub fn render_register_mod(
Ok(mod_items)
}

fn render_register_mod_debug(
register: &Register,
access: &Access,
cur_fields: &[&Field],
config: &Config,
) -> Result<TokenStream> {
let name = util::name_of(register, config.ignore_groups);
let span = Span::call_site();
let regspec_ident = format!("{name}_SPEC").to_constant_case_ident(span);
let open = Punct::new('{', Spacing::Alone);
let close = Punct::new('}', Spacing::Alone);
let mut r_debug_impl = TokenStream::new();

// implement Debug for register readable fields that have no read side effects
if access.can_read() && register.read_action.is_none() {
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for R #open
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result #open
f.debug_struct(#name)
});
for &f in cur_fields.iter() {
let field_access = match &f.access {
Some(a) => a,
None => access,
};
let bit_or_bits = if f.bit_width() > 1 { "bits" } else { "bit" };
let bit_or_bits = syn::Ident::new(bit_or_bits, span);
log::debug!("register={} field={}", name, f.name);
if field_access.can_read() && f.read_action.is_none() {
if let Field::Array(_, de) = &f {
for (_, suffix) in de.indexes().enumerate() {
let f_name_n = util::replace_suffix(&f.name, &suffix)
.to_snake_case_ident(Span::call_site());
let f_name_n_s = format!("{f_name_n}");
r_debug_impl.extend(quote! {
.field(#f_name_n_s, &format_args!("{}", self.#f_name_n().#bit_or_bits()))
});
}
} else {
let f_name = util::replace_suffix(&f.name, "");
let f_name = f_name.to_snake_case_ident(span);
let f_name_s = format!("{f_name}");
r_debug_impl.extend(quote! {
.field(#f_name_s, &format_args!("{}", self.#f_name().#bit_or_bits()))
});
}
}
}
r_debug_impl.extend(quote! {
.finish()
#close
#close
});
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.read().fmt(f)
}
}
});
} else if !access.can_read() || register.read_action.is_some() {
if let Some(feature) = &config.impl_debug_feature {
r_debug_impl.extend(quote! {
#[cfg(feature=#feature)]
});
}
r_debug_impl.extend(quote! {
impl core::fmt::Debug for crate::generic::Reg<#regspec_ident> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "(not readable)")
}
}
});
} else {
warn!("not implementing debug for {name}");
}
Ok(r_debug_impl)
}

#[allow(clippy::too_many_arguments)]
pub fn fields(
mut fields: Vec<&Field>,
Expand Down
16 changes: 16 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,13 +495,29 @@
//! `portable-atomic` v0.3.16 must be added to the dependencies, with default features off to
//! disable the `fallback` feature.
//!
//! ## the `--impl_debug` flag
//!
//! The `--impl_debug` option will cause svd2rust to generate `core::fmt::Debug` implementations for
//! all registers and blocks. If a register is readable and has fields defined then each field value
//! will be printed - if no fields are defined then the value of the register will be printed. Any
//! register that has read actions will not be read and printed as `(not read/has read action!)`.
//! Registers that are not readable will have `(write only register)` printed as the value.
//!
//! The `--impl_debug_feature` flad can also be specified to include debug implementations conditionally
//! behind the supplied feature name.
//!
//! Usage examples:
//!
//! ```ignore
//! // These can be called from different contexts even though they are modifying the same register
//! P1.p1out.set_bits(|w| unsafe { w.bits(1 << 1) });
//! P1.p1out.clear_bits(|w| unsafe { w.bits(!(1 << 2)) });
//! P1.p1out.toggle_bits(|w| unsafe { w.bits(1 << 4) });
//! // if impl_debug was used one can print Registers or RegisterBlocks
//! // print single register
//! println!("RTC_CNT {:#?}", unsafe { &*esp32s3::RTC_CNTL::ptr() }.options0);
//! // print all registers for peripheral
//! println!("RTC_CNT {:#?}", unsafe { &*esp32s3::RTC_CNTL::ptr() });
//! ```
#![recursion_limit = "128"]

Expand Down
13 changes: 13 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ fn run() -> Result<()> {
.action(ArgAction::SetTrue)
.help("Use array increment for cluster size"),
)
.arg(
Arg::new("impl_debug")
.long("impl_debug")
.action(ArgAction::SetTrue)
.help("implement Debug for readable blocks and registers"),
)
.arg(
Arg::new("impl_debug_feature")
.long("impl_debug_feature")
.help("add feature gating for block and register debug implementation")
.action(ArgAction::Set)
.value_name("FEATURE"),
)
.arg(
Arg::new("make_mod")
.long("make_mod")
Expand Down
6 changes: 6 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub struct Config {
pub feature_peripheral: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub max_cluster_size: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub impl_debug: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub impl_debug_feature: Option<String>,
#[cfg_attr(feature = "serde", serde(default = "current_dir"))]
pub output_dir: PathBuf,
#[cfg_attr(feature = "serde", serde(default))]
Expand Down Expand Up @@ -118,6 +122,8 @@ impl Default for Config {
feature_group: false,
feature_peripheral: false,
max_cluster_size: false,
impl_debug: false,
impl_debug_feature: None,
output_dir: current_dir(),
input: None,
source_type: SourceType::default(),
Expand Down

0 comments on commit be920c0

Please sign in to comment.