Add validator and flattened map generator

This commit is contained in:
Wataru Otsubo 2025-02-21 15:47:34 +00:00
parent 605228e2b5
commit f8c653a1cc
8 changed files with 426 additions and 6 deletions

View file

@ -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.

22
Cargo.lock generated
View file

@ -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",

View file

@ -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"]

View file

@ -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<validator::ValidationError>),
#[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<dyn Write> = 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(())
}

View file

@ -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;

View file

@ -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<path::PathBuf>,
},
}
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(())
}

View file

@ -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)
}
}

251
src/validator.rs Normal file
View file

@ -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<Option<(Vec<&'a str>, &'a Register, Option<u32>)>>;
impl Module {
/// Validate the address assignment, generating a flatten register map.
pub fn validate(&self) -> (FlattenedRegisterMap, Vec<ValidationError>) {
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<ValidationError>,
base: (u32, Vec<&'a str>),
) -> (FlattenedRegisterMap<'a>, Vec<ValidationError>);
}
impl Validate for Module {
fn fill<'a>(
&'a self,
mut mapping: FlattenedRegisterMap<'a>,
mut errors: Vec<ValidationError>,
base: (u32, Vec<&'a str>),
) -> (FlattenedRegisterMap<'a>, Vec<ValidationError>) {
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<ValidationError>,
base: (u32, Vec<&'a str>),
) -> (FlattenedRegisterMap<'a>, Vec<ValidationError>) {
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<ValidationError>,
base: (u32, Vec<&'a str>),
) -> (FlattenedRegisterMap<'a>, Vec<ValidationError>) {
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<ValidationError>,
base: (u32, Vec<&'a str>),
) -> (FlattenedRegisterMap<'a>, Vec<ValidationError>) {
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::<usize>(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);
}
}