diff --git a/CHANGELOG.md b/CHANGELOG.md index 46d7d71..8f3dbfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- Added register map validator and flattened map generator. + ### Changed - Changed block, register type definitions to support multiple backends. See [the merge request at mpsoc software](https://gitlab.cern.ch/wotsubo/mpsoc-software/-/merge_requests/9) for more information. diff --git a/Cargo.lock b/Cargo.lock index 550b77e..fa8e6aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,6 +260,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.10" @@ -358,6 +379,7 @@ dependencies = [ "anyhow", "chrono", "clap", + "csv", "env_logger", "heck", "hex", diff --git a/Cargo.toml b/Cargo.toml index 2a17f3a..eac1783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ path = "src/lib.rs" anyhow = { version = "1.0", optional = true } chrono = "0.4" clap = { version = "4.5", features = ["derive"] } +csv = { version = "1.3.1", optional = true } env_logger = { version = "0.11", optional = true } heck = "0.5" hex = "0.4" @@ -38,3 +39,4 @@ vergen-gitcl = { version = "1.0", features = ["build", "cargo", "rustc", "si"] } [features] bin = ["anyhow", "env_logger"] +flatmap = ["csv"] diff --git a/src/integrated.rs b/src/integrated.rs index a342730..121e506 100644 --- a/src/integrated.rs +++ b/src/integrated.rs @@ -1,11 +1,12 @@ use std::path; +use itertools::Itertools; use thiserror::Error; use crate::{ converter, generator, io::{get_xml_gitinfo, XmlGitInfoError}, - types, + types, validator, }; #[derive(Debug, Error)] @@ -18,8 +19,13 @@ pub enum Error { XmlParseError(#[from] roxmltree::Error), #[error("dom conversion error: {0}")] DomConversionError(#[from] converter::DomConversionError), + #[error("invalid register map:\n{}", ._0.iter().map(|e| e.to_string()).join("\n"))] + RegmapValidationError(Vec), #[error("code generation error: {0}")] CodeGenError(#[from] generator::CodeGenError), + #[cfg(feature = "flatmap")] + #[error("csv write error: {0}")] + CsvWriteError(#[from] csv::Error), } /// Generate register interface code from xml in `xml` and write them under directory `out`. @@ -42,6 +48,31 @@ pub fn generate(xml: &path::Path, out: &path::Path) -> Result<(), Error> { let register_map = types::Module::from_xml_dom(doc.root_element(), xml_git_info)?; log::debug!("converted {register_map:?}"); + let (maps, errors) = register_map.validate(); + if !errors.is_empty() { + return Err(Error::RegmapValidationError(errors)); + } + // TODO: this is a temporarily implementation + println!("address,path,name,multiple_id,modf,description"); + for (addr, reg) in maps.iter().enumerate() { + match reg { + Some(reg) => { + let path = reg.0.join("/"); + let multiple_id = reg.2.map_or("".to_string(), |x| x.to_string()); + println!( + "0x{:04x},{},{},{},{},{},", + addr, + path, + reg.1.name, + multiple_id, + reg.1.modf, + reg.1.desc.as_ref().map_or_else(|| "", |s| s), + ) + } + None => println!("0x{:04x},,,,", addr), + } + } + let files = register_map.generate_code()?; if log::log_enabled!(log::Level::Debug) { for (path, code) in &files { @@ -56,3 +87,76 @@ pub fn generate(xml: &path::Path, out: &path::Path) -> Result<(), Error> { Ok(()) } + +#[cfg(feature = "flatmap")] +pub fn generate_flatmap(xml: &path::Path, out: Option<&path::Path>) -> Result<(), Error> { + use std::{ + fs::File, + io::{self, BufWriter, Write}, + }; + + log::debug!("generate called: xml:{:?}, out: {:?}", xml, out); + + let xml_git_info = get_xml_gitinfo(xml)?; + log::debug!("xml git info {xml_git_info:?}"); + + let xmlfile = std::fs::read_to_string(xml)?; + let doc = roxmltree::Document::parse_with_options( + &xmlfile, + roxmltree::ParsingOptions { + allow_dtd: true, + nodes_limit: u32::MAX, + }, + )?; + log::debug!("xml parsed {doc:?}"); + + let register_map = types::Module::from_xml_dom(doc.root_element(), xml_git_info)?; + log::debug!("converted {register_map:?}"); + + let (maps, errors) = register_map.validate(); + if !errors.is_empty() { + return Err(Error::RegmapValidationError(errors)); + } + let f: Box = match out { + Some(f) => { + let file = File::options().write(true).truncate(true).open(f)?; + Box::new(BufWriter::new(file)) + } + None => Box::new(io::stdout()), + }; + let mut wtr = csv::Writer::from_writer(f); + + wtr.write_record([ + "address", + "path", + "name", + "multiple_id", + "modf", + "description", + ])?; + for (addr, reg) in maps.iter().enumerate() { + match reg { + Some(reg) => { + let path = reg.0.join("/"); + let multiple_id = reg.2.map_or("".to_string(), |x| x.to_string()); + wtr.write_record([ + format!("0x{:04x}", addr), + path, + reg.1.name.clone(), + multiple_id, + reg.1.modf.to_string(), + reg.1.desc.as_ref().unwrap_or(&"".to_string()).to_string(), + ])?; + } + None => wtr.write_record([ + format!("0x{:04x}", addr), + "".to_string(), + "".to_string(), + "".to_string(), + "".to_string(), + "".to_string(), + ])?, + } + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index e76a395..483816a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod meta; mod parser; mod type_traits; pub mod types; +pub mod validator; pub use integrated::generate; pub use io::write_to_files; diff --git a/src/main.rs b/src/main.rs index 3dc85ac..e37c513 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::path; use anyhow::Result; -use clap::Parser; +use clap::{Parser, Subcommand}; /// Generate register interface from register map xml. #[derive(Parser, Debug)] @@ -9,8 +9,21 @@ use clap::Parser; struct Args { /// Input XML register map. xml: path::PathBuf, - /// Output directory. - out: path::PathBuf, + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + Generate { + /// Output directory. + out: path::PathBuf, + }, + #[cfg(feature = "flatmap")] + Flatmap { + /// Flattened csv out path. + flatmap: Option, + }, } fn main() -> Result<()> { @@ -19,7 +32,17 @@ fn main() -> Result<()> { let args = Args::parse(); log::debug!("args: {:?}", args); - endcap_sl_software_ri_generator::generate(&args.xml, &args.out)?; + match args.command { + Commands::Generate { out } => endcap_sl_software_ri_generator::generate(&args.xml, &out)?, + #[cfg(feature = "flatmap")] + Commands::Flatmap { flatmap } => { + let outpath: Option<&path::Path> = match flatmap { + Some(ref p) => Some(p.as_path()), + None => None, + }; + endcap_sl_software_ri_generator::integrated::generate_flatmap(&args.xml, outpath)? + } + } Ok(()) } diff --git a/src/type_traits.rs b/src/type_traits.rs index d68da02..09f638d 100644 --- a/src/type_traits.rs +++ b/src/type_traits.rs @@ -1,6 +1,8 @@ //! Util traits to get info from types in [`crate::types`]. -use crate::types::{Block, ModuleBlockElements, Register}; +use std::fmt::Display; + +use crate::types::{Block, ModuleBlockElements, Register, RwSpecifier}; pub(crate) trait GetName { fn get_name(&self) -> String; @@ -28,3 +30,14 @@ impl GetName for Register { self.name.clone() } } + +impl Display for RwSpecifier { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + RwSpecifier::R => "r", + RwSpecifier::W => "w", + RwSpecifier::RW => "rw", + }; + write!(f, "{}", s) + } +} diff --git a/src/validator.rs b/src/validator.rs new file mode 100644 index 0000000..479bcb5 --- /dev/null +++ b/src/validator.rs @@ -0,0 +1,251 @@ +//! Validate the address assignment and generate a flattened register map. + +use thiserror::Error; + +use crate::types::{Block, Module, ModuleBlockElements, Register}; + +type FlattenedRegisterMap<'a> = Vec, &'a Register, Option)>>; + +impl Module { + /// Validate the address assignment, generating a flatten register map. + pub fn validate(&self) -> (FlattenedRegisterMap, Vec) { + let mut mapping = Vec::new(); + mapping.resize(self.size.try_into().unwrap(), None); + let errors = Vec::new(); + self.fill(mapping, errors, (0x0, Vec::new())) + } +} + +#[derive(Debug, Error)] +pub enum ValidationError { + #[error("address duplicated at 0x{address:08x}: \"{existing_name}\", \"{new_name}\"")] + AddressDuplicated { + address: u32, + existing_name: String, + new_name: String, + }, + #[error("unsupported structure: {msg}")] + UnsupportedStructure { msg: &'static str }, +} + +trait Validate { + fn fill<'a>( + &'a self, + mapping: FlattenedRegisterMap<'a>, + errors: Vec, + base: (u32, Vec<&'a str>), + ) -> (FlattenedRegisterMap<'a>, Vec); +} + +impl Validate for Module { + fn fill<'a>( + &'a self, + mut mapping: FlattenedRegisterMap<'a>, + mut errors: Vec, + base: (u32, Vec<&'a str>), + ) -> (FlattenedRegisterMap<'a>, Vec) { + for child in &self.elements_other { + (mapping, errors) = child.fill(mapping, errors, base.clone()); + } + (mapping, errors) + } +} + +impl Validate for ModuleBlockElements { + fn fill<'a>( + &'a self, + mapping: FlattenedRegisterMap<'a>, + errors: Vec, + base: (u32, Vec<&'a str>), + ) -> (FlattenedRegisterMap<'a>, Vec) { + match self { + ModuleBlockElements::Block(block) => block.fill(mapping, errors, base), + ModuleBlockElements::Register(register) => register.fill(mapping, errors, base), + ModuleBlockElements::Memory(_memory) => todo!(), + ModuleBlockElements::Fifo(_fifo) => todo!(), + } + } +} + +impl Validate for Block { + fn fill<'a>( + &'a self, + mut mapping: FlattenedRegisterMap<'a>, + mut errors: Vec, + base: (u32, Vec<&'a str>), + ) -> (FlattenedRegisterMap<'a>, Vec) { + if self.multiple.is_some() { + errors.push(ValidationError::UnsupportedStructure { + msg: "multiple in block", + }); + } + + let addr = base.0 + self.addr; + let mut path = base.1; + path.push(&self.name); + for child in &self.elements { + (mapping, errors) = child.fill(mapping, errors, (addr, path.clone())); + } + (mapping, errors) + } +} + +impl Validate for Register { + fn fill<'a>( + &'a self, + mut mapping: FlattenedRegisterMap<'a>, + mut errors: Vec, + base: (u32, Vec<&'a str>), + ) -> (FlattenedRegisterMap<'a>, Vec) { + let addr = base.0 + self.addr; + let path = base.1; + let (len, offset) = match &self.multiple { + Some(multiple) => (multiple.multiple, multiple.offset), + None => (1, 1), + }; + for id in 0..len { + let addr = addr + id * offset; + let regmap: &mut Option<_> = mapping + .get_mut::(addr.try_into().unwrap()) + .expect("index of mapping out of range"); + if let Some(old) = regmap { + let existing_name = { + let mut path = old.0.clone(); + path.push(&old.1.name); + path.join("/") + }; + let new_name = { + let mut path = path.clone(); + path.push(&self.name); + path.join("/") + }; + errors.push(ValidationError::AddressDuplicated { + address: addr, + existing_name, + new_name, + }); + } else { + let multiple_id = if len == 1 { None } else { Some(id) }; + *regmap = Some((path.clone(), self, multiple_id)) + } + } + + (mapping, errors) + } +} + +#[cfg(test)] +mod test { + use itertools::Itertools; + + use crate::types::{ + Module, ModuleBlockElements, MultipleParams, Register, RwSpecifier, XmlGitInfo, + }; + + #[test] + fn duplicate() { + let reg1 = Register { + name: "test1".to_string(), + addr: 0, + r#type: crate::types::DataType::D32, + mask: None, + modf: RwSpecifier::R, + multiple: None, + default: None, + desc: None, + elements: vec![], + }; + let reg2 = Register { + name: "test1".to_string(), + addr: 0, + r#type: crate::types::DataType::D32, + mask: None, + modf: RwSpecifier::R, + multiple: None, + default: None, + desc: None, + elements: vec![], + }; + let module = Module { + name: "module".to_string(), + addr: 0, + size: 0x10, + amod: None, + r#type: None, + desc: None, + elements_bitstring: vec![], + elements_other: vec![ + ModuleBlockElements::Register(reg1), + ModuleBlockElements::Register(reg2), + ], + xmlhash: [0; 32], + git_info: XmlGitInfo { + describe: "".to_string(), + commit_timestamp: "".to_string(), + sha: "".to_string(), + }, + }; + + let (maps, errors) = module.validate(); + eprintln!("{:#?}", maps); + eprintln!( + "{:#?}", + maps.iter().filter(|e| e.is_some()).collect_vec().len() + ); + assert_eq!(maps.len(), 0x10); + assert!(!errors.is_empty()); + assert_eq!(maps.iter().filter(|e| e.is_some()).collect_vec().len(), 0x1); + } + + #[test] + fn duplicate_with_multiple() { + let reg1 = Register { + name: "test1".to_string(), + addr: 0, + r#type: crate::types::DataType::D32, + mask: None, + modf: RwSpecifier::R, + multiple: Some(MultipleParams { + multiple: 16, + offset: 1, + }), + default: None, + desc: None, + elements: vec![], + }; + let reg2 = Register { + name: "test1".to_string(), + addr: 15, + r#type: crate::types::DataType::D32, + mask: None, + modf: RwSpecifier::R, + multiple: None, + default: None, + desc: None, + elements: vec![], + }; + let module = Module { + name: "module".to_string(), + addr: 0, + size: 0x1000, + amod: None, + r#type: None, + desc: None, + elements_bitstring: vec![], + elements_other: vec![ + ModuleBlockElements::Register(reg1), + ModuleBlockElements::Register(reg2), + ], + xmlhash: [0; 32], + git_info: XmlGitInfo { + describe: "".to_string(), + commit_timestamp: "".to_string(), + sha: "".to_string(), + }, + }; + + let (maps, errors) = module.validate(); + assert_eq!(maps.len(), 0x1000); + assert_eq!(errors.len(), 1); + } +}