Compare commits

...

29 commits
v0.1.0 ... main

Author SHA1 Message Date
b492d097e7 Improve errormsg 2025-04-14 07:27:15 +00:00
12332e8565 update(CHANGELOG): on !17 2025-04-14 16:21:45 +09:00
fafd262230 Add functionality to overwrite xml metadata with env var 2025-04-14 07:10:54 +00:00
9444431a61 new(nix): build with nixpkgs buildRustPackage 2025-04-14 06:52:12 +00:00
1bb239812e release v0.3.0 2025-04-11 22:01:01 +09:00
ccfd443e5d update(docs): add docs on validator 2025-03-31 17:53:44 +09:00
cba5b8594b Refactor error msg 2025-03-28 03:50:37 +00:00
b5d165a3f5 Refactor const def 2025-03-27 16:08:42 +00:00
e6e7ada193 run formatter (diff are maybe due to edition) 2025-03-27 19:19:05 +09:00
b9f6dc6c23 update rust edition 2025-03-27 19:07:18 +09:00
1104f826fc release 0.3.0 2025-03-27 18:52:52 +09:00
5c7aa91d56 fix(generator): avoid constant name duplication of mask and value 2025-03-27 18:22:30 +09:00
fa777f43fb Allow mask definition in register with fields, iff they match 2025-03-27 09:09:47 +00:00
205b08400b Allow definition of overlapped register with non-overlapped fields 2025-03-13 13:12:49 +00:00
8732afd297 update(bin): add doccomments to subcommands 2025-03-11 19:49:16 +09:00
d0d5299101 update(bin): enable flatmap in bin feature 2025-03-11 19:48:55 +09:00
76595d0c92 change(validator): add AddressOutofRange error 2025-02-22 23:52:45 +09:00
06a5429583 refactor(integrated): remove printlns in fn generate 2025-02-22 23:52:18 +09:00
9971774a01 fix: use super::RegisterInterface 2025-02-22 04:46:03 +09:00
f8c653a1cc Add validator and flattened map generator 2025-02-21 15:47:34 +00:00
605228e2b5 update(CHANGELOG): on multiple backends 2025-02-20 22:07:19 +09:00
dd119ccdb8 Multiple backends 2025-02-20 13:01:02 +00:00
testuser
e62d935829 fmt: 2025-02-20 15:18:18 +09:00
testuser
34e1528745 refactor(generator): change CodeGenError name 2025-02-20 15:13:56 +09:00
122f7a4b3e bump to 0.2.0 2025-02-12 22:14:00 +09:00
9555b743f9 Xml metadata embedding 2025-02-12 13:10:48 +00:00
f714b42249 update(README): add badges 2025-02-10 22:32:01 +09:00
82d9978987 update CHANGELOG 2025-02-10 22:24:11 +09:00
25dba869db refactor: loosen dep compat & separate bin as feature 2025-02-10 22:21:05 +09:00
21 changed files with 1245 additions and 150 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
result*

View file

@ -21,7 +21,7 @@ stages: # List of stages for jobs, and their order of execution
- test - test
.setup-rust: .setup-rust:
image: rust:latest image: registry.cern.ch/docker.io/library/rust:latest
before_script: before_script:
- rustup component add clippy rustfmt - rustup component add clippy rustfmt

View file

@ -5,10 +5,59 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] ## [unreleased]
### Added
- Packagin with Nix flakes (with nixpkgs buildRustPackage) [!16](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/merge_requests/16)
- Now xml metadata can be overwritten with corresponding environmental variables. [!17](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/merge_requests/17)
### Changed
- Improve error message from syn in generator. !15
## [0.3.1] - 2025-04-11
### Changed
- Moved register value mask definitions from reg module to field value impl.
- Updated error messages for #[from] attributed errors.
## [0.3.0] - 2025-03-27
### Added
- Added register map validator and flattened map generator.
- Allow register with mask and fields iff the masks are matched.
### 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.
### Fixed
- Avoid mask and value constant name duplication by inserting `_VAL_` to value constants.
## [0.2.0] - 2025-02-12
### Added
- Embedding XML git metadata
- New high-level API (`generate` function)
### Fixed
- Loosen Cargo.toml dependencies
- Separate bin crate as optional feature to reduce dependency
## [0.1.0] - 2025-02-07
### Added ### Added
- Implemented basic code generation covering current CSR XML. - Implemented basic code generation covering current CSR XML.
[unreleased]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/compare/v0.3.1...main
[0.3.1]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/compare/v0.3.0...v0.3.1
[0.3.0]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/compare/v0.2.0...v0.3.0
[0.2.0]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/compare/v0.1.0...v0.2.0
[0.1.0]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/~/tags/v0.1.0 [0.1.0]: https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/~/tags/v0.1.0

25
Cargo.lock generated
View file

@ -260,6 +260,27 @@ dependencies = [
"typenum", "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]] [[package]]
name = "darling" name = "darling"
version = "0.20.10" version = "0.20.10"
@ -353,11 +374,12 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "endcap-sl-software-ri-generator" name = "endcap-sl-software-ri-generator"
version = "0.1.0" version = "0.3.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"clap", "clap",
"csv",
"env_logger", "env_logger",
"heck", "heck",
"hex", "hex",
@ -370,7 +392,6 @@ dependencies = [
"sha2", "sha2",
"syn", "syn",
"thiserror", "thiserror",
"typenum",
"vergen-gitcl", "vergen-gitcl",
] ]

View file

