Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement Debug for readable fields of registers #716

Merged
merged 1 commit into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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