mirror of
https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator.git
synced 2025-08-05 10:19:30 +09:00
509 lines
18 KiB
Rust
509 lines
18 KiB
Rust
//! Generate register interface rust code from types in [`crate::types`].
|
|
//!
|
|
//! root: [`Module::generate_code`]
|
|
//!
|
|
//! # For developers
|
|
//! Pass `--document-private-items` to see non-public items.
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
path::{self, PathBuf},
|
|
};
|
|
|
|
use crate::{
|
|
meta::{
|
|
GENERATOR_BUILD_TIMESTAMP, GENERATOR_GIT_COMMIT_TIMESTAMP, GENERATOR_GIT_DESCRIBE,
|
|
GENERATOR_GIT_SHA,
|
|
},
|
|
type_traits::GetName,
|
|
types::{Block, Module, ModuleBlockElements, Register},
|
|
};
|
|
use chrono::Local;
|
|
use heck::{ToSnakeCase, ToUpperCamelCase};
|
|
use itertools::Itertools;
|
|
use quote::quote;
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum CodeGenError {
|
|
#[error("tokenization(syn) error: {source}: {code}")]
|
|
SynError {
|
|
#[source]
|
|
source: syn::Error,
|
|
code: String,
|
|
},
|
|
#[error("failed to create file (name duplicated): {0}")]
|
|
FilePathDuplicatedError(String),
|
|
#[error("parent is required for {module}")]
|
|
ParentMissing { module: &'static str },
|
|
#[error("Unsupported structure: {}", name)]
|
|
UnsupportedStructure { name: &'static str },
|
|
}
|
|
|
|
mod util {
|
|
use crate::types::DataType;
|
|
|
|
use super::CodeGenError;
|
|
|
|
pub(super) fn parse_to_ident(s: &str) -> Result<proc_macro2::Ident, CodeGenError> {
|
|
syn::parse_str(s).map_err(|e| CodeGenError::SynError {
|
|
source: e,
|
|
code: s.to_string(),
|
|
})
|
|
}
|
|
|
|
pub(super) fn parse_to_literal(s: &str) -> Result<proc_macro2::Literal, CodeGenError> {
|
|
syn::parse_str(s).map_err(|e| CodeGenError::SynError {
|
|
source: e,
|
|
code: s.to_string(),
|
|
})
|
|
}
|
|
|
|
// currently only U32 is used, so `dead_code` for Debug, PartialEq
|
|
#[allow(dead_code)]
|
|
#[derive(Debug, PartialEq)]
|
|
pub(crate) enum RustUxTypes {
|
|
Bool,
|
|
U8,
|
|
U16,
|
|
U32,
|
|
}
|
|
|
|
impl RustUxTypes {
|
|
/// Derive appropriate rust types for `mask`ed value.
|
|
pub(super) fn from_mask(mask: u32) -> RustUxTypes {
|
|
match 32 - mask.leading_zeros() - mask.trailing_zeros() {
|
|
0 => panic!("mask cannot be 0"),
|
|
1 => RustUxTypes::Bool,
|
|
x if 1 < x && x <= 8 => RustUxTypes::U8,
|
|
x if 8 < x && x <= 16 => RustUxTypes::U16,
|
|
x if 16 < x && x <= 32 => RustUxTypes::U32,
|
|
_ => panic!("supposed not to be reachable"),
|
|
}
|
|
}
|
|
|
|
/// Derive appropriate rust type for right aligned `mask`ed value.
|
|
pub(super) fn from_exact_mask(mask: u32) -> Option<RustUxTypes> {
|
|
if mask.trailing_zeros() != 0 {
|
|
return None;
|
|
}
|
|
match 32 - mask.leading_zeros() {
|
|
0 => panic!("mask cannot be 0"),
|
|
1 => Some(RustUxTypes::Bool),
|
|
8 => Some(RustUxTypes::U8),
|
|
16 => Some(RustUxTypes::U16),
|
|
32 => Some(RustUxTypes::U32),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub(super) fn to_rust_type_token(&self) -> proc_macro2::Ident {
|
|
match self {
|
|
RustUxTypes::Bool => parse_to_ident("bool").unwrap(),
|
|
RustUxTypes::U8 => parse_to_ident("u8").unwrap(),
|
|
RustUxTypes::U16 => parse_to_ident("u16").unwrap(),
|
|
RustUxTypes::U32 => parse_to_ident("u32").unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&DataType> for RustUxTypes {
|
|
fn from(value: &DataType) -> Self {
|
|
match value {
|
|
DataType::D32 => Self::U32,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::RustUxTypes;
|
|
|
|
#[test]
|
|
fn rustuxtypes_from_mask() {
|
|
assert_eq!(RustUxTypes::from_mask(0x1), RustUxTypes::Bool);
|
|
assert_eq!(RustUxTypes::from_mask(0x20), RustUxTypes::Bool);
|
|
assert_eq!(RustUxTypes::from_mask(0x4000), RustUxTypes::Bool);
|
|
assert_eq!(RustUxTypes::from_mask(0x80_0000), RustUxTypes::Bool);
|
|
assert_eq!(RustUxTypes::from_mask(0x100_0000), RustUxTypes::Bool);
|
|
|
|
assert_eq!(RustUxTypes::from_mask(0x0300_0000), RustUxTypes::U8);
|
|
assert_eq!(RustUxTypes::from_mask(0x0000_01e0), RustUxTypes::U8);
|
|
|
|
assert_eq!(RustUxTypes::from_mask(0x0000_01f0), RustUxTypes::U8);
|
|
assert_eq!(RustUxTypes::from_mask(0x000f_f000), RustUxTypes::U8);
|
|
|
|
assert_eq!(RustUxTypes::from_mask(0x0fff_0000), RustUxTypes::U16);
|
|
assert_eq!(RustUxTypes::from_mask(0x0f0f_0000), RustUxTypes::U16);
|
|
assert_eq!(RustUxTypes::from_mask(0x010f_8000), RustUxTypes::U16);
|
|
|
|
assert_eq!(RustUxTypes::from_mask(0xffff_f000), RustUxTypes::U32);
|
|
assert_eq!(RustUxTypes::from_mask(0x1fff_ff00), RustUxTypes::U32);
|
|
}
|
|
|
|
#[test]
|
|
fn rustuxtypes_to_token() {
|
|
assert_eq!(
|
|
RustUxTypes::U8.to_rust_type_token(),
|
|
proc_macro2::Ident::new("u8", proc_macro2::Span::call_site())
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Module {
|
|
pub fn generate_code(self) -> Result<HashMap<path::PathBuf, syn::File>, CodeGenError> {
|
|
let build_metadata = format!(
|
|
"
|
|
This code is auto generated using endcap_sl_software_ri_generator.
|
|
|
|
# Build metadata
|
|
|
|
- timestamp: {}
|
|
|
|
## CSR XML
|
|
|
|
- sha256: {}
|
|
- git describe: {}
|
|
- git commit timestamp: {}
|
|
- git SHA: {}
|
|
|
|
## Generator
|
|
|
|
- build timestamp: {}
|
|
- git describe: {}
|
|
- git commit timestamp: {}
|
|
- git SHA: {}
|
|
",
|
|
Local::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, false),
|
|
hex::encode(self.xmlhash),
|
|
self.git_info.describe,
|
|
self.git_info.commit_timestamp,
|
|
self.git_info.sha,
|
|
GENERATOR_BUILD_TIMESTAMP,
|
|
GENERATOR_GIT_DESCRIBE,
|
|
GENERATOR_GIT_COMMIT_TIMESTAMP,
|
|
GENERATOR_GIT_SHA,
|
|
);
|
|
let desc = match self.desc {
|
|
Some(ref desc) => quote! { #![doc = #desc] },
|
|
None => quote! {},
|
|
};
|
|
let files = self.generate_register_interface(None, None, HashMap::new())?;
|
|
files
|
|
.into_iter()
|
|
.map(|(path, tokens)| -> Result<(PathBuf, syn::File), _> {
|
|
let tokens = if path.file_name().is_some_and(|file| file == "mod.rs") {
|
|
quote! {
|
|
#![doc = #build_metadata]
|
|
#desc
|
|
|
|
#tokens
|
|
}
|
|
} else {
|
|
tokens
|
|
};
|
|
let file: syn::File =
|
|
syn::parse2(tokens.clone()).map_err(|e| CodeGenError::SynError {
|
|
source: e,
|
|
code: tokens.to_string(),
|
|
})?;
|
|
Ok((path, file))
|
|
})
|
|
.process_results(|kv| HashMap::from_iter(kv))
|
|
}
|
|
}
|
|
|
|
trait CodeGen {
|
|
/// `parent_name` in UpperCamelCase.
|
|
fn generate_register_interface(
|
|
self,
|
|
parent_name: Option<proc_macro2::Ident>,
|
|
parent_path: Option<path::PathBuf>,
|
|
files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
|
|
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError>;
|
|
}
|
|
|
|
impl CodeGen for Module {
|
|
fn generate_register_interface(
|
|
self,
|
|
_: Option<proc_macro2::Ident>,
|
|
_: Option<path::PathBuf>,
|
|
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
|
|
) -> std::result::Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError> {
|
|
if !self.elements_bitstring.is_empty() {
|
|
todo!("bitstring generation is not yet implemented")
|
|
}
|
|
|
|
let child_mods = self
|
|
.elements_other
|
|
.iter()
|
|
.map(|e| {
|
|
util::parse_to_ident(&e.get_name().to_snake_case()).map(|child_name| {
|
|
quote! {
|
|
pub mod #child_name;
|
|
}
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
let out = quote! {
|
|
#(#child_mods)*
|
|
|
|
pub use super::RegisterInterface;
|
|
};
|
|
files.insert(PathBuf::from("./mod.rs"), out);
|
|
|
|
let ident_register_interface = util::parse_to_ident("RegisterInterface").unwrap();
|
|
let register_interface_mod = PathBuf::from("./");
|
|
let files = self
|
|
.elements_other
|
|
.into_iter()
|
|
.try_fold(files, |files, e| {
|
|
e.generate_register_interface(
|
|
Some(ident_register_interface.clone()),
|
|
Some(register_interface_mod.clone()),
|
|
files,
|
|
)
|
|
})?;
|
|
|
|
Ok(files)
|
|
}
|
|
}
|
|
|
|
impl CodeGen for ModuleBlockElements {
|
|
fn generate_register_interface(
|
|
self,
|
|
parent_name: Option<proc_macro2::Ident>,
|
|
parent_path: Option<path::PathBuf>,
|
|
files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
|
|
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError> {
|
|
match self {
|
|
ModuleBlockElements::Block(block) => {
|
|
block.generate_register_interface(parent_name, parent_path, files)
|
|
}
|
|
ModuleBlockElements::Register(register) => {
|
|
register.generate_register_interface(parent_name, parent_path, files)
|
|
}
|
|
ModuleBlockElements::Memory(_memory) => todo!(),
|
|
ModuleBlockElements::Fifo(_fifo) => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CodeGen for Block {
|
|
fn generate_register_interface(
|
|
self,
|
|
parent_name: Option<proc_macro2::Ident>,
|
|
parent_path: Option<path::PathBuf>,
|
|
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
|
|
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError> {
|
|
if self.multiple.is_some() {
|
|
// Plan: expand automatically, or same as register?
|
|
return Err(CodeGenError::UnsupportedStructure {
|
|
name: "multiple in block",
|
|
});
|
|
}
|
|
let parent_name = parent_name.ok_or(CodeGenError::ParentMissing { module: "Block" })?;
|
|
let parent_path = parent_path.ok_or(CodeGenError::ParentMissing { module: "Block" })?;
|
|
|
|
let snake_case_name = util::parse_to_ident(&self.name.to_snake_case())?;
|
|
let upper_camel_name = util::parse_to_ident(&self.name.to_upper_camel_case())?;
|
|
let addr = util::parse_to_literal(&format!("0x{:x}", self.addr))?;
|
|
let desc = self.desc.unwrap_or("".to_string());
|
|
|
|
let accessors_methods = self.elements.iter().map(|e| {
|
|
let child_name = e.get_name();
|
|
let snake_case_name = util::parse_to_ident(&child_name.to_snake_case())?;
|
|
match e {
|
|
ModuleBlockElements::Block(_) => {
|
|
let child_upper_camel_name = util::parse_to_ident(&child_name.to_upper_camel_case())?;
|
|
Ok(quote! {
|
|
pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name<M> {
|
|
#snake_case_name::#child_upper_camel_name::new(self.mem_ptr)
|
|
}
|
|
})
|
|
},
|
|
ModuleBlockElements::Register(register) => {
|
|
let child_upper_camel_name = util::parse_to_ident(&format!("Reg{}", child_name.to_upper_camel_case()))?;
|
|
match ®ister.multiple {
|
|
None => {
|
|
Ok(quote! {
|
|
pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name<M> {
|
|
#snake_case_name::#child_upper_camel_name::new(self.mem_ptr)
|
|
}
|
|
})
|
|
},
|
|
Some(multiple_param) => {
|
|
let num_multiple = multiple_param.multiple as usize;
|
|
let elements = (0..num_multiple).map(|i| {
|
|
let offset = (multiple_param.offset as usize) * i;
|
|
quote! {
|
|
#snake_case_name::#child_upper_camel_name::new(unsafe { self.mem_ptr.add(#offset) } )
|
|
}
|
|
});
|
|
Ok(quote! {
|
|
pub fn #snake_case_name(&self) -> [#snake_case_name::#child_upper_camel_name<M>; #num_multiple] {
|
|
[ #(#elements),* ]
|
|
}
|
|
})
|
|
},
|
|
}
|
|
},
|
|
ModuleBlockElements::Memory(_memory) => todo!(),
|
|
ModuleBlockElements::Fifo(_fifo) => todo!(),
|
|
}
|
|
}).collect::<Result<Vec<proc_macro2::TokenStream>, CodeGenError>>()?;
|
|
|
|
let parent_struct = if parent_name == util::parse_to_ident("RegisterInterface").unwrap() {
|
|
quote! {#parent_name<M>}
|
|
} else {
|
|
quote! {#parent_name<'a, M>}
|
|
};
|
|
|
|
let child_mods = self
|
|
.elements
|
|
.iter()
|
|
.map(|e| {
|
|
util::parse_to_ident(&e.get_name().to_snake_case()).map(|child_name| {
|
|
quote! {
|
|
pub mod #child_name;
|
|
}
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
let out = quote! {
|
|
#![doc = #desc]
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use mmapped_reg::mmapped::Mmapped;
|
|
|
|
use super::#parent_name;
|
|
|
|
#(#child_mods)*
|
|
|
|
const OFFSET: usize = #addr;
|
|
|
|
pub struct #upper_camel_name<'a, M: Mmapped<U = u32>> {
|
|
mem_ptr: *mut u32,
|
|
_marker: PhantomData<&'a mut #parent_struct>,
|
|
}
|
|
|
|
impl<M: Mmapped<U = u32>> #upper_camel_name<'_, M> {
|
|
pub(crate) fn new(parent_ptr: *mut u32) -> Self {
|
|
#upper_camel_name {
|
|
mem_ptr: unsafe { parent_ptr.add(OFFSET) },
|
|
_marker: PhantomData,
|
|
}
|
|
}
|
|
|
|
#(#accessors_methods)*
|
|
}
|
|
};
|
|
let (out_path, next_parent_path) = mod_file_path(parent_path, &snake_case_name);
|
|
if let Some(old_out) = files.insert(out_path.clone(), out.clone()) {
|
|
log::error!("path {}", out_path.display());
|
|
log::error!("old {}", old_out.to_string());
|
|
log::error!("new {}", out.to_string());
|
|
return Err(CodeGenError::FilePathDuplicatedError(
|
|
snake_case_name.to_string(),
|
|
));
|
|
};
|
|
|
|
let files = self.elements.into_iter().try_fold(files, |files, e| {
|
|
e.generate_register_interface(
|
|
Some(upper_camel_name.clone()),
|
|
Some(next_parent_path.clone()),
|
|
files,
|
|
)
|
|
})?;
|
|
|
|
Ok(files)
|
|
}
|
|
}
|
|
|
|
/// Get filepath to write the `mod_snake_case_name` and next parent_path.
|
|
fn mod_file_path(
|
|
parent_path: path::PathBuf,
|
|
mod_snake_case_name: &proc_macro2::Ident,
|
|
) -> (path::PathBuf, path::PathBuf) {
|
|
(
|
|
parent_path.join(format!("{mod_snake_case_name}.rs")),
|
|
parent_path.join(mod_snake_case_name.to_string()),
|
|
)
|
|
}
|
|
|
|
mod codegen_register;
|
|
mod codegen_registerspec_impl;
|
|
|
|
/// Internally calls functions in [`codegen_register`]
|
|
impl CodeGen for Register {
|
|
fn generate_register_interface(
|
|
self,
|
|
parent_name: Option<proc_macro2::Ident>,
|
|
parent_path: Option<path::PathBuf>,
|
|
mut files: HashMap<path::PathBuf, proc_macro2::TokenStream>,
|
|
) -> Result<HashMap<path::PathBuf, proc_macro2::TokenStream>, CodeGenError> {
|
|
let parent_name = parent_name.ok_or(CodeGenError::ParentMissing { module: "Block" })?;
|
|
let parent_path = parent_path.ok_or(CodeGenError::ParentMissing { module: "Block" })?;
|
|
|
|
let snake_case_name = util::parse_to_ident(&self.name.to_snake_case())?;
|
|
let upper_camel_name = util::parse_to_ident(&self.name.to_upper_camel_case())?;
|
|
let reg_name = util::parse_to_ident(&format!("Reg{upper_camel_name}"))?;
|
|
let addr = util::parse_to_literal(&format!("0x{:x}", self.addr))?;
|
|
|
|
let (code_t_def, type_t, type_ux): (
|
|
proc_macro2::TokenStream,
|
|
proc_macro2::Ident,
|
|
proc_macro2::Ident,
|
|
) = codegen_register::reg_type_def(&self, &upper_camel_name)?;
|
|
|
|
let code_reg_def: proc_macro2::TokenStream =
|
|
codegen_registerspec_impl::gen_registerspec_impl(
|
|
reg_name.clone(),
|
|
self.modf,
|
|
type_t,
|
|
type_ux,
|
|
);
|
|
|
|
let desc = self.desc.unwrap_or("".to_string());
|
|
|
|
let out = quote! {
|
|
#![doc = #desc]
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use mmapped_reg::mmapped::Mmapped;
|
|
|
|
const OFFSET: usize = #addr;
|
|
|
|
pub struct #reg_name<'a, M: Mmapped<U = u32>> {
|
|
mem_ptr: *mut u32,
|
|
_marker: PhantomData<&'a mut super::#parent_name<'a, M>>,
|
|
}
|
|
|
|
impl<M: Mmapped<U = u32>> #reg_name<'_, M> {
|
|
pub(crate) fn new(parent_ptr: *mut u32) -> Self {
|
|
#reg_name {
|
|
mem_ptr: unsafe { parent_ptr.add(OFFSET) },
|
|
_marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
#code_reg_def
|
|
|
|
#code_t_def
|
|
};
|
|
|
|
let (out_path, _next_parent_path) = mod_file_path(parent_path, &snake_case_name);
|
|
log::info!("{:?}", out_path);
|
|
if files.insert(out_path, out).is_some() {
|
|
return Err(CodeGenError::FilePathDuplicatedError(
|
|
snake_case_name.to_string(),
|
|
));
|
|
}
|
|
Ok(files)
|
|
}
|
|
}
|