@ -1,8 +1,8 @@
[package] [package]
name = "endcap-sl-software-ri-generator" name = "endcap-sl-software-ri-generator"
version = "0.1.0" version = "0.3.1"
authors = ["Wataru Otsubo <wataru.otsubo@cern.ch>"] authors = ["Wataru Otsubo <wataru.otsubo@cern.ch>"]
edition = "2021" edition = "2024"
description = "A generator of register interface for mpsoc software from register map in xml format" description = "A generator of register interface for mpsoc software from register map in xml format"
repository = "https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator" repository = "https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator"
build = "build.rs" build = "build.rs"
@ -10,28 +10,33 @@ build = "build.rs"
[[bin]] [[bin]]
name = "endcap-sl-software-ri-generator" name = "endcap-sl-software-ri-generator"
path = "src/main.rs" path = "src/main.rs"
required-features = ["bin"]
[lib] [lib]
name = "endcap_sl_software_ri_generator" name = "endcap_sl_software_ri_generator"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
anyhow = "1.0.95" anyhow = { version = "1.0", optional = true }
chrono = "0.4.39" chrono = "0.4"
clap = { version = "4.5.28", features = ["derive"] } clap = { version = "4.5", features = ["derive"] }
env_logger = "0.11.6" csv = { version = "1.3.1", optional = true }
env_logger = { version = "0.11", optional = true }
heck = "0.5" heck = "0.5"
hex = "0.4.3" hex = "0.4"
itertools = "0.14" itertools = "0.14"
log = "0.4" log = "0.4"
prettyplease = "0.2" prettyplease = "0.2"
proc-macro2 = "1.0.93" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
roxmltree = "0.20" roxmltree = "0.20"
sha2 = "0.10" sha2 = "0.10"
syn = "2.0.96" syn = "2.0"
thiserror = "2.0" thiserror = "2.0"
typenum = "1.17.0"
[build-dependencies] [build-dependencies]
vergen-gitcl = { version = "1.0.0", features = ["build", "cargo", "rustc", "si"] } vergen-gitcl = { version = "1.0", features = ["build", "cargo", "rustc", "si"] }
[features]
bin = ["anyhow", "env_logger", "flatmap"]
flatmap = ["csv"]

View file

@ -1,5 +1,8 @@
# Endcap SL Software RI Generator # Endcap SL Software RI Generator
[![pipeline status](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/badges/main/pipeline.svg)](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/commits/main)
[![Latest Release](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/badges/release.svg)](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/releases)
これはXMLのレジスタマップからMPSoCソフトで使う用のコードを生成するソフトです。 これはXMLのレジスタマップからMPSoCソフトで使う用のコードを生成するソフトです。
XMLおよびそのスキーマは[L0 Muon Endcap/Endcap Sl CSR XML](https://gitlab.cern.ch/l0muon-endcap/endcap-sl-csr-xml)にあります。 XMLおよびそのスキーマは[L0 Muon Endcap/Endcap Sl CSR XML](https://gitlab.cern.ch/l0muon-endcap/endcap-sl-csr-xml)にあります。
@ -22,14 +25,15 @@ rustupのインストールには公式サイトにある`curl`スクリプト
### バイナリクレートの使い方 ### バイナリクレートの使い方
```bash ```bash
cargo build --bins --release cargo build --features=bin --bins --release
``` ```
を実行します。 を実行します。
`--release`はオプションです。 `--release`はオプションです。
`target/release`に入ってるバイナリファイルを実行するか、`cargo run -- <XML> <OUT>`で実行できます。 `target/release`に入ってるバイナリファイルを実行するか、`cargo run -- <XML> <OUT>`で実行できます。
詳しくは`--help`を見てください。 詳しくは(`-h`ではなく)`--help`を見てください。
### ライブラリクレートの使い方 ### ライブラリクレートの使い方

View file

@ -1,5 +1,8 @@
# Endcap SL Software RI Generator # Endcap SL Software RI Generator
[![pipeline status](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/badges/main/pipeline.svg)](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/commits/main)
[![Latest Release](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/badges/release.svg)](https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator/-/releases)
[日本語版](README-ja.md) [日本語版](README-ja.md)
Generates register interface for mpsoc software from register map in xml format. Generates register interface for mpsoc software from register map in xml format.
@ -25,7 +28,7 @@ Note that `cargo` is available on lxplus, so you might be able to use that (it i
Execute Execute
```bash ```bash
cargo build --bins --release cargo build --features=bin --bins --release
``` ```
to build. to build.
@ -33,7 +36,7 @@ to build.
Execute the binary generated in `target/release/`, or run with `cargo run -- <XML> <OUT>`. Execute the binary generated in `target/release/`, or run with `cargo run -- <XML> <OUT>`.
See the `--help` for more information. See the `--help`(not `-h`) for more information.
### Library crate usage ### Library crate usage

82
flake.lock generated Normal file
View file

@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1744098102,
"narHash": "sha256-tzCdyIJj9AjysC3OuKA+tMD/kDEDAF9mICPDU7ix0JA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c8cd81426f45942bb2906d5ed2fe21d2f19d95b7",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1744166053,
"narHash": "sha256-mpI7OzFwp+fUeDtZYQbVZ2YmtxTN2UNrrOwbYD27xKU=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "896158be1835589db6f42f45ef0a49b8b492cc66",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

37
flake.nix Normal file
View file

@ -0,0 +1,37 @@
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
nixpkgs,
flake-utils,
rust-overlay,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
in
{
devShells.default = pkgs.mkShell {
packages = [
pkgs.rust-bin.stable.latest.default
];
};
packages.default = pkgs.callPackage ./nix/endcap-sl-software-ri-generator.nix { };
}
);
}

View file

@ -0,0 +1,11 @@
{ rustPlatform }:
# https://nixos.org/manual/nixpkgs/stable/#rust
rustPlatform.buildRustPackage {
pname = "endcap-sl-software-ri-generator";
version = "0.3.0";
src = ../.;
cargoLock.lockFile = ../Cargo.lock;
buildFeatures = [ "bin" ];
}

View file

@ -13,7 +13,7 @@ use thiserror::Error;
use crate::parser::{ParseCustomBool, ParseEnumError, ParsePrefixedU32, ParsePrefixedU32Error}; use crate::parser::{ParseCustomBool, ParseEnumError, ParsePrefixedU32, ParsePrefixedU32Error};
use crate::types::{ use crate::types::{
BitString, Block, Field, Fifo, Memory, Module, ModuleBlockElements, MultipleParams, Register, BitString, Block, Field, Fifo, Memory, Module, ModuleBlockElements, MultipleParams, Register,
Value, Value, XmlGitInfo,
}; };
/// Possible errors in conversion, with positional information. /// Possible errors in conversion, with positional information.
@ -164,7 +164,7 @@ mod util {
} }
impl Module { impl Module {
pub fn from_xml_dom(node: Node) -> Result<Self, DomConversionError> { pub fn from_xml_dom(node: Node, git_info: XmlGitInfo) -> Result<Self, DomConversionError> {
assert!(node.parent().unwrap().is_root()); assert!(node.parent().unwrap().is_root());
assert_eq!(node.tag_name().name(), "module"); assert_eq!(node.tag_name().name(), "module");
@ -212,6 +212,7 @@ impl Module {
elements_bitstring: child_bitstrings, elements_bitstring: child_bitstrings,
elements_other: child_other, elements_other: child_other,
xmlhash: Sha256::digest(node.document().input_text()).into(), xmlhash: Sha256::digest(node.document().input_text()).into(),
git_info,
}) })
} }
} }
@ -395,12 +396,6 @@ impl Register {
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Validation // Validation
if mask.is_some() && !children.is_empty() {
return Err(DomConversionError::other_error(
"both mask and field are used in the same register",
node,
));
}
if default.is_some() && !children.is_empty() { if default.is_some() && !children.is_empty() {
return Err(DomConversionError::other_error( return Err(DomConversionError::other_error(
"both default and field are used in the same register", "both default and field are used in the same register",

View file

@ -26,10 +26,14 @@ use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum CodeGenError { pub enum CodeGenError {
#[error("tokenization(syn) error: {0}")] #[error("tokenization(syn) error: {source}: {code}")]
SynError(#[from] syn::Error), SynError {
#[error("failed to create file: {0}")] #[source]
FilePathError(String), source: syn::Error,
code: String,
},
#[error("failed to create file (name duplicated): {0}")]
FilePathDuplicatedError(String),
#[error("parent is required for {module}")] #[error("parent is required for {module}")]
ParentMissing { module: &'static str }, ParentMissing { module: &'static str },
#[error("Unsupported structure: {}", name)] #[error("Unsupported structure: {}", name)]
@ -42,11 +46,17 @@ mod util {
use super::CodeGenError; use super::CodeGenError;
pub(super) fn parse_to_ident(s: &str) -> Result<proc_macro2::Ident, CodeGenError> { pub(super) fn parse_to_ident(s: &str) -> Result<proc_macro2::Ident, CodeGenError> {
Ok(syn::parse_str(s)?) 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> { pub(super) fn parse_to_literal(s: &str) -> Result<proc_macro2::Literal, CodeGenError> {
Ok(syn::parse_str(s)?) 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 // currently only U32 is used, so `dead_code` for Debug, PartialEq
@ -130,6 +140,8 @@ impl Module {
pub fn generate_code(self) -> Result<HashMap<path::PathBuf, syn::File>, CodeGenError> { pub fn generate_code(self) -> Result<HashMap<path::PathBuf, syn::File>, CodeGenError> {
let build_metadata = format!( let build_metadata = format!(
" "
This code is auto generated using endcap_sl_software_ri_generator.
# Build metadata # Build metadata
- timestamp: {} - timestamp: {}
@ -137,9 +149,9 @@ impl Module {
## CSR XML ## CSR XML
- sha256: {} - sha256: {}
- git describe: TODO (after building step is fixed) - git describe: {}
- git commit timestamp: TODO - git commit timestamp: {}
- git SHA: TODO - git SHA: {}
## Generator ## Generator
@ -150,16 +162,18 @@ impl Module {
", ",
Local::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, false), Local::now().to_rfc3339_opts(chrono::SecondsFormat::Nanos, false),
hex::encode(self.xmlhash), hex::encode(self.xmlhash),
self.git_info.describe,
self.git_info.commit_timestamp,
self.git_info.sha,
GENERATOR_BUILD_TIMESTAMP, GENERATOR_BUILD_TIMESTAMP,
GENERATOR_GIT_DESCRIBE, GENERATOR_GIT_DESCRIBE,
GENERATOR_GIT_COMMIT_TIMESTAMP, GENERATOR_GIT_COMMIT_TIMESTAMP,
GENERATOR_GIT_SHA, GENERATOR_GIT_SHA,
); );
let files = self.generate_register_interface(None, None, HashMap::new())?; let files = self.generate_register_interface(None, None, HashMap::new())?;
Ok(files files
.into_iter() .into_iter()
.map( .map(|(path, tokens)| -> Result<(PathBuf, syn::File), _> {
|(path, tokens)| -> Result<(PathBuf, syn::File), syn::Error> {
let tokens = if path let tokens = if path
.file_name() .file_name()
.is_some_and(|file| file == "register_interface.rs") .is_some_and(|file| file == "register_interface.rs")
@ -172,11 +186,14 @@ impl Module {
} else { } else {
tokens tokens
}; };
let file: syn::File = syn::parse2(tokens)?; let file: syn::File =
syn::parse2(tokens.clone()).map_err(|e| CodeGenError::SynError {
source: e,
code: tokens.to_string(),
})?;
Ok((path, file)) Ok((path, file))
}, })
) .process_results(|kv| HashMap::from_iter(kv))
.process_results(|kv| HashMap::from_iter(kv))?)
} }
} }
@ -214,6 +231,8 @@ impl CodeGen for Module {
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let out = quote! { let out = quote! {
#(#child_mods)* #(#child_mods)*
pub use super::RegisterInterface;
}; };
files.insert(PathBuf::from("./register_interface.rs"), out); files.insert(PathBuf::from("./register_interface.rs"), out);
@ -282,7 +301,7 @@ impl CodeGen for Block {
ModuleBlockElements::Block(_) => { ModuleBlockElements::Block(_) => {
let child_upper_camel_name = util::parse_to_ident(&child_name.to_upper_camel_case())?; let child_upper_camel_name = util::parse_to_ident(&child_name.to_upper_camel_case())?;
Ok(quote! { Ok(quote! {
pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name { pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name<T> {
#snake_case_name::#child_upper_camel_name::new(self.mem_ptr) #snake_case_name::#child_upper_camel_name::new(self.mem_ptr)
} }
}) })
@ -292,7 +311,7 @@ impl CodeGen for Block {
match &register.multiple { match &register.multiple {
None => { None => {
Ok(quote! { Ok(quote! {
pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name { pub fn #snake_case_name(&self) -> #snake_case_name::#child_upper_camel_name<T> {
#snake_case_name::#child_upper_camel_name::new(self.mem_ptr) #snake_case_name::#child_upper_camel_name::new(self.mem_ptr)
} }
}) })
@ -306,7 +325,7 @@ impl CodeGen for Block {
} }
}); });
Ok(quote! { Ok(quote! {
pub fn #snake_case_name(&self) -> [#snake_case_name::#child_upper_camel_name; #num_multiple] { pub fn #snake_case_name(&self) -> [#snake_case_name::#child_upper_camel_name<T>; #num_multiple] {
[ #(#elements),* ] [ #(#elements),* ]
} }
}) })
@ -319,9 +338,9 @@ impl CodeGen for Block {
}).collect::<Result<Vec<proc_macro2::TokenStream>, CodeGenError>>()?; }).collect::<Result<Vec<proc_macro2::TokenStream>, CodeGenError>>()?;
let parent_struct = if parent_name == util::parse_to_ident("RegisterInterface").unwrap() { let parent_struct = if parent_name == util::parse_to_ident("RegisterInterface").unwrap() {
quote! {#parent_name} quote! {#parent_name<T>}
} else { } else {
quote! {#parent_name<'a>} quote! {#parent_name<'a, T>}
}; };
let child_mods = self let child_mods = self
@ -341,18 +360,20 @@ impl CodeGen for Block {
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::axic2c1::spec::AxiC2c1Spec;
use super::#parent_name; use super::#parent_name;
#(#child_mods)* #(#child_mods)*
const OFFSET: usize = #addr; const OFFSET: usize = #addr;
pub struct #upper_camel_name<'a> { pub struct #upper_camel_name<'a, T: AxiC2c1Spec> {
mem_ptr: *mut u32, mem_ptr: *mut u32,
_marker: PhantomData<&'a mut #parent_struct>, _marker: PhantomData<&'a mut #parent_struct>,
} }
impl #upper_camel_name<'_> { impl<T: AxiC2c1Spec> #upper_camel_name<'_, T> {
pub(crate) fn new(parent_ptr: *mut u32) -> Self { pub(crate) fn new(parent_ptr: *mut u32) -> Self {
#upper_camel_name { #upper_camel_name {
mem_ptr: unsafe { parent_ptr.add(OFFSET) }, mem_ptr: unsafe { parent_ptr.add(OFFSET) },
@ -364,12 +385,13 @@ impl CodeGen for Block {
} }
}; };
let (out_path, next_parent_path) = mod_file_path(parent_path, &snake_case_name); let (out_path, next_parent_path) = mod_file_path(parent_path, &snake_case_name);
log::info!("{:?}", out_path);
if let Some(old_out) = files.insert(out_path.clone(), out.clone()) { if let Some(old_out) = files.insert(out_path.clone(), out.clone()) {
log::error!("path {}", out_path.display()); log::error!("path {}", out_path.display());
log::error!("old {}", old_out.to_string()); log::error!("old {}", old_out.to_string());
log::error!("new {}", out.to_string()); log::error!("new {}", out.to_string());
return Err(CodeGenError::FilePathError(snake_case_name.to_string())); return Err(CodeGenError::FilePathDuplicatedError(
snake_case_name.to_string(),
));
}; };
let files = self.elements.into_iter().try_fold(files, |files, e| { let files = self.elements.into_iter().try_fold(files, |files, e| {
@ -435,16 +457,17 @@ impl CodeGen for Register {
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::axic2c1::spec::AxiC2c1Spec;
use crate::register_spec::{DataConversionError, Modifiable, Readable, RegisterSpec, Writable}; use crate::register_spec::{DataConversionError, Modifiable, Readable, RegisterSpec, Writable};
const OFFSET: usize = #addr; const OFFSET: usize = #addr;
pub struct #reg_name<'a> { pub struct #reg_name<'a, T: AxiC2c1Spec> {
mem_ptr: *mut u32, mem_ptr: *mut u32,
_marker: PhantomData<&'a mut super::#parent_name<'a>>, _marker: PhantomData<&'a mut super::#parent_name<'a, T>>,
} }
impl #reg_name<'_> { impl<T: AxiC2c1Spec> #reg_name<'_, T> {
pub(crate) fn new(parent_ptr: *mut u32) -> Self { pub(crate) fn new(parent_ptr: *mut u32) -> Self {
#reg_name { #reg_name {
mem_ptr: unsafe { parent_ptr.add(OFFSET) }, mem_ptr: unsafe { parent_ptr.add(OFFSET) },
@ -461,7 +484,9 @@ impl CodeGen for Register {
let (out_path, _next_parent_path) = mod_file_path(parent_path, &snake_case_name); let (out_path, _next_parent_path) = mod_file_path(parent_path, &snake_case_name);
log::info!("{:?}", out_path); log::info!("{:?}", out_path);
if files.insert(out_path, out).is_some() { if files.insert(out_path, out).is_some() {
return Err(CodeGenError::FilePathError(snake_case_name.to_string())); return Err(CodeGenError::FilePathDuplicatedError(
snake_case_name.to_string(),
));
} }
Ok(files) Ok(files)
} }

View file

@ -9,8 +9,8 @@ use quote::quote;
use crate::types::{DataType, Field, MultipleParams, Register, Value}; use crate::types::{DataType, Field, MultipleParams, Register, Value};
use super::{ use super::{
util::{self, RustUxTypes},
CodeGenError, CodeGenError,
util::{self, RustUxTypes},
}; };
/// Generate underlying `T` and its implementation for `RegisterSpec`. /// Generate underlying `T` and its implementation for `RegisterSpec`.
@ -232,11 +232,11 @@ fn generate_single_field(
} }
} }
FieldType::CustomValue(values) => { FieldType::CustomValue(values) => {
let additional = let values_enum_masks =
generate_custom_values_const_enumdef(&base_type, &snake_case_name, values); generate_custom_values_const_enumdef(&base_type, &snake_case_name, values);
quote! { quote! {
const #mask_name: #base_type = #mask_val; const #mask_name: #base_type = #mask_val;
#additional #values_enum_masks
} }
} }
}; };
@ -320,13 +320,8 @@ fn generate_single_ux_field(
(code_getter, code_setter) (code_getter, code_setter)
} }
fn custom_value_const_name(field_name: &Ident, value_name: &str) -> Ident { fn custom_value_const_name(value_name: &str) -> Ident {
util::parse_to_ident(&format!( util::parse_to_ident(&format!("VAL_{}", value_name.to_shouty_snake_case())).unwrap()
"{}_{}",
field_name.to_string().to_shouty_snake_case(),
value_name.to_shouty_snake_case()
))
.unwrap()
} }
/// Generate const var and value enum definition. /// Generate const var and value enum definition.
@ -338,7 +333,7 @@ fn generate_custom_values_const_enumdef(
values: &[Value], values: &[Value],
) -> TokenStream { ) -> TokenStream {
let consts = values.iter().map(|value| { let consts = values.iter().map(|value| {
let const_name = custom_value_const_name(field_name, &value.name); let const_name = custom_value_const_name(&value.name);
let val = value.data; let val = value.data;
quote! { quote! {
const #const_name: #base_type = #val; const #const_name: #base_type = #val;
@ -356,10 +351,13 @@ fn generate_custom_values_const_enumdef(
util::parse_to_ident(&field_name.to_string().to_upper_camel_case()).unwrap(); util::parse_to_ident(&field_name.to_string().to_upper_camel_case()).unwrap();
quote! { quote! {
#(#consts)*
pub enum #value_enum_name { pub enum #value_enum_name {
#(#variants),* #(#variants),*
} }
impl #value_enum_name {
#(#consts)*
}
} }
} }
@ -376,14 +374,14 @@ fn generate_single_custom_values_field(
let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values
.iter() .iter()
.map(|value| { .map(|value| {
let const_name = custom_value_const_name(&snake_case_name, &value.name); let const_name = custom_value_const_name(&value.name);
let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap(); let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap();
( (
quote! { quote! {
#const_name => #value_enum_name::#variant_name #value_enum_name::#const_name => #value_enum_name::#variant_name
}, },
quote! { quote! {
#value_enum_name::#variant_name => #const_name #value_enum_name::#variant_name => #value_enum_name::#const_name
}, },
) )
}) })
@ -596,14 +594,14 @@ fn generate_multiple_custom_values_field(
let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values let (getter_match_arms, setter_match_arms): (Vec<_>, Vec<_>) = values
.iter() .iter()
.map(|value| { .map(|value| {
let const_name = custom_value_const_name(&snake_case_name, &value.name); let const_name = custom_value_const_name(&value.name);
let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap(); let variant_name = util::parse_to_ident(&value.name.to_upper_camel_case()).unwrap();
( (
quote! { quote! {
#const_name => #value_enum_name::#variant_name #value_enum_name::#const_name => #value_enum_name::#variant_name
}, },
quote! { quote! {
#value_enum_name::#variant_name => #const_name #value_enum_name::#variant_name => #value_enum_name::#const_name
}, },
) )
}) })

View file

@ -11,19 +11,19 @@ pub(super) fn gen_registerspec_impl(
) -> TokenStream { ) -> TokenStream {
let impl_rw = match modf { let impl_rw = match modf {
RwSpecifier::R => quote! { RwSpecifier::R => quote! {
impl Readable for #reg_name<'_> {} impl<T: AxiC2c1Spec> Readable for #reg_name<'_, T> {}
}, },
RwSpecifier::W => quote! { RwSpecifier::W => quote! {
impl Writable for #reg_name<'_> {} impl<T: AxiC2c1Spec> Writable for #reg_name<'_, T> {}
}, },
RwSpecifier::RW => quote! { RwSpecifier::RW => quote! {
impl Readable for #reg_name<'_> {} impl<T: AxiC2c1Spec> Readable for #reg_name<'_, T> {}
impl Writable for #reg_name<'_> {} impl<T: AxiC2c1Spec> Writable for #reg_name<'_, T> {}
impl Modifiable for #reg_name<'_> {} impl<T: AxiC2c1Spec> Modifiable for #reg_name<'_, T> {}
}, },
}; };
quote! { quote! {
impl RegisterSpec for #reg_name<'_> { impl<T: AxiC2c1Spec> RegisterSpec for #reg_name<'_, T> {
type Ux = #type_ux; type Ux = #type_ux;
type T = #type_t; type T = #type_t;

146
src/integrated.rs Normal file
View file

@ -0,0 +1,146 @@
use std::path;
use itertools::Itertools;
use thiserror::Error;
use crate::{
converter, generator,
io::{XmlGitInfoError, get_xml_gitinfo},
types, validator,
};
#[derive(Debug, Error)]
pub enum Error {
#[error("error in getting xml git info")]
XmlGitInfoError(#[from] XmlGitInfoError),
#[error("io error")]
IOError(#[from] std::io::Error),
#[error("xml parse error")]
XmlParseError(#[from] roxmltree::Error),
#[error("dom conversion error")]
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")]
CodeGenError(#[from] generator::CodeGenError),
#[cfg(feature = "flatmap")]
#[error("csv write error")]
CsvWriteError(#[from] csv::Error),
}
/// Generate register interface code from xml in `xml` and write them under directory `out`.
pub fn generate(xml: &path::Path, out: &path::Path) -> Result<(), Error> {
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 files = register_map.generate_code()?;
if log::log_enabled!(log::Level::Debug) {
for (path, code) in &files {
log::debug!("path: {:?}", path);
log::debug!("{}", prettyplease::unparse(code));
log::trace!("{}", prettyplease::unparse(code));
}
}
crate::write_to_files(files, out)?;
log::debug!("wrote to files");
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.path.join("/");
let multiple_id = reg.multiple_id.map_or("".to_string(), |x| x.to_string());
wtr.write_record([
format!("0x{:04x}", addr),
path,
reg.register.name.clone(),
multiple_id,
reg.register.modf.to_string(),
reg.register
.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(())
}

117
src/io.rs
View file

@ -1,6 +1,121 @@
//! File IO for generated codes. //! File IO for generated codes.
use std::{collections::HashMap, fs, io, path}; use std::{collections::HashMap, env, ffi::OsString, fs, io, path, process};
use thiserror::Error;
use crate::types::XmlGitInfo;
const ENVVAR_XML_GIT_DESCRIBE: &str = "XML_GIT_DESCRIBE";
const ENVVAR_XML_GIT_TIMESTAMP: &str = "XML_GIT_TIMESTAMP";
const ENVVAR_XML_GIT_SHA: &str = "XML_GIT_SHA";
#[derive(Debug, Error)]
pub enum XmlGitInfoError {
#[error("io error while getting xml git info: {msg} {source}")]
IOError {
msg: &'static str,
#[source]
source: io::Error,
},
#[error("git failed: {msg}: {stderr}")]
CommandFailed { msg: &'static str, stderr: String },
#[error("env var {var} is not unicode: {}", s.to_string_lossy())]
NotUnicodeEnvVar { var: &'static str, s: OsString },
}
impl XmlGitInfoError {
fn io_error(e: io::Error, msg: &'static str) -> Self {
XmlGitInfoError::IOError { msg, source: e }
}
}
pub(crate) fn get_xml_gitinfo(xml_path: &path::Path) -> Result<XmlGitInfo, XmlGitInfoError> {
let describe = match env::var(ENVVAR_XML_GIT_DESCRIBE) {
Ok(describe) => describe,
Err(env::VarError::NotUnicode(s)) => {
return Err(XmlGitInfoError::NotUnicodeEnvVar {
var: ENVVAR_XML_GIT_DESCRIBE,
s,
});
}
Err(env::VarError::NotPresent) => {
let out_describe = process::Command::new("git")
.args(["describe", "--always", "--dirty"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git describe"))?;
let describe = if out_describe.status.success() {
String::from_utf8_lossy(&out_describe.stdout)
.trim()
.to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git describe",
stderr: String::from_utf8_lossy(&out_describe.stderr).into_owned(),
});
};
describe
}
};
let timestamp = match env::var(ENVVAR_XML_GIT_TIMESTAMP) {
Ok(timestamp) => timestamp,
Err(env::VarError::NotUnicode(s)) => {
return Err(XmlGitInfoError::NotUnicodeEnvVar {
var: ENVVAR_XML_GIT_TIMESTAMP,
s,
});
}
Err(env::VarError::NotPresent) => {
let timestamp_out = process::Command::new("git")
.args(["log", "-1", "--pretty=format:'%cI'"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git log (timestamp)"))?;
let timestamp = if timestamp_out.status.success() {
String::from_utf8_lossy(&timestamp_out.stdout)
.trim()
.to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git log (timestamp)",
stderr: String::from_utf8_lossy(&timestamp_out.stderr).into_owned(),
});
};
timestamp
}
};
let git_sha = match env::var(ENVVAR_XML_GIT_SHA) {
Ok(sha) => sha,
Err(env::VarError::NotUnicode(s)) => {
return Err(XmlGitInfoError::NotUnicodeEnvVar {
var: ENVVAR_XML_GIT_SHA,
s,
});
}
Err(env::VarError::NotPresent) => {
let sha = process::Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(xml_path.parent().unwrap())
.output()
.map_err(|e| XmlGitInfoError::io_error(e, "git rev-parse (sha)"))?;
let git_sha = if sha.status.success() {
String::from_utf8_lossy(&sha.stdout).trim().to_owned()
} else {
return Err(XmlGitInfoError::CommandFailed {
msg: "git rev-parse (sha)",
stderr: String::from_utf8_lossy(&sha.stderr).into_owned(),
});
};
git_sha
}
};
Ok(XmlGitInfo {
describe,
commit_timestamp: timestamp,
sha: git_sha,
})
}
/// Write formatted codes generated with [`Module::generate_code`](crate::types::Module::generate_code). /// Write formatted codes generated with [`Module::generate_code`](crate::types::Module::generate_code).
pub fn write_to_files( pub fn write_to_files(

View file

@ -8,32 +8,14 @@
//! //!
//! # Example //! # Example
//! //!
//! Here's a typical usage: //! See [`generate`].
//! ```no_run
//! use endcap_sl_software_ri_generator::types;
//!
//! let xmlfile = std::fs::read_to_string("./csr.xml")?;
//! let doc = roxmltree::Document::parse_with_options(
//! &xmlfile,
//! roxmltree::ParsingOptions {
//! allow_dtd: true,
//! nodes_limit: u32::MAX,
//! },
//! )?;
//!
//! let register_map = types::Module::from_xml_dom(doc.root_element())?;
//!
//! let files = register_map.generate_code()?;
//! endcap_sl_software_ri_generator::write_to_files(files, &std::path::PathBuf::from("out"))?;
//!
//! # Ok::<(), anyhow::Error>(())
//! ```
//! //!
//! # Overview //! # Overview
//! //!
//! 1. Convert [`roxmltree::Document`] to register map represented with types defined in //! 1. Convert [`roxmltree::Document`] to register map represented with types defined in
//! [`types`], filling missing parameters. See [`converter`]. //! [`types`], filling missing parameters. See [`converter`].
//! 2. Generate [`proc_macro2::TokenStream`] from register map produced in the previous step. See //! 2. Validate the register map. See [`types::Module::validate`] in [`validator`].
//! 3. Generate [`proc_macro2::TokenStream`] from register map produced in the previous step. See
//! [`generator`]. //! [`generator`].
//! //!
//! # modules //! # modules
@ -41,13 +23,17 @@
//! - [`converter`]: DOM to internal representation //! - [`converter`]: DOM to internal representation
//! - [`generator`]: internal representation to rust code //! - [`generator`]: internal representation to rust code
//! - [`io`]: formatting and printing //! - [`io`]: formatting and printing
//! - [`validator`]: validate the register map and generate flattened register map
pub mod converter; pub mod converter;
pub mod generator; pub mod generator;
pub mod integrated;
pub mod io; pub mod io;
pub mod meta; pub mod meta;
mod parser; mod parser;
mod type_traits; mod type_traits;
pub mod types; pub mod types;
pub mod validator;
pub use integrated::generate;
pub use io::write_to_files; pub use io::write_to_files;

View file

@ -1,18 +1,41 @@
use std::{fs, path}; use std::path;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::{Parser, Subcommand};
use endcap_sl_software_ri_generator::types;
use itertools::Itertools;
/// Generate register interface from register map xml. /// Validate the register map xml and generate register interface from register map xml.
/// See `help` with each commands to see detailed explanations.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about)] #[command(version, about, long_about)]
struct Args { struct Args {
/// Input XML register map. /// Input XML register map.
///
/// The path must be a valid git repository to get git metadata.
/// You can also force these metadata with environmental variables `XML_GIT_DESCRIBE`,
/// `XML_GIT_TIMESTAMP` and `XML_GIT_SHA`.
xml: path::PathBuf, xml: path::PathBuf,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// Generate register interface code.
///
/// Also run validation before generation.
Generate {
/// Output directory. /// Output directory.
out: path::PathBuf, out: path::PathBuf,
},
/// Generate flattened register map in CSV.
///
/// Also run validation before generation.
#[cfg(feature = "flatmap")]
Flatmap {
/// Flattened csv out path.
/// Print to stdout by default.
flatmap: Option<path::PathBuf>,
},
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -21,35 +44,17 @@ fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
log::debug!("args: {:?}", args); log::debug!("args: {:?}", args);
let xmlfile = fs::read_to_string(args.xml)?; match args.command {
let doc = roxmltree::Document::parse_with_options( Commands::Generate { out } => endcap_sl_software_ri_generator::generate(&args.xml, &out)?,
&xmlfile, #[cfg(feature = "flatmap")]
roxmltree::ParsingOptions { Commands::Flatmap { flatmap } => {
allow_dtd: true, let outpath: Option<&path::Path> = match flatmap {
nodes_limit: u32::MAX, Some(ref p) => Some(p.as_path()),
}, None => None,
)?; };
log::debug!("Parsed: {:#?}", doc); endcap_sl_software_ri_generator::integrated::generate_flatmap(&args.xml, outpath)?
let register_map = types::Module::from_xml_dom(doc.root_element())?;
log::info!("read: {:?}", register_map);
log::debug!("read: {:#?}", register_map);
let files = register_map.generate_code()?;
if log::log_enabled!(log::Level::Debug) {
for (path, code) in &files {
log::debug!("path: {:?}", path);
log::debug!("{}", prettyplease::unparse(code));
log::trace!("{}", prettyplease::unparse(code));
} }
} }
if log::log_enabled!(log::Level::Info) {
for filepath in files.keys().sorted() {
log::info!("{}", filepath.display());
}
}
endcap_sl_software_ri_generator::write_to_files(files, &args.out)?;
Ok(()) Ok(())
} }

View file

@ -1,6 +1,8 @@
//! Util traits to get info from types in [`crate::types`]. //! 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 { pub(crate) trait GetName {
fn get_name(&self) -> String; fn get_name(&self) -> String;
@ -28,3 +30,14 @@ impl GetName for Register {
self.name.clone() 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)
}
}

View file

@ -11,6 +11,7 @@ pub struct Module {
pub elements_bitstring: Vec<BitString>, pub elements_bitstring: Vec<BitString>,
pub elements_other: Vec<ModuleBlockElements>, pub elements_other: Vec<ModuleBlockElements>,
pub xmlhash: [u8; 32], pub xmlhash: [u8; 32],
pub git_info: XmlGitInfo,
} }
#[derive(Debug)] #[derive(Debug)]
@ -151,3 +152,10 @@ pub enum FieldFifoInterface {
Block, Block,
Both, Both,
} }
#[derive(Debug)]
pub struct XmlGitInfo {
pub(crate) describe: String,
pub(crate) commit_timestamp: String,
pub(crate) sha: String,
}

591
src/validator.rs Normal file
View file

@ -0,0 +1,591 @@
//! Validate the address assignment and generate a flattened register map.
use thiserror::Error;
use crate::types::{Block, Module, ModuleBlockElements, Register};
#[derive(Debug, Clone)]
pub struct FlattenedRegisterEntry<'a> {
/// contains all parents
pub path: Vec<&'a str>,
pub register: &'a Register,
/// Id of multiple, if the register is multiple
pub multiple_id: Option<u32>,
/// Used bits
pub field_masks: u32,
}
/// Flattened register map.
type FlattenedRegisterMap<'a> = Vec<Option<FlattenedRegisterEntry<'a>>>;
impl Module {
/// Validate the address assignment, generating a flatten register map.
/// Returns a tuple of [`FlattenedRegisterMap`] and [`ValidationError`]s vector.
/// Empty errors vector means there is no errors in the 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, PartialEq)]
pub enum ValidationError {
#[error("address duplicated at 0x{address:08x}: \"{existing_name}\", \"{new_name}\"")]
RegisterAddressDuplicated {
address: u32,
existing_name: String,
new_name: String,
},
#[error("address 0x{address:08x} is out of range: \"{name}\"")]
AddressOutofRange { address: u32, name: String },
#[error(
"field masks are duplicated at 0x{address:08x}: \"{reg_name}\", bits: 0x{duplicated_mask:08x}"
)]
FieldMaskDuplicated {
address: u32,
reg_name: String,
duplicated_mask: u32,
},
#[error(
"register \"{name}\" at 0x{address:08x} has mask 0x{reg_mask:08x} that doesn't match sum of field masks: 0x{fields_mask:08x}"
)]
RegisterAndFieldMaskMismatch {
address: u32,
name: String,
reg_mask: u32,
fields_mask: u32,
},
#[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 new_name = {
let mut path = path.clone();
path.push(&self.name);
path.join("/")
};
let (reg_mask, duplicate_bits) = match self.elements.is_empty() {
true => (0xffff_ffff, 0),
false => self
.elements
.iter()
.fold((0, 0), |(sum, _duplicate_bits), field| {
let field_mask = match &field.multiple {
Some(multiple) => (0..multiple.multiple)
.map(|multiple_id| field.mask << (multiple.offset * multiple_id))
.sum(),
None => field.mask,
};
(sum + field_mask, sum & field_mask)
}),
};
if duplicate_bits != 0 {
errors.push(ValidationError::FieldMaskDuplicated {
address: addr,
reg_name: new_name,
duplicated_mask: duplicate_bits,
});
return (mapping, errors);
}
if let Some(mask) = self.mask {
let has_fields = !self.elements.is_empty();
if has_fields && mask != reg_mask {
errors.push(ValidationError::RegisterAndFieldMaskMismatch {
address: addr,
name: new_name.clone(),
reg_mask: mask,
fields_mask: reg_mask,
});
return (mapping, errors);
}
};
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<_> = match mapping.get_mut::<usize>(addr.try_into().unwrap()) {
Some(regmap) => regmap,
None => {
errors.push(ValidationError::AddressOutofRange {
address: addr,
name: self.name.clone(),
});
continue;
}
};
if let Some(old) = regmap {
let existing_name = {
let mut path = old.path.clone();
path.push(&old.register.name);
path.join("/")
};
if old.register.name != self.name {
errors.push(ValidationError::RegisterAddressDuplicated {
address: addr,
existing_name,
new_name: new_name.clone(),
});
continue;
}
log::trace!(
"defining same register at 0x{addr:08x}: {:?}, {:?}",
old.register,
self
);
let duplicate_bits = old.field_masks & reg_mask;
if duplicate_bits != 0 {
errors.push(ValidationError::FieldMaskDuplicated {
address: addr,
reg_name: new_name.clone(),
duplicated_mask: duplicate_bits,
});
}
let multiple_id = if len == 1 { None } else { Some(id) };
*regmap = Some(FlattenedRegisterEntry {
path: path.clone(),
register: self,
multiple_id,
field_masks: old.field_masks | reg_mask,
})
} else {
let multiple_id = if len == 1 { None } else { Some(id) };
*regmap = Some(FlattenedRegisterEntry {
path: path.clone(),
register: self,
multiple_id,
field_masks: reg_mask,
})
}
}
(mapping, errors)
}
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use crate::{
types::{
Field, Module, ModuleBlockElements, MultipleParams, Register, RwSpecifier, XmlGitInfo,
},
validator::ValidationError,
};
#[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!(!errors.is_empty());
assert_eq!(errors.len(), 1);
}
// Fields are orthogonal
#[test]
fn duplicate_reg_but_fields_are_ok() {
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![
Field {
name: "field_1".to_string(),
mask: 0x0000_00ff,
interface: None,
multiple: None,
default: None,
sclr: None,
desc: Some("field 1 description".to_string()),
elements: vec![],
},
Field {
name: "field_2".to_string(),
mask: 0x0000_ff00,
interface: None,
multiple: None,
default: None,
sclr: None,
desc: Some("field 2 description".to_string()),
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![
Field {
name: "field_3".to_string(),
mask: 0x00ff_0000,
interface: None,
multiple: None,
default: None,
sclr: None,
desc: Some("field 3 description".to_string()),
elements: vec![],
},
Field {
name: "field_4".to_string(),
mask: 0xff00_0000,
interface: None,
multiple: None,
default: None,
sclr: None,
desc: Some("field 4 description".to_string()),
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 register_with_mask_and_field() {
let reg1 = Register {
name: "test1".to_string(),
addr: 0,
r#type: crate::types::DataType::D32,
mask: Some(0x000f_00ff),
modf: RwSpecifier::R,
multiple: None,
default: None,
desc: None,
elements: vec![
Field {
name: "field_1".to_string(),
mask: 0x0000_0001,
interface: None,
multiple: Some(MultipleParams {
multiple: 8,
offset: 1,
}),
default: None,
sclr: None,
desc: Some("field 1 description".to_string()),
elements: vec![],
},
Field {
name: "field_2".to_string(),
mask: 0x0003_0000,
interface: None,
multiple: Some(MultipleParams {
multiple: 2,
offset: 2,
}),
default: None,
sclr: None,
desc: Some("field 1 description".to_string()),
elements: vec![],
},
],
};
let module = Module {
name: "module".to_string(),
addr: 0,
size: 0x1,
amod: None,
r#type: None,
desc: None,
elements_bitstring: vec![],
elements_other: vec![ModuleBlockElements::Register(reg1)],
xmlhash: [0; 32],
git_info: XmlGitInfo {
describe: "".to_string(),
commit_timestamp: "".to_string(),
sha: "".to_string(),
},
};
let (maps, errors) = module.validate();
eprintln!("{:#?}", maps);
eprintln!("{:#?}", errors);
eprintln!(
"{:#?}",
maps.iter().filter(|e| e.is_some()).collect_vec().len()
);
assert_eq!(maps.len(), 0x1);
assert!(errors.is_empty());
assert_eq!(maps.iter().filter(|e| e.is_some()).collect_vec().len(), 0x1);
}
#[test]
fn register_with_invalid_mask_and_field() {
let reg1 = Register {
name: "test1".to_string(),
addr: 0,
r#type: crate::types::DataType::D32,
mask: Some(0x0000_000f),
modf: RwSpecifier::R,
multiple: None,
default: None,
desc: None,
elements: vec![Field {
name: "field_1".to_string(),
mask: 0x0000_0001,
interface: None,
multiple: Some(MultipleParams {
multiple: 8,
offset: 1,
}),
default: None,
sclr: None,
desc: Some("field 1 description".to_string()),
elements: vec![],
}],
};
let module = Module {
name: "module".to_string(),
addr: 0,
size: 0x1,
amod: None,
r#type: None,
desc: None,
elements_bitstring: vec![],
elements_other: vec![ModuleBlockElements::Register(reg1)],
xmlhash: [0; 32],
git_info: XmlGitInfo {
describe: "".to_string(),
commit_timestamp: "".to_string(),
sha: "".to_string(),
},
};
let (maps, errors) = module.validate();
eprintln!("{:#?}", maps);
eprintln!("{:#?}", errors);
eprintln!(
"{:#?}",
maps.iter().filter(|e| e.is_some()).collect_vec().len()
);
assert_eq!(maps.len(), 0x1);
assert_eq!(errors.len(), 1);
assert_eq!(maps.iter().filter(|e| e.is_some()).collect_vec().len(), 0x0);
assert_eq!(
errors.first().unwrap(),
&ValidationError::RegisterAndFieldMaskMismatch {
address: 0x0,
name: "test1".to_string(),
reg_mask: 0x0000_000f,
fields_mask: 0x0000_00ff
}
);
}
